freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

无需sendmail:巧用LD_PRELOAD突破disable_functions
2018-12-21 14:00:03

*本文原创作者:yangyangwithgnu,本文属FreeBuf原创奖励计划,未经许可禁止转载

摘要:千辛万苦拿到的 webshell 居然无法执行系统命令,怀疑服务端 disable_functions 禁用了命令执行函数,通过环境变量 LD_PRELOAD 劫持系统函数,却又发现目标根本没安装 sendmail,无法执行命令的 webshell 是无意义的,看我如何突破!

半月前逛“已黑网站列表”时复审一小电商网站,“列表”中并未告知漏洞详情,简单浏览了下功能,只有注册、登录、下单、支付等几个而已。登录接口中,找到个 RCE(远程代码执行,非远程命令执行)漏洞:

image.png顺势写入菜刀马后连接:

image.png习惯上,getshell 后我会先了解下该系统配置,虚拟终端中执行 cat /proc/meminfo 但执行报错:

image.png怀疑有 WAF 拦劫了待执行的命令,尝试了空字符串、路径扩展、自定义变量平时常用的几种绕命令执行限制的手法,结果都失败:

image.png无命令执行功能的 webshell 是无意义的,得突破!

通常来说,导致 webshell 不能执行命令的原因大概有三类:一是 php.ini 中用 disable_functions 指示器禁用了 system()、exec() 等等这类命令执行的相关函数;二是 web 进程运行在 rbash 这类受限 shell 环境中;三是 WAF 拦劫。若是一则无法执行任何命令,若是二、三则可以执行少量命令。从当前现象来看,很可能由 disable_functions 所致。为验证,我利用前面的 RCE 漏洞执行 phpinfo(),确认的确如此:

image.png有四种绕过 disable_functions 的手法:第一种,攻击后端组件,寻找存在命令注入的、web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞;第二种,寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的 popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼;第三种,mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制;第四种,利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。

尝试第一种时,我用 phpinfo() 查看 ImageMagick 版本为 v6.9.4-10:image.png

用 searchsploit(exploit-db.com 的本地版)搜索存在命令注入的版本为 v6.9.3-9 或 v7.0.1-0:

image.png显然,当前 ImageMagick 无法利用;尝试第二种时,常见的、不常见的、罕见的(如 dl()),所有可启动进程的函数均被禁用;尝试第三种时,发现并未启用 mod_cgi 模式。所有希望,寄托在 LD_PRELOAD。

设想这样一种思路:利用漏洞控制 web 启动新进程 a.bin(即便进程名无法让我随意指定),a.bin 内部调用系统函数 b(),b() 位于系统共享对象 c.so 中,所以系统为该进程加载共 c.so,我想法在 c.so 前优先加载可控的 c_evil.so,c_evil.so 内含与 b() 同名的恶意函数,由于 c_evil.so 优先级较高,所以,a.bin 将调用到 c_evil.so 内 b() 而非系统的 c.so 内 b(),同时,c_evil.so 可控,达到执行恶意代码的目的。基于这一思路,将突破 disable_functions 限制执行操作系统命令这一目标,大致分解成几步在本地推演:查看进程调用系统函数明细、操作系统环境下劫持系统函数注入代码、找寻内部启动新进程的 PHP 函数、PHP 环境下劫持系统函数注入代码。

查看进程调用系统函数明细。linux 创建新进程的过程较为复杂,我关心进程加载了哪些共享对象、可能调用哪些 API、实际调用了哪些 API。比如,运行 /usr/bin/id,通过 ldd 可查看系统为其加载的共享对象:

image.png由于可执行文件 /usr/bin/id 内含符号表,所以,运行 nm -D /usr/bin/id 2>&1 或 readelf -Ws /usr/bin/id 可查看该程序可能调用的系统 API 明细:

image.png由于程序运行时会根据命令行选项、运行环境作出不同反应,导致真正运行时调用的 API 可能只是 readefl 查看的子集,你可以运行 strace -f /usr/bin/id 2>&1 跟踪实际 API 调用情况,比如,实际调用 open() 的入参、返回值一目了然:

image.png操作系统环境下劫持系统函数注入代码。linux 的环境变量 LD_PRELOAD 是一种类似 win32 API hook 的更优雅的实现,适用于打热补丁、读取进程空间数据、禁止程序调用指定 API、调试程序等等场景,甚至可以在不更改原始可执行文件前提下植入后门(管理员常用的 /bin/ps)。由于被劫持的系统函数得由我们重新实现一次,函数原型必须一致,为减少复杂性,我会选择劫持那些无参数且常用的系统函数,getuid() 就适合,以此为例,完整劫持过程步骤大致如下:首先,用 man 2 getuid 查看函数原型:

image.png然后,编写同原型的 getuid() 函数,保存至 getuid_shadow.c,源码为:

image.png执行 gcc -shared -fPIC getuid_shadow.c -o getuid_shadow.so 将其编译为共享对象:

image.png最后,借助环境变量 LD_PRELOAD 劫持系统函数 getuid(),获取控制权。执行 LD_PRELOAD=/root/getuid_shadow.so /usr/bin/id,注入代码成功执行:

image.png注意,LD_PRELOAD 是进程独占环境变量,类似于命令适配器,它与待执行命令间必须为空白字符,而非命令分隔符(;、&&、||)。

找寻内部启动新进程的 PHP 函数。虽然 LD_PRELOAD 为我提供了劫持系统函数的能力,但前提是我得控制 php 启动外部程序才行(只要有进程启动行为即可,无所谓是谁)。常见的 system() 启动程序方式显然不行,否则就不存在突破 disable_functions 一事了。PHP 脚本中除了调用 system()、exec()、shell_exec() 等等一堆 php 函数外,还有哪种可能启动外部程序呢?php 解释器自身!比如,php 函数 goForward() 实现“前进”的功能,php 函数 goForward() 又由组成 php 解释器的 C 语言模块之一的 move.c 实现,C 模块 move.c 内部又通过调用外部程序 go.bin 实现,那么,我的 php 脚本中调用了函数 goForward(),势必启动外部程序 go.bin。现在,我需要找到类似 goForward() 的真实存在的 PHP 函数。印象中,处理图片、请求网页、发送邮件等三类场景中可能存在我想要的函数,我得逐一验证。处理图片,通常调用 PHP 封装的 ImageMagick 库,新建 image.php,调用 Imagick():

image.png运行 strace -f php image.php 2>&1 | grep -A2 -B2 execve 查看 Imagick() 是否启动新进程:

image.png第一个 execve 是启动 PHP 解释器而已,必须找到第二个 execve,没有则说明并未启动新进程;请求网页,新建 http.php,调用 curl_init():

image.png运行 strace -f php http.php 2>&1 | grep -A2 -B2 execve 查看 curl_init() 是否启动新进程:

image.png仍然不是我要的;发送邮件,新建 mail.php,调用 mail():

image.png运行 strace -f php mail.php 2>&1 | grep -A2 -B2 execve 查看 mail() 是否启动新进程:image.pngbingo!mail() 内部启动了 /usr/sbin/sendmail、/usr/sbin/postdrop 两个新进程,它就是我一直苦寻的函数(用相同的测试方式,还找到一个 imap_mail())。

PHP 环境下劫持系统函数注入代码。mail.php 内增加设置 LD_PRELOAD 的代码:

image.png然后将 mail.php 以及内含 mail() 函数的共享对象 getuid_shadow.so 放入 web 目录 /var/www/:

image.png执行 mail.php 之后,找到 getuid_shadow.so 中 mail() 创建的文件 /tmp/evil,成功在 PHP 环境下不借助任何 PHP 命令执行函数执行命令:

image.png有了前面的分析,看我如何在目标站点绕过 disable_functions 执行系统命令。

首先,基于前面的 mail.php 写了个小马 bypass_disablefunc.php:image.png

bypass_disablefunc.php 提供三个 GET 参数。一是 cmd 参数,待执行的系统命令(如 pwd);二是 outpath 参数,保存命令执行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,另外关于该参数,你应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;三是 sopath 参数,指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so),另外关于该参数,你应注意 web 是否可跨目录访问到它。此外,bypass_disablefunc.php 拼接命令和输出路径成为完整的命令行,所以你不用在 cmd 参数中重定向了:

$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";

同时,通过环境变量 EVIL_CMDLINE 向 bypass_disablefunc_x64.so 传递具体执行的命令行信息:

putenv("EVIL_CMDLINE=" . $evil_cmdline);

然后,基于 getuid_shadow.c 编写劫持函数的代码 bypass_disablefunc.c。回想下,先前我之所以劫持 getuid(),是因为 sendmail 程序会调用该函数,在真实环境中,存在两方面问题:一是,某些环境中,web 禁止启用 senmail、甚至系统上根本未安装 sendmail,也就谈不上劫持 getuid(),通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件;二是,即便目标可以启用 sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts(如,127.0.0.1    lamp、lamp.、lamp.com)。基于这两个原因,我不得不放弃劫持函数 getuid(),必须找个更普适的方法。回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那我就完全可以不依赖 sendmail 了。这种场景与 C++ 的构造函数简直神似!几经搜索后了解到,GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。强调下,这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,我们不要局限于仅劫持某一函数,而应考虑劫持共享对象。bypass_disablefunc.c 源码如下:

image.png从环境变量 EVIL_CMDLINE 中接收 bypass_disablefunc.php 传递过来的待执行的命令行。

接着,用命令 gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc_x64.so 将 bypass_disablefunc.c 编译为共享对象 bypass_disablefunc_x64.so:

image.png你要根据目标架构编译成不同版本,在 x64 的环境中编译,若不带编译选项则默认为 x64,若要编译成 x86 架构需要加上 -m32 选项。

最后,用菜刀将 bypass_disablefunc.php 和 bypass_disablefunc_x64.so 传到目标:

image.png指定好命令输出路径、共享对象路径后,在 bypass_disablefunc.php 上再次执行先前失败的命令 cat /proc/meminfo:

image.png啊哈!很酷对不对。

好了,巧用 LD_PRELOAD 突破 disable_functions 的手法就是这样子,唯一条件,PHP 支持putenv()、mail() 即可,甚至无需安装 sendmail。那么,现在的情况是,我知道你很忙,没时间看前面的技术细节,要的只是开箱即用的工具。行,bypass_disablefunc.php、bypass_disablefunc.c、bypass_disablefunc_x64.so 托管在 https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD,自取。

*本文原创作者:yangyangwithgnu,本文属FreeBuf原创奖励计划,未经许可禁止转载

# disable_functions # LD_PRELOAD
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者