1. 程式人生 > >深入探究寬位元組注入漏洞與修補原理

深入探究寬位元組注入漏洞與修補原理



0、前言

最近要為了自動化審計蒐集所有PHP漏洞,在整理注入的時候,發現寬位元組注入中使用iconv造成的漏洞原理沒有真正搞懂,網上的文章也說得不是很清楚,於是看了榮哥(lxsec)以前發的一篇,加上我們兩個人的討論,最終有了這一篇深入的研究成果。

1、概述

主要是由於使用了寬位元組編碼造成的。

什麼是字符集?

計算機顯示的字元圖形與儲存該字元時的二進位制編碼的對映關係。

ASCII中,A(圖形)對應編碼0100000165)。

對於MYSQL資料庫來說,涉及字符集的地方大致分為儲存和傳輸時,即:

(1)儲存在伺服器端的資料是何種編碼

(2)客戶端和伺服器互動的時候資料傳輸使用的編碼。

2、MYSQL伺服器端儲存字符集

MYSQL伺服器端進行資料儲存時,允許在以下的級別設定字符集:

(1)伺服器端字符集(character_set_server

(2)庫字符集

(3)表字符集

(4)欄位字符集

優先順序為:欄位----->------->-------->伺服器

對應的語法是:

Create table test(
name varchar(20) charset gbk,
number varchar(10),
age int
)engine=innodb charset=utf-8 ;

3、客戶端與伺服器互動資料傳輸的字符集

儲存時的字符集已經確定了,不會影響互動階段的字符集。

MYSQL

中,還有一箇中間層的結構,負責客戶端和伺服器之間的連線,所以稱為連線層。

互動的過程如下:

(1)客戶端以某種字符集生成的SQL語句傳送至伺服器端,這個“某種字符集”其實是任意規定的,PHP作為客戶端連線MYSQL時,這個字符集就是PHP檔案預設的編碼。

(2)伺服器會將這個SQL語句轉為連線層的字符集。問題在於MYSQL是怎麼知道我們傳過來的這個SQL語句是什麼編碼呢?這時主要依靠兩個MYSQL的內部變數來表示,一個是character_set_client(客戶端的字符集)和character_set_connection(連線層的字符集)。可以使用show variables like 

character_set_% ;進行檢視。

可以看到,這裡的客戶端字符集為GBK,連線層字符集也是為GBK

兩者相同,就不會有問題,如果不一致,就會出現亂碼問題了。

使用MYSQL中的set命令可以對這些內部變數做設定,如修改客戶端編碼為UTF-8;

set character_set_client = UTF-8

(1)伺服器將轉換好的SQL語句,轉為伺服器內部編碼與儲存在伺服器上的資料進行互動

(2)伺服器處理完之後,將結果返回給客戶端,還是轉為伺服器認為客戶端可以認識的編碼,如上圖的GBK,使用character_set_results來確定返回客戶端的編碼。

平時在PHP中寫的set names UTF-8相當於下面三條同時執行:

(1)set character_set_client = UTF-8

(2)set character_set_connection = UTF-8

(3)set character_set_results = UTF-8

4、亂碼問題原理

設定三個字符集相同,這也就不會出現亂碼的真正原理。網頁上有時會出現亂碼是因為PHP動態檔案將資料列印到瀏覽器的時候,瀏覽器也會按照一定的字符集進行判斷,如果PHP的響應資料編碼和瀏覽器編碼一致,就不會出現亂碼,否則就出現亂碼。可以通過在PHP中使用header()來指定這個響應資料的編碼。

5、寬位元組注入原理

有三種形式:

(1)情景一:在PHP中使用mysql_query(set names GBK);指定三個字符集(客戶端、連線層、結果集)都是GBK編碼。

情景程式碼: 

.....
mysql_query(“set names GBK”);
$bar = addslashes($_GET[‘bar’]) ;
$sql = “select password from user where bar=’{$bar}’”;
$res = mysql_query($sql) ;
......

提交:http://127.0.0.1/foo.php?bar=admin%df%27

這時,發生如下轉換:

%df%27=====(addslashes)======>%df%5c%27======(GBK)======>

帶入sql為:

Select password from user where bar=

成功將單引號閉合。為了避免漏洞,網站一般會設定UTF-8編碼,然後進行轉義過濾。但是由於一些不經意的字符集轉換,又會導致漏洞。

(2)情景二:

使用set names UTF-8指定了UTF-8字符集,並且也使用轉義函式進行轉義。有時候,為了避免亂碼,會將一些使用者提交的GBK字元使用iconv函式(或者mb_convert_encoding)先轉為UTF-8,然後再拼接入SQL語句。

情景程式碼:

....
mysql_query(“set names UTF-8”) ;
$bar =iconv(“GBK”,”UTF-8”, addslashes($_GET[‘’bar])) ;
$sql = “select password from user where bar=’{$bar}’” ;
$res = mysql_query($sql) ;
......

我們可以看到,為了使得SQL語句中的字符集保持一致,一般都會使用iconv等字符集轉換函式進行字符集轉換,問題就是出在了GBKUTF-8轉換的過程中。

提交:http://127.0.0.1/foo.php?bar=%e5%5c%27

變換過程:(e55c轉為UTF-8e98ca6

e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27

可以看到,多出了一個5c,將轉義符(反斜槓)本身轉義,使得後面的%27發揮了作用。

測試如下:



(3)情景三:使用iconv進行字符集轉換,將UTF-8轉為GBK,同時,set names字符集為GBK。提交%e9%8c%a6即可。

這個情景的大前提是先編碼後轉義:

e98ca6====(iconv)=====>e55c=====(addslashes)====>e55c5c

同樣可以多出一個反斜槓進行利用,在此不再詳述,因為漏洞條件比較苛刻。

6、安全方案

對於寬位元組編碼,有一種最好的修補就是:

(1)使用mysql_set_charset(GBK)指定字符集

(2)使用mysql_real_escape_string進行轉義

原理是,mysql_real_escape_stringaddslashes的不同之處在於其會考慮當前設定的字符集,不會出現前面e55c拼接為一個寬位元組的問題,但是這個“當前字符集”如何確定呢?

就是使用mysql_set_charset進行指定。

上述的兩個條件是“與”運算的關係,少一條都不行。

測試;


輸出:


效果很明顯。