sql寬字節注入詳解

儘管現在呼籲所有的程序都使用unicode編碼,所有的網站都使用utf-8編碼,來一個統一的國際規範。但仍然有很多,包括國內及國外(特別是非英語國家)的一些cms,仍然使用着自己國家的一套編碼,比如gbk,作爲自己默認的編碼類型。也有一些cms爲了考慮老用戶,所以出了gbk和utf-8兩個版本。

我們就以gbk字符編碼爲示範,拉開帷幕。gbk是一種多字符編碼,具體定義自行百度。但有一個地方尤其要注意:

通常來說,一個gbk編碼漢字,佔用2個字節。一個utf-8編碼的漢字,佔用3個字節。在php中,我們可以通過輸出

echo strlen("和");

來測試。當將頁面編碼保存爲gbk時輸出2,utf-8時輸出3。

除了gbk以外,所有ANSI編碼都是2個字節。ansi只是一個標準,在不用的電腦上它代表的編碼可能不相同,比如簡體中文系統中ANSI就代表是GBK。

以上是一點關於多字節編碼的小知識,只有我們足夠了解它的組成及特性以後,才能更好地去分析它身上存在的問題。

說了這麼多廢話,現在來研究一下在SQL注入中,字符編碼帶來的各種問題。

0×01 MYSQL中的寬字符注入

這是一個老話題了,也被人玩過無數遍。但作爲我們這篇文章的序幕,也是基礎,是必須要提的。

我們先搭建一個實驗環境。暫且稱之爲phithon內容管理系統v1.0,首先先新建一個數據庫,把如下壓縮包中的sql文件導入:

測試代碼及數據庫:http://pan.baidu.com/s/1eQmUArw 提取密碼:75tu

之後的phithon內容管理系統會逐步完善,但會一直使用這個數據表。

源碼很簡單(注意先關閉自己php環境的magic_quotes_gpc):

<?php
//連接數據庫部分,注意使用了gbk編碼,把數據庫信息填寫進去
$conn = mysql_connect('localhost', 'root', 'toor!@#$') or die('bad!');
mysql_query("SET NAMES 'gbk'");
mysql_select_db('test', $conn) OR emMsg("連接數據庫失敗,未找到您填寫的數據庫");
//執行sql語句
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
$result = mysql_query($sql, $conn) or die(mysql_error()); //sql出錯會報錯,方便觀察
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="gbk" />
<title>新聞</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h2>{$row['title']}</h2><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>

SQL語句是SELECT * FROM news WHERE tid='{$id}',就是根據文章的id把文章從news表中取出來。

在這個sql語句前面,我們使用了一個addslashes函數,將$id的值轉義。這是通常cms中對sql注入進行的操作,只要我們的輸入參數在單引號中,就逃逸不出單引號的限制,無法注入,如下圖:

001.jpg

那麼怎麼逃過addslashes的限制?衆所周知addslashes函數產生的效果就是,讓'變成\',讓引號變得不再是“單引號”,只是一撇而已。一般繞過方式就是,想辦法處理\'前面的\

1.想辦法給\前面再加一個\(或單數個即可),變成\\',這樣\被轉義了,'逃出了限制
2.想辦法把\弄沒有。

我們這裏的寬字節注入是利用mysql的一個特性,mysql在使用GBK編碼的時候,會認爲兩個字符是一個漢字(前一個ascii碼要大於128,纔到漢字的範圍)。如果我們輸入%df'看會怎樣:

003.jpg

我們可以看到,已經報錯了。我們看到報錯,說明sql語句出錯,看到出錯說明可以注入了。

爲什麼從剛纔到現在,只是在'也就是%27前面加了一個%df就報錯了?而且從圖中可以看到,報錯的原因就是多了一個單引號,而單引號前面的反斜槓不見了。

這就是mysql的特性,因爲gbk是多字節編碼,他認爲兩個字節代表一個漢字,所以%df和後面的\也就是%5c變成了一個漢字“運”,而'逃逸了出來。

因爲兩個字節代表一個漢字,所以我們可以試試%df%df%27:

004.jpg

不報錯了。因爲%df%df是一個漢字,%5c%27不是漢字,仍然是\'

那麼mysql怎麼判斷一個字符是不是漢字,根據gbk編碼,第一個字節ascii碼大於128,基本上就可以了。比如我們不用%df,用%a1也可以:

005.jpg

%a1%5c他可能不是漢字,但一定會被mysql認爲是一個寬字符,就能夠讓後面的%27逃逸了出來。

於是我可以構造一個exp出來,查詢管理員賬號密碼:

006.jpg

0×02 GB2312與GBK的不同

曾經有一個問題一直困擾我很久。

gb2312和gbk應該都是寬字節家族的一員。但我們來做個小實驗。把phithon內容管理系統中set names修改成gb2312:

007.jpg

結果就是不能注入了:

008.jpg

有些同學不信的話,也可以把數據庫編碼也改成gb2312,也是不成功的。

爲什麼,這歸結於gb2312編碼的取值範圍。它的高位範圍是0xA1~0xF7,低位範圍是0xA1~0xFE,而\是0x5c,是不在低位範圍中的。所以,0x5c根本不是gb2312中的編碼,所以自然也是不會被吃掉的。

所以,把這個思路擴展到世界上所有多字節編碼,我們可以這樣認爲:只要低位的範圍中含有0x5c的編碼,就可以進行寬字符注入。

0×03 mysql_real_escape_string解決問題?

部分cms對寬字節注入有所瞭解,於是尋求解決方案。在php文檔中,大家會發現一個函數,mysql_real_escape_string,文檔裏說了,考慮到連接的當前字符集。

009.jpg

於是,有的cms就把addslashes替換成mysql_real_escape_string,來抵禦寬字符注入。我們繼續做試驗,phithon內容管理系統v1.2:,就用mysql_real_escape_string來過濾輸入:

0010.jpg

我們來試試能不能注入:

0011.jpg

一樣沒壓力注入。爲什麼,明明我用了mysql_real_escape_string,但卻仍然不能抵禦寬字符注入。

原因就是,你沒有指定php連接mysql的字符集。我們需要在執行sql語句之前調用一下mysql_set_charset函數,設置當前連接的字符集爲gbk。

0012.jpg

就可以避免這個問題了:

0013.jpg

0×04 寬字符注入的修復

在3中我們說到了一種修復方法,就是先調用mysql_set_charset函數設置連接所使用的字符集爲gbk,再調用mysql_real_escape_string來過濾用戶輸入。

這個方式是可行的,但有部分老的cms,在多處使用addslashes來過濾字符串,我們不可能去一個一個把addslashes都修改成mysql_real_escape_string。我們第二個解決方案就是,將character_set_client設置爲binary(二進制)。

只需在所有sql語句前指定一下連接的形式是二進制:

SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary

這幾個變量是什麼意思?

當我們的mysql接受到客戶端的數據後,會認爲他的編碼是character_set_client,然後會將之將換成character_set_connection的編碼,然後進入具體表和字段後,再轉換成字段對應的編碼。

然後,當查詢結果產生後,會從表和字段的編碼,轉換成character_set_results編碼,返回給客戶端。

所以,我們將character_set_client設置成binary,就不存在寬字節或多字節的問題了,所有數據以二進制的形式傳遞,就能有效避免寬字符注入。

比如,我們的phithon內容管理系統v2.0版本更新如下:

0014.jpg

已經不能夠注入了:

0015.jpg

在我審計過的代碼中,大部分cms是以這樣的方式來避免寬字符注入的。這個方法可以說是有效的,但如果開發者畫蛇添足地增加一些東西,會讓之前的努力前功盡棄。

0×05 iconv導致的致命後果

很多cms,不止一個,我就不提名字了,他們的gbk版本都存在因爲字符編碼造成的注入。但有的同學說,自己測試了這些cms的寬字符注入,沒有效果呢,難道是自己姿勢不對?

當然不是。實際上,這一章說的已經不再是寬字符注入了,因爲問題並不是出在mysql上,而是出在php中了。

很多cms(真的很多哦,不信大家自己網上找找)會將接收到數據,調用這樣一個函數,轉換其編碼:

iconv('utf-8', 'gbk', $_GET['word']);

目的一般是爲了避免亂碼,特別是在搜索框的位置。

比如我們的phithon內容管理系統v3.0

0016.jpg

我們可以看到,它在sql語句執行前,將character_set_client設置成了binary,所以可以避免寬字符注入的問題。但之後其調用了iconv將已經過濾過的參數$id給轉換了一下。

那我們來試試此時能不能注入:

0017.jpg

居然報錯了。說明可以注入。而我只是輸入了一個錦'。這是什麼原因?

我們來分析一下。“錦“這個字,它的utf-8編碼是0xe98ca6,它的gbk編碼是0xe55c

0018.jpg

0019.jpg

有的同學可能就領悟了。\的ascii碼正是5c。那麼,當我們的錦被iconv從utf-8轉換成gbk後,變成了%e5%5c,而後面的'被addslashes變成了%5c%27,這樣組合起來就是%e5%5c%5c%27,兩個%5c就是\,正好把反斜槓轉義了,導致’逃逸出單引號,產生注入。

這正利用了我之前說的,繞過addslashes的兩種方式的第一種:將\轉義掉。

那麼,如果我是用iconv將gbk轉換成utf-8呢?

0020.jpg

我們來試試:

0021.jpg

果然又成功了。這次直接用寬字符注入的姿勢來的,但實際上問題出在php而不是mysql。我們知道一個gbk漢字2字節,utf-8漢字3字節,如果我們把gbk轉換成utf-8,則php會每兩個字節一轉換。所以,如果\'前面的字符是奇數的話,勢必會吞掉\'逃出限制。

那麼爲什麼之前utf-8轉換成gbk的時候,沒有使用這個姿勢?

這跟utf-8的規則有關,UTF-8的編碼規則很簡單,只有二條:

1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
2)對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。

從2我們可以看到,對於多字節的符號,其第2、3、4字節的前兩位都是10,也就是說,\(0x0000005c)不會出現在utf-8編碼中,所以utf-8轉換成gbk時,如果有\則php會報錯:

0022.jpg

但因爲gbk編碼中包含了\,所以仍然可以利用,只是利用方式不同罷了。

總而言之,在我們處理了mysql的寬字符注入以後,也別認爲就可以高枕無憂了。調用iconv時千萬要小心,避免出現不必要的麻煩。 

0×06 總結

在逐漸國際化的今天,推行utf-8編碼是大趨勢。如果就安全性來說的話,我也覺得使用utf-8編碼能夠避免很多多字節造成的問題。

不光是gbk,我只是習慣性地把gbk作爲一個典型的例子在文中與大家說明。世界上的多字節編碼有很多,特別是韓國、日本及一些非英語國家的cms,都可能存在由字符編碼造成的安全問題,大家應該有擴展性的思維。

總結一下全文中提到的由字符編碼引發的安全問題及其解決方案:

  1. gbk編碼造成的寬字符注入問題,解決方法是設置character_set_client=binary。
  2. 矯正人們對於mysql_real_escape_string的誤解,單獨調用set names gbk和mysql_real_escape_string是無法避免寬字符注入問題的。還得調用mysql_set_charset來設置一下字符集。
  3. 謹慎使用iconv來轉換字符串編碼,很容易出現問題。只要我們把前端html/js/css所有編碼設置成gbk,mysql/php編碼設置成gbk,就不會出現亂碼問題。不用畫蛇添足地去調用iconv轉換編碼,造成不必要的麻煩。

這篇文章是我對於自己白盒審計經驗的一點小總結,但自己確實在很多方面存在欠缺,文中所提到的姿勢難免存在紕漏和錯誤,希望有相同愛好的同學能與我指出,共同進步。

這篇文章不像上篇xss的,能夠舉出很多0day實例來論證寬字符造成的危害。原因有二:

  1. 寬字符問題確實不如富文本xss那麼普遍,gbk編碼的cms所佔的比例也比較小,怪我才疏學淺,並不能每一章都找到相應的實例。
  2. 注入的危害比xss大得多,如果作爲0day發出來,影響很壞。但我確實在寫文章以及以前的審計過程中找到不少cms存在的編碼問題。

所以我用實驗的形式,自己寫了的php小文件,給大家作爲例子,希望不會因爲例證的不足,影響大家學習的效果。

例子php文件和sql文件打包下載:

鏈接:http://pan.baidu.com/s/1eQmUArw 提取密碼:75tu

本文PDF版本:鏈接: http://pan.baidu.com/s/1eprLs 密碼: yoyw

原文鏈接:
https://www.leavesongs.com/PENETRATION/mutibyte-sql-inject.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章