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字符的。


>>> hex(ord('\\'))

'0x5c'

>>> s='\xc0\x5c'

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

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

'\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(),源码中的处理是一个字节一个字节来处理的。这种漏洞应该有一定普遍性,在当时来说。下面我们看下修复的源代码:


char *php_escape_shell_cmd(char *str) {

register int x, y, l;

char *cmd;

char *p = NULL;

TSRMLS_FETCH();

l = strlen(str);

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

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

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

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

/* skip non-valid multibyte characters */

if (mb_len < 0) {

continue;

} else if (mb_len > 1) {

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

y += mb_len;

x += mb_len - 1;

continue;

}

switch (str[x]) {

case '"':

case '\'':

#ifndef PHP_WIN32

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

/* noop */

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

p = NULL;

} else {

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

}

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

break;

#endif

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

case '&':

case ';':

case '`':

case '|':

case '*':

case '?':

case '~':

case '<':

case '>':

case '^':

case '(':

case ')':

case '[':

case ']':

case '{':

case '}':

case '$':

case '\\':

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

case '\xFF':

#ifdef PHP_WIN32

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

case '%':

cmd[y++] = ' ';

break;

#endif

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

/* fall-through */

default:

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

}

}

cmd[y] = '\0';

return cmd;

}

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

2 宽字节sql注入

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


$user = $_POST[ 'username' ];

$user = addslashes($user);

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

$pass = $_POST[ 'password' ];

$pass = md5( $pass );

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

print_r($qry);

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

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


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

>>> s.decode('utf8')

u'\u9326'

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

'\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/