«
php escapeshellcmd多字节编码漏洞解析

时间:2008-5-31    作者:Deri    分类: 分享


   <p>  漏洞公告在http://www.sektioneins.de/advisories/SE-2008-03.txt <p>  PHP 5 <= 5.2.5</p><p>  PHP 4 <= 4.4.8</p><p>  一些允许如GBK,EUC-KR, SJIS等宽字节字符集的系统都可能受此影响,影响还是非常大的,国内的虚拟主机应该是通杀的,在测试完这个漏洞之后,发现还是十分有意思的,以前也有过对这种类型安全漏洞的研究,于是就把相关的漏洞解释和一些自己的想法都写出来,也希望国内的一些有漏洞的平台能迅速做出响应,修补漏洞。</p><p>  这个漏洞出在php的用来转义命令行字符串的函数上,这些函数底层是用的php_escape_shell_cmd这个函数的,我们先来看看他的处理过程:</p><p><code>   /*{{{php_escape_shell_cmd<br />  Escapeallcharsthatcouldpossiblybeusedto<br />  breakoutofashellcommand<br />  Thisfunctionemalloc'sastringandreturnsthepointer.<br />  Remembertoefreeitwhendonewithit.<br />  *NOT*safeforbinarystrings<br />  */<br />  char*php_escape_shell_cmd(char*str){<br />  registerintx,y,l;<br />  char*cmd;<br />  char*p=NULL;<br />  l=strlen(str);<br />  cmd=safe_emalloc(2,l,1);<br />  for(x=0,y=0;x<l;x++){<br />  switch(str[x]){<br />  case'"':<br />  case''':<br />  #ifndefPHP_WIN32<br />  if(!p&&(p=memchr(str+x+1,str[x],l-x-1))){<br />  /*noop*/<br />  }elseif(p&&*p==str[x]){<br />  p=NULL;<br />  }else{<br />  cmd[y++]='';<br />  }<br />  cmd[y++]=str[x];<br />  break;<br />  #endif<br />  case'#':/*Thisischaracter-setindependent*/<br />  case'&':<br />  case';':<br />  case'`':<br />  case'|':<br />  case'*':<br />  case'?':<br />  case'~':<br />  case'<':<br />  case'>':<br />  case'^':<br />  case'(':<br />  case')':<br />  case'[':<br />  case']':<br />  case'{':<br />  case'}':<br />  case'$':<br />  case'':<br />  case'x0A':/*excludingthesetwo*/<br />  case'xFF':<br />  #ifdefPHP_WIN32<br />  /*sinceWindowsdoesnotallowustoescapethesechars,justremovethem*/<br />  case'%':<br />  cmd[y++]='';<br />  break;<br />  #endif<br />  cmd[y++]='';<br />  /*fall-through*/<br />  default:<br />  cmd[y++]=str[x];<br />  }<br />  }<br />  cmd[y]='';<br />  returncmd;<br />  }<br />  /*}}}*/</code></p>
<p> </p>

   <p>  可以看到,php通过将",',#,&,;.....等等在shell命令行里有特殊意义的字符都通过在前面加上变成".',#,&,;......来进行转义,使得用户的输入被过滤,来避免产生command injection漏洞。在php看来,只要过滤了这些字符,送入到system等函数中时,参数就会是安全的,php手册中给出的利用例子如下:</p><p><code><?php<br />$e=escapeshellcmd($userinput);<br />//herewedon'tcareif$ehasspaces<br />system("echo$e");<br />$f=escapeshellcmd($filename);<br />//andherewedo,soweusequotes<br />system("touch"/tmp/$f";ls-l"/tmp/$f"");<br />?></code></p><p>  很明显,如果没有经过escapeshellcmd的处理,用户输入hello;id的话,最后system执行的会是: <p>  echo hello;id</p><p>  ;在shell里是分割命令的作用,这样不仅仅会echo hello,还会执行id这个命令,导致命令注入漏洞。用escapeshellcmd处理之后命令变成:</p><p>  echo hello;id</p><p>  这样执行的命令就只会是echo,其他的都变成echo的参数,很安全。</p><p>  事实上是这样么?php在处理完参数送入system之后它就什么都不管了,后面的工作实际上都是由linux来完成的,那么linux在处理这些参数的时候是怎么样的呢?linux在执行命令的时候会有一些的表示工作环境的环境变量,譬如PWD代表当前的工作环境,UID代表了你的身份,BASH代表命令解释器等等......而在linux系统执行命令的时候,还有一个非常重要的参数,LANG,这个参数决定了linux shell如何处理你的输入,这样就可以当你输入一些中文字符的时候,linux能认识他,不至于出现人与系统之间出现理解上的错误。默认情况下,linux的LANG是en_US.UTF-8,UTF-8是一个很安全的字符集,其系列中包含有对自身的校验,所以不会出现错误,会工作良好。一些系统支持多字节字符集如GBK的时候,这也正是国内的多数情况,你可以设置LANG=zh_CN.GBK,这样你的输入都会被当作GBK编码处理,而GBK是双字节的,合法的GBK编码会被认为是一个字符。</p>
 <p> </p>

   <p>  大家可以看到,在php的处理过程中,它是单字节处理的,它只把输入当作一个字节流,而在linux设置了GBK字符集的时候,它的处理是双字节的,大家的理解很明显地不一致。我们查下GBK的字符集范围为8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE 之间,而一个非常重要的字符的编码为5c,在GBK的尾字节范围之内,这样我们考虑一个特殊的输入:</p><p>  0xbf;id 或 0xbf'id</p><p>  经过php的escapeshellcmd单字节转码之后将会是</p><p>  0xbf5c;id</p><p>  0xbf5c'id</p><p>  注意0xbf5c是一个合法的GBK编码,那么在linux执行的时候,会认为输入是</p><p>  [0xbfbc];id</p><p>  很好,后面的id将会被执行。可以做个简单的实验,如下:</p><p><code>   [loveshell@Loveshelltmp]$echo?lt;br />  ><br />  ?<br />  [loveshell@Loveshelltmp]$set|grep-ilang<br />  LANG=zh_CN.GB2312<br />  LANGVAR=en_US.UTF-8<br />  [loveshell@Loveshelltmp]$exportLANG=zh_CN.GBK<br />  [loveshell@Loveshelltmp]$echo?lt;br />  ?lt;br />  [loveshell@Loveshelltmp]$set|grep-ilang<br />  LANG=zh_CN.GBK<br />  LANGVAR=en_US.UTF-8<br />  [loveshell@Loveshelltmp]$</code></p><p>  其中康谋嗦胛?xbf5c,可以看到在不设置LANG为GBK的时候渴且桓龇欠ǖ