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/