編碼問題原理及漏洞解析

0x00 簡介

前一段時間一直想解決一下字元編碼問題,從而研究了Python (Python2.7版本)、PHP、MYSQL等在字元編碼方面的原理,從根本上理解編碼問題產生的根源,以及一些有效的解決辦法。本文打算從兩大部分出發分別介紹各個語言在編碼上的處理機制,以及由編碼問題造成的漏洞。

總的來說字元編碼問題可以歸結為以下幾點

1、以錯誤的方式解析字元編碼

2、在編碼轉化時被轉化的編碼沒有相應的值

3、在編碼轉化時沒有相應的值與被轉化的編碼對應

簡單介紹幾種方式

UTF-8

UTF-8是一種變長位元組編碼方式。對於某一個字元的UTF-8編碼,如果只有一個位元組則其最高二進位位為0;如果是多位元組,其第一個位元組從最高位開始,連續的二進位位值為1的個數決定了其編碼的位數,其餘各位元組均以10開頭。UTF-8最多可用到6個位元組。

如表:

1位元組 0xxxxxxx

2位元組 110xxxxx 10xxxxxx

3位元組 1110xxxx 10xxxxxx 10xxxxxx

4位元組 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

5位元組 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

6位元組 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

GBK

專門為解決漢字的編碼而生成的解決方案

GBK的中文編碼是雙位元組來表示的,英文用單位元組表示,但GBK編碼表中也有英文字元的雙位元組表示形式,所以英文字母可以有2種GBK表示方式。為區分中文,將其最高位都定成1。英文單位元組最高位都為0。

0x01 Python&PHP&MYSQL編碼問題

Python

整體分為三大部分,從存儲、解析到顯示。python里string object和unicode object是兩種不同的類型,string里的character是有多種編碼方式的,比如單位元組的ASCII,雙位元組的GB2312等等,再比如UTF-8。很明顯要想解讀string,必需知道string里的character是用哪種編碼方式,然後才能進行。

Python文檔編碼

Python解析器從文本中解析代碼,需要知道文檔的編碼方式一般在文檔開頭

# coding:utf-8# -*- coding:utf-8 -*-

例如:

# encoding:utf-8print "?"

此時文檔應當按照utf-8編碼存儲,因為Python 解析器會以utf-8的格式讀取存儲在磁碟上的二進位,如果存儲格式有誤就會出現解析錯誤。例如下面報錯信息

SyntaxError: Non-ASCII character xe5 in file F:codepython2.py on line 4, but no encoding declared; see python.org/peps/pep-026 for details

Python 解析器默認以ascii進行解析,xe5超出ascii編碼範圍,因此解析錯誤。

另一種改變python 解析器的默認解析編碼為使用UTF-8-BOM進行存儲。

不用使用頭部注釋即可解析中文字元編碼,利用hexdump工具可以看出其中的文件頭有三個標誌字元ef bb bf

至此Python載入文件的編碼操作已經結束,下面就是在Python解析器中的操作,可以說是在內存中的操作。

Python解析器編碼

由前面的介紹可以了解到Python中的字元類型分為string 和 unicode,其中string 又包括了gbk、utf-8等各種編碼。他們之間的轉化通過encode和decode進行

>>> a = 啊>>> axb0xa1>>> a.decode(gbk)uu554a>>> a.decode(gbk).encode(utf-8)xe5x95x8a

因為終端使用的是gbk編碼,所以 啊的編碼為xb0xa1 以gbk編碼格式進行解析,解析成unicode編碼,再使用encode編碼方式編碼成utf-8。

在這裡需要再次強調一下Python string的默認編碼方式 ascii形式,也就是說在不指定string編碼方式的情況下默認以ascii進行解析,因此會造成很多編碼問題。

比如下面兩個例子,正好將encode和decode函數都有講解到

Python 寫入字元串到文件

這裡只針對py2,

write方法的參數類型是str,str是二進位流(不包含編碼信息),當給出一個unicode對象時,會執行str函數轉換成str類型再送給write方法。unicode轉str包含一次編碼,如不指定則默認使用ascii編

看下面的錯誤

# coding:utf-8a = u啊f = open(./111,a)
f.write(a)
Traceback (most recent call last):
File "F:codepython2.py", line 4, in <module>
f.write(a)
UnicodeEncodeError: ascii codec cant encode character uu554a in position 0: ordinal not in range(128)

這裡產生錯誤的原因是,unicode首先要encode轉化為ascii編碼,顯然有很多位元組是對不上號的,這裡的解決方法有兩個,其一是改變其默認編碼使用下面代碼

# coding:utf-8import sys
reload(sys)
sys.setdefaultencoding(utf-8)
a = u啊f = open(./111,a)
f.write(a)

其二將unicode轉換為其他編碼過後再次存入文件

# coding:utf-8a = u啊f = open(./111,a)
f.write(a.encode(gbk))

繞過unicode直接進行編碼轉化

首先看下下面代碼

>>> a = 啊>>> print a.encode(utf-8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: ascii codec cant decode byte 0xb0 in position 0: ordinal not in range(128)

a原本為gbk編碼,如果直接進行encode那麼首先會轉換編碼為unicode格式,因為默認解析格式為ascii所以在ascii轉換成unicode編碼時會產生以上錯誤。那麼這時可以改變默認編碼

# coding:utf-8import sys
reload(sys)
sys.setdefaultencoding(utf-8)
a = 啊print a.encode(utf-8)

成功解決該類編碼問題

終端顯示編碼

在第二小節的圖示中最後一個環節,是終端編碼的問題。

問題一般出在交給操作系統的字元編碼與終端顯示編碼有所差別。

在windows cmd終端採用的gbk編碼

如果使用utf-8編碼就會出現亂碼

在linux 終端下採用的是utf-8編碼形式![enter description here](./attachments/Little CMS.md "Little CMS")如果使用gbk編碼就會出現亂碼

PHP

載入

PHP編碼相對 Python而言簡單了許多,PHP直接使用ascii進行單位元組解析,也就是說不論文件採用什麼編碼,PHP在解析是總是按位元組處理,如下面例子:

該PHP腳本採用gbk編碼方式編碼,"誠" 字的位元組碼為"D55c" 5c為使得PHP解析錯誤。

顯示

PHP以及HTML腳本可以指定顯示在瀏覽器上的編碼方式

header("content-type:text/html;charset=utf-8")<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

MYSQL

編碼過程

mysql 編碼數據比較複雜,大體上可以分為以下幾個

1、character_set_client

2、character_set_connection

3、character_set_results

4、數據欄位存儲

character_set_client

無論客戶端傳遞的是什麼編碼的數據,伺服器都當成該編碼來處理,例如該編碼為UTF8,那麼如果客戶端發送過來的數據不是UTF8,那麼就會出現亂碼;

character_set_connection

connection 可以說是在SQL語句執行時的編碼 ,當執行的是查詢語句時,客戶端發送過來的數據會先轉換成connection指定的編碼。但只要客戶端發送過來的數據與client指定的編碼一致,那麼轉換就不會出現問題; 

數據欄位存儲

當insert數據時需要將數據存儲在資料庫中,這裡就涉及到編碼轉化,當沒用找到對應關係時就會令該位置字元為3F ,這也是資料庫中亂碼中3F出現的原因。

character_set_results

當select從資料庫中取出時以前的編碼要變為character_set_results設置的編碼。

整個流程可參照下圖:

從查詢到取出整個流程經歷了三次編碼轉換,每次轉換都有可能產生編碼問題。client編碼指定了用戶輸入的編碼格式,connection按照此編碼格式將編碼轉化為connection指定的編碼,然後再將編碼轉化為存儲格式。當有查詢操作時再將取出的數據按照results格式轉化。

這裡盜用一張圖

驗證原理

設計以下實驗

實驗一 (驗證數據存儲時的轉換)

寫一個資料庫交互的代碼,在前端使用GBK編碼方式插入數據,character_set_client設置為GBK,character_set_connection設置為GBK,數據存儲設置為utf8,查看最後的存儲位元組為E8AAA0為正確的utf8編碼

http://127.0.0.1/1.php?a=%d5%5c$conn->query("SET character_set_client = gbk"); $conn->query("SET character_set_connection = gbk"); ALTER TABLE `yz` CHANGE `a` `a` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

實驗二(驗證client到connection的轉換)

在前端使用GBK編碼方式插入數據,character_set_client設置為GBK,character_set_connection設置為utf8,數據存儲設置為utf8,查看最後的存儲位元組為E8AAA0為正確的uft8編碼

http://127.0.0.1/1.php?a=%d5%5c$conn->query("SET character_set_client = gbk"); $conn->query("SET character_set_connection = utf8"); ALTER TABLE `yz` CHANGE `a` `a` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

實驗三(驗證數據存儲到取出的顯示的轉換)

將以utf8編碼存儲的內容用gbk格式取出

http://127.0.0.1/1.php?a=%d5%5c$conn->query("SET character_set_client = gbk"); $conn->query("SET character_set_connection = utf8"); $conn->query("SET character_set_results = gbk"); ALTER TABLE `yz` CHANGE `a` `a` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

成功轉化為gbk格式,沒有亂碼出現enter code here

0x02 漏洞產生GBK編碼新問題

讓我們回顧一下GBK寬位元組注入的相關細節在可以使用單引號的前提下,使用addslashes將單引號轉義,又因為database使用的是GBK編碼,所以如果前面有%d5等位元組存在,就會將反斜杠吃掉變為%d5%5c在gbk編碼裡面這是一個合法字元所以只剩下單引號,成功逃逸。

在研究編碼問題的時候發現PHP是按照位元組處理的所以如果對於%d5%5c這樣的gbk編碼如果使用addslashes的話會變成%d5%5c%5c,這樣在SQL server處理的時候如果以gbk格式進行數據查詢就會 多出一個反斜杠 ,利用該反斜杠可以轉移其他特殊字元,具體使用方法如下:

源碼鏈接

$conn->query("SET NAMES gbk"); sql_get("select * from yz where b=$a and c=$b");

如果$a和$b兩個參數分別傳入

a=%d5%5c&b=or 1 %23

經過編碼解析成為

select * from yz where b=誠 and c=or 1 #

成功進行注入

0x03 總結

系統的總結了在以前學習過程中遇到的編碼問題,學習了編碼問題產生的原因以及糾錯方法,同時在研究過程中找到了新的漏洞所在。那麼總結來看編碼問題還是三大點

1、以錯誤的方式解析字元編碼

2、在編碼轉化時被轉化的編碼沒有相應的值

3、在編碼轉化時沒有相應的值與被轉化的編碼對應

上圖中A是UTF8編碼,B是GBK編碼,簡單來講上述問題可以這麼描述

1、A字元串用GBK編碼進行解析,必然會產生編碼錯誤

2、現在A要轉化為B種編碼,但在解析A時默認編碼為ascii,從而出現解析錯誤

3、現在A要轉化為B種編碼,以UTF-8格式解析A,但在轉化成GBK編碼時發現沒有對應的字元編碼,這裡就出現了轉換錯誤

簡單的總結到這裡,如果後面遇到了其他問題會及時補充,如果描述有錯請及時指正。

0x04 參考鏈接

mysql編碼層次介紹

GBK編碼

(註:本文屬於合天原創投稿獎勵,未經允許,禁止轉載!)


推薦閱讀:

TAG:計算機科學 | 編碼 | 科技 |