0、前言php
最近要爲了自動化審計蒐集全部PHP漏洞,在整理注入的時候,發現寬字節注入中使用iconv形成的漏洞原理沒有真正搞懂,網上的文章也說得不是很清楚,因而看了榮哥(lxsec)之前發的一篇http://www.91ri.org/8611.html,加上咱們兩我的的討論,最終有了這一篇深刻的研究成果。html
一、概述mysql
主要是因爲使用了寬字節編碼形成的。,典型情景是網站使用GBK編碼,數據庫使用UTF-8編碼sql
什麼是字符集?數據庫
計算機顯示的字符圖形與保存該字符時的二進制編碼的映射關係。瀏覽器
如ASCII中,A(圖形)對應編碼01000001(65)。安全
對於MYSQL數據庫來講,涉及字符集的地方大體分爲存儲和傳輸時,即:服務器
(1)存儲在服務器端的數據是何種編碼函數
(2)客戶端和服務器交互的時候數據傳輸使用的編碼。post
二、MYSQL服務器端存儲字符集
在MYSQL服務器端進行數據存儲時,容許在如下的級別設置字符集:
(1)服務器端字符集(character_set_server)
(2)庫字符集
(3)表字符集
(4)字段字符集
優先級爲:字段----->表------->庫-------->服務器
對應的語法是:
三、客戶端與服務器交互數據傳輸的字符集
存儲時的字符集已經肯定了,不會影響交互階段的字符集。
在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
四、亂碼問題原理
設置三個字符集相同,這也就不會出現亂碼的真正原理。網頁上有時會出現亂碼是由於PHP動態文件將數據打印到瀏覽器的時候,瀏覽器也會按照必定的字符集進行判斷,若是PHP的響應數據編碼和瀏覽器編碼一致,就不會出現亂碼,不然就出現亂碼。能夠經過在PHP中使用header()來指定這個響應數據的編碼。
五、寬字節注入原理
有三種形式:
(1)情景一:在PHP中使用mysql_query(「set names GBK」);指定三個字符集(客戶端、鏈接層、結果集)都是GBK編碼。
情景代碼:
提交: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語句。
情景代碼:
咱們能夠看到,爲了使得SQL語句中的字符集保持一致,通常都會使用iconv等字符集轉換函數進行字符集轉換,問題就是出在了GBK向UTF-8轉換的過程當中。
提交:http://127.0.0.1/foo.php?bar=%e5%5c%27
變換過程:(e55c轉爲UTF-8爲e98ca6)
e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27
能夠看到,多出了一個5c,將轉義符(反斜槓)自己轉義,使得後面的%27發揮了做用。
測試以下:
(3)情景三:使用iconv進行字符集轉換,將UTF-8轉爲GBK,同時,set names字符集爲GBK。提交%e9%8c%a6便可。
這個情景的大前提是先編碼後轉義:
e98ca6====(iconv)=====>e55c=====(addslashes)====>e55c5c
一樣能夠多出一個反斜槓進行利用,在此再也不詳述,由於漏洞條件比較苛刻。
六、安全方案
對於寬字節編碼,有一種最好的修補就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string進行轉義
原理是,mysql_real_escape_string與addslashes的不一樣之處在於其會考慮當前設置的字符集,不會出現前面e5和5c拼接爲一個寬字節的問題,可是這個「當前字符集」如何肯定呢?
就是使用mysql_set_charset進行指定。
上述的兩個條件是「與」運算的關係,少一條都不行。
測試;
輸出: