12月7日全天:寬字節(jié)注入原理解析

網(wǎng)頁出現(xiàn)亂碼的原因:

客戶端與數(shù)據(jù)庫的數(shù)據(jù)傳輸處編碼、數(shù)據(jù)庫存儲處編碼,兩者編碼不同,就會出現(xiàn)亂碼。還有一種情況,PHP沒有向瀏覽器發(fā)送header頭設(shè)置,告訴瀏覽器用用什么編碼來展現(xiàn),導(dǎo)致亂碼。

通常,數(shù)據(jù)庫在創(chuàng)建數(shù)據(jù)表的時候,就已經(jīng)設(shè)置好了數(shù)據(jù)庫存儲端的字符編碼??梢钥吹絥ame字段設(shè)置的是gbk,而我們注入的時候,腦子中構(gòu)造的語句都是select xxx from xxx where name="{$user_name}"這樣類似的語句。(下面的gbk支持所有常用中文的簡體和繁體,utf-8包含所有的字符,至于為何要這么設(shè)置,我個人猜測可能是為了保證數(shù)據(jù)的唯一性吧。有懂?dāng)?shù)據(jù)庫的大佬可以在下方留言,因為感覺這方面轉(zhuǎn)換編碼挺復(fù)雜的)


正常情況下,整個數(shù)據(jù)的走向大概如下:

請求過程:用戶提交數(shù)據(jù)--->經(jīng)過中間件--->到達(dá)web應(yīng)用--->web應(yīng)用再作為客戶端,向數(shù)據(jù)庫服務(wù)器發(fā)送請求--->數(shù)據(jù)庫服務(wù)器接受請求,執(zhí)行web應(yīng)用發(fā)送的sql語句

web應(yīng)用作為客戶端,向數(shù)據(jù)庫服務(wù)器發(fā)送請求的時候,會以自身php文件默認(rèn)的編碼,來對信息進(jìn)行編碼,接著,再向數(shù)據(jù)庫發(fā)送請求,那么這肯定有一個客戶端和服務(wù)器建立連接的過程,這個建立連接的過程也是統(tǒng)一編碼的過程,依靠的mysql的兩個內(nèi)部變量character_set_client和character_set_connection,只有將character_set_client和character_set_connection的變量值(也就是編碼方式)都設(shè)置成相同的值,那么才不會出現(xiàn)亂碼(是在數(shù)據(jù)庫端進(jìn)行設(shè)置)

之后,服務(wù)器將處理結(jié)果,以客戶端能理解的編碼方式,再傳給客戶端(這里的客戶端我理解是web應(yīng)用),按照上面的來看,客戶端能理解的就是gbk了,所以,character_set_results就設(shè)置成gbk,這就是服務(wù)器返回給客戶端的按照gbk編碼的結(jié)果

而通常程序員在php里寫的set names utf8也就是上面這三條語句同時執(zhí)行的效果。同時,我最上面提到,網(wǎng)頁的編碼也可能造成亂碼,那是因為瀏覽器端的編碼和PHP返回結(jié)果(或者說是打印結(jié)果)的編碼不一致導(dǎo)致的。解決方法就是php用header()來告訴瀏覽器端用什么編碼來解碼。這樣,也就同時解決了瀏覽器端可能出現(xiàn)的亂碼問題。

寬字節(jié)注入的問題就是出在這里:

若數(shù)據(jù)庫端設(shè)置了character_set_client為gbk,character_set_connection為gbk,那么php代碼接受到的$bar為?%df%27?的時候


addslashes()將用戶提交上來的url編碼的?%df%27 進(jìn)行解讀,發(fā)現(xiàn)%27是單引號的意思,所以就加上了轉(zhuǎn)義字符\進(jìn)行轉(zhuǎn)義,于是就變成了%df%5c%27,這里的%5c就是\的url編碼后的形式。問題就在這里形成了,PHP代碼中有set names GBK,那就代表character_set_client,character_set_connection,character_set_results的值都是gbk,而php代碼執(zhí)行mysql($sql)的時候,會進(jìn)行編碼轉(zhuǎn)換!

重點:寬字節(jié)注入發(fā)生的位置就是PHP發(fā)送請求到MYSQL時字符集使用character_set_client設(shè)置值進(jìn)行了一次編碼。在這次編碼中,%df%5c被合并成了一個新的字節(jié),而%27則被當(dāng)做單引號,這樣就實現(xiàn)了閉合!


通過查詢gdk編碼表,可以看到%df%5c就是那個字符(我不認(rèn)識,好像是運(yùn)氣的運(yùn)?)這樣的話,由于這里的編碼變成了新的字符,那么到數(shù)據(jù)庫端執(zhí)行的語句就成了select xxx from xxx where name='運(yùn)' and 1=1-- 實現(xiàn)了閉合,也實現(xiàn)了繞過代碼層的過濾。

再總結(jié)一下:

當(dāng)一個Mysql連接請求從客戶端傳來的時候,服務(wù)器認(rèn)為它的編碼是character_set_client,然后會根據(jù)character_set_connection把請求進(jìn)行轉(zhuǎn)碼,從character_set_client轉(zhuǎn)成character_set_connection,然后更新到數(shù)據(jù)庫的時候,再轉(zhuǎn)化成字段所對應(yīng)的編碼如果使用了set names指令,那么可以修改character_set_connection的值,也同時會修改character_set_client和character_set_results的值,當(dāng)從數(shù)據(jù)庫查詢數(shù)據(jù)返回結(jié)果的時候,將字段從默認(rèn)的編碼轉(zhuǎn)成character_set_results

另外,據(jù)我查閱相關(guān)資料,得知網(wǎng)站的正常開發(fā)都是:

將一些用戶提交的GBK字符使用iconv函數(shù)(或者mb_convert_encoding)先轉(zhuǎn)為UTF-8,然后再拼接入SQL語句

像這種情況,我搜集資料的時候,看到有人博客上是這么寫的

實際上,我想說,不加那個\不是也是同樣的效果嗎?還是說GBK轉(zhuǎn)成URF8加個\是為了湊字符?不管了,這里就先記著吧。如果我的想法實施的時候錯誤了,那就再加個\去嘗試。

最后,網(wǎng)站如何才能防止這種寬字符注入攻擊呢?

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

(2)使用mysql_real_escape_string進(jìn)行轉(zhuǎn)義

原理是,mysql_real_escape_string與addslashes的不同之處在于其會考慮當(dāng)前設(shè)置的字符集,不會出現(xiàn)前面e5和5c拼接為一個寬字節(jié)的問題,但是這個“當(dāng)前字符集”如何確定呢?

就是使用mysql_set_charset進(jìn)行指定。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容