从escapeshellcmd bypass说起到宽字节注入

1 php 多字节绕过escapeshellcmd

escapeshellcmd()对shell元字符过滤加反斜杠; 
反斜线(\)会在以下字符之前插入: #&;`|*?~<>^()[]{}$, \x0A 和 \xFF,但在php5.2.5及之前存在通过输入多字节绕过escapeshellcmd的问题。5.2.6 已经修复了该问题。

执行 escapeshellcmd("echo ".chr(0xc0).";id"); 
加上反斜杠之后,也就是echo \xc0\x5c;id,在中文环境中\xc0\x5c是会被认为是gbk字符的。


  1. >>> hex(ord('\\'))

  2. '0x5c'

  3. >>> s='\xc0\x5c'

  4. >>> print s.decode('gbk').encode('utf8')

  5. �\

  6. >>> s.decode('gbk').encode('utf8')

  7. '\xe7\xb9\xba'

\被吃掉之后于是就变成了echo �\;id 了。 
gbk是宽字节,两个字节,gbk字符范围:8140-FEFE,首字节在81-FE直接,尾字节在40-FE之间,显然5C在尾字节中。考虑0xbf;id,escape之后就变成了0xbf5c;id,0xbf5c是一个合法的GBK编码,那就变成了[0xbf5c];id了。而utf8表示中文一般三个字节。 
同样受影响的还有escapeshellarg(),源码中的处理是一个字节一个字节来处理的。这种漏洞应该有一定普遍性,在当时来说。下面我们看下修复的源代码:


  1. char *php_escape_shell_cmd(char *str) {

  2. register int x, y, l;

  3. char *cmd;

  4. char *p = NULL;

  5. TSRMLS_FETCH();

  6. l = strlen(str);

  7. cmd = safe_emalloc(2, l, 1); //申请了2倍字符

  8. for (x = 0, y = 0; x < l; x++) {

  9. int mb_len = php_mblen(str + x, (l - x));

  10. //这一段是5.2.6新加的,就是在处理多字节符号的时候,当多字节字符小于0的时候不处理,大于1的时候跳过,等于1的时候执行过滤动作

  11. /* skip non-valid multibyte characters */

  12. if (mb_len < 0) {

  13. continue;

  14. } else if (mb_len > 1) {

  15. memcpy(cmd + y, str + x, mb_len);

  16. y += mb_len;

  17. x += mb_len - 1;

  18. continue;

  19. }

  20. switch (str[x]) {

  21. case '"':

  22. case '\'':

  23. #ifndef PHP_WIN32

  24. if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {

  25. /* noop */

  26. } else if (p && *p == str[x]) {

  27. p = NULL;

  28. } else {

  29. cmd[y++] = '\\';

  30. }

  31. cmd[y++] = str[x];

  32. break;

  33. #endif

  34. case '#': /* This is character-set independent */

  35. case '&':

  36. case ';':

  37. case '`':

  38. case '|':

  39. case '*':

  40. case '?':

  41. case '~':

  42. case '<':

  43. case '>':

  44. case '^':

  45. case '(':

  46. case ')':

  47. case '[':

  48. case ']':

  49. case '{':

  50. case '}':

  51. case '$':

  52. case '\\':

  53. case '\x0A': /* excluding these two */

  54. case '\xFF':

  55. #ifdef PHP_WIN32

  56. /* since Windows does not allow us to escape these chars, just remove them */

  57. case '%':

  58. cmd[y++] = ' ';

  59. break;

  60. #endif

  61. cmd[y++] = '\\';

  62. /* fall-through */

  63. default:

  64. cmd[y++] = str[x];

  65. }

  66. }

  67. cmd[y] = '\0';

  68. return cmd;

  69. }

这个bypass已经成为过去时了,但是还是有很大的借鉴意义,就是宽字节注入,这种情况不仅仅发生命令注入时,更多的时候在sql注入,下面来分析一下宽字节注入如下三种情况,都是由于宽字节的问题导致的。

2 宽字节sql注入

1,一种情况 iconv转换,addslashes之后从gbk转到utf8


  1. $user = $_POST[ 'username' ];

  2. $user = addslashes($user);

  3. $user = iconv("gbk", 'utf8', $user);

  4. $pass = $_POST[ 'password' ];

  5. $pass = md5( $pass );

  6. $qry = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";

  7. print_r($qry);

  8. $result = @mysql_query($qry) or die('<pre>' . mysql_error() . '</pre>' );

  9. var_dump($result);

处理过程如下: 
%bf%27----(addslashes)->%bf%5c%27-----(utf8)---->�\' 这样单引号就放出来了,大体流程是%bf%27经过addslashes之后变成了%bf%5c%27,再经过iconv从gbk转换为utf8的时候,变成了%e7%b8%97%27,也就是�\'。利用的前提是设置了set names utf8。

2,在php中使用mysql_query('set names gbk'),指定了客户端,连接层,结果为gbk编码。构造数据%bf%27,过程和第一种情况类似 
%bf%27---(addslashes)-->%bf%5c%27---(set names gbk)--->�\'

3,iconv转换从utf8到gbk,set names字符集为gbk,构造数据如下%e9%8c%a6带入反斜杠,注释掉单引号 
大体数据流程:%e9%8c%a6-----(utf8)----%e5%5c----(addslashes)--->%e5%5c%5c


  1. >>> s = '\xe9\x8c\xa6'

  2. >>> s.decode('utf8')

  3. u'\u9326'

  4. >>> s.decode('utf8').encode('gbk')

  5. '\xe5\\'

总之一条,都是打的%5c的注意,要么转义后转utf8吃掉%5c,要么转utf8后再转义放出%5c

参考: 
http://seclists.org/bugtraq/2008/May/61 
http://www.sektioneins.de/en/advisories/advisory-032008-php-multibyte-shell-command-escaping-bypass-vulnerability.html 
http://php.net/ChangeLog-5.php 
http://php.net/releases/


你可能感兴趣的:(PHP,bypass,多字节,escapeshellcmd,绕过)