基于劫持启动进程BypassDisablefuc
Bypass_disablefunc via LD_PRELOAD
背景
在做ctf题的时候,千辛万苦获得了webshell,蚁剑也连接上了,但是查看不了其他目录,也看不了文件,很有可能是open basedir
和disablefunc
的设置导致。这时候我们查看phpinfo(养成rce前先查阅phpinfo的好习惯),发现果然如此,与系统命令相关的函数全被禁止(例如[极客大挑战 2019]RCE ME)。这时候我们也许可以直接用蚁剑的bypass插件逃课,但是这种方法成功率较低,本篇介绍正规方法:使用LD_PRELOAD
和putenv
来劫持进程,从而命令执行。
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:
- 用c语言编译一个恶意的可以执行命令的so文件
- 在php文件中使用putenv设置LD_PRELOAD为恶意文件路径
- 使用某个php函数,触发shared library,从而成功rce
如何找到合适的php函数
传统方法:劫持mail函数
1 |
|
strace一下,可以看到运行这个脚本的时候,程序会启动子进程来调用sendmail,有很多函数可以可以使用,这里可以选择getuid(),然后编写一个c文件:
1 |
|
注意这里的unset是为了防止形成死循环。然后用gcc编译成so,运行php脚本即可执行系统命令。
1 | gcc -c -fPIC hack.c -o hack |
改进方法:劫持启动进程
之所以劫持 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 |
|
但是依然要找到一个函数来启动系统进程,通常题目如果禁掉mail就会有所提示别的函数(例如0ctf wallbreaker中的imagick)。
做题流程
直接将这个bypass的so上传到服务器,再通过webshell执行php命令就行了。
0CTF-2019 Wallbreaker
这题因为给了我们webshell
,我们可以在/tmp/md5($_SERVER['REMOTE_ADDR'])/
路径下写文件,但是不能执行系统函数,题目告诉我们要想办法去执行/readflag
文件。
做法就是先上传一个.so
文件
1 |
|
通过这个文件中包含的函数来执行任意指令。 然后因为Imagick
在将bmp
编码的图片文件转换为wdp
编码的文件的时候,会触发一个新进程,从而就可以执行.so
文件中的恶意函数了
1 | $a=new Imagick(); |
再读取目录下的flag.txt就行。
[极客大挑战 2019]RCE ME1
还顺带附加了点无字符命令执行,用异或绕过,写shell:
1 | ?code=${_GET}[_](${_GET}[_]);&_=assert&_=eval($_POST['a']) |
用蚁剑连接后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 |
基于劫持启动进程BypassDisablefuc