Padding Oracle攻擊實例分析
在《2010年揚名的十大WEB黑客技術》和《淺談ASP.NET的Padding Oracle攻擊》中,我們都提到了Padding Oracle Attack的相關內容,也對其進行了一些簡單的了解。接下來,通過這篇譯文,我們再來對其進行一下深入的學習吧。
最近出現了許多有關Padding Oracle Attack的聲音,在今年夏天早些時候的BlakHat Europe會議上,Juliano Rizzo和Thai Duong在他們的演講中演示了這種攻擊方式。雖然Padding Oracle是種相對容易的攻擊方式,但如果您還沒有對它的自動攻擊原理有一定了解,那么利用它進行攻擊還是需要不少時間的。由于缺少好用的工具以識別及利用Padding Oracles,我們開發了一個基于Padding Oracle的內部腳本,PadBuster,現在我們打算將它與社區分享。您可以在這里下載工具,現在我們也會花些時間來討論這個工具的工作方式,以及它所支持的幾種場景。
一些背景知識
在討論PadBuster之前,我們先來簡單討論一下典型的Padding Oracle Attack基礎。故名思義,Padding Oracle Attack背后的關鍵性概念便是加/解密時的填充(Padding)。明文信息可以是任意長度,但是塊狀加密算法需要所有的信息都由一定數量的數據塊組成。為了滿足這樣的需求,便需要對明文進行填充,這樣便可以將它分割為完整的數據塊。
加密時可以使用多種填充規則,但最常見的填充方式之一是在PKCS#5標準中定義的規則。PCKS#5的填充方式為:明文的最后一個數據塊包含N個字節的填充數據(N取決于明文最后一塊的數據長度)。下圖是一些示例,展示了不同長度的單詞(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它們使用PKCS#5填充后的結果(每個數據塊為8字節長)。
請注意,每個字符串都至少有1個字節的填充數據,因此7字節的值(如AVOCADO)則使用0x01進行填充,而8字節的值(如PLANTAIN)則會填充一個額外的數據塊。填充字節的值也說明了填充的字節數,因此待加密數據的最后幾個字節必須是以下幾種情況之一:
如果解密后的最后一個數據塊末尾并非這些合法的字節序列,大部分加/解密程序都會拋出一個填充異常。這個異常對于攻擊者尤為關鍵,它是Padding Oracle Attack的基礎。#p#
一個基本的Padding Oracle Attack場景
作為一個具體例子,請考慮以下場景:
http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
某個應用程序使用Query String參數來傳遞一個用戶加密后的用戶名,公司ID及角色ID。參數使用CBC模式加密,每次都使用不同的初始化向量(IV,Initialization Vector)并添加在密文前段。
當應用程序接受到加密后的值以后,它將返回三種情況:
接受到正確的密文之后(填充正確且包含合法的值),應用程序正常返回(200 - OK)。
接受到非法的密文之后(解密后發現填充不正確),應用程序拋出一個解密異常(500 - Internal Server Error)。
接受到合法的密文(填充正確)但解密后得到一個非法的值,應用程序顯示自定義錯誤消息(200 - OK)。
上述的場景體現了一個典型的Padding Oracle(填充提示),我們可以利用應用程序的行為輕易了解某個加密的值是否填充正確。這里的單詞Oracle代表了一種機制,用于了解某個測試是否通過。
既然已經給出了場景,那么我們便來查看應用程序所使用的一個加密后的參數。這個參數保存了使用分號隔離的一系列值,在我們的示例中,則是用戶名(BRIAN),公司ID(12)及角色ID(12):因此這里的明文是“BRIAN;12;2;”。以下則是經過加密的Query String實例,請注意加密后的UID參數使用了ASCII十六進制表示法。
在實際情況中,攻擊者并不會知道這里所對應的明文是多少,不過作為示例,我們已經知道了明文、填充、以及加密后的值(如下表)。正如之前所提到的那樣,IV添加在密文的前段,即最前面8個字節。
攻擊者可以根據加密后值的長度來推測出數據塊的大小。由于長度(這里是24)能被8整除但不能被16整除,因此可以得知數據塊的大小是8個字節。現在我們來觀察下加密和解密的內部實現,下圖便展示了字節級別的運算方式,這對以后攻擊方式的討論很有幫助。請注意,其中帶圓圈的加號表示XOR(異或)操作。
加密過程:
解密過程:
同樣值得指出的是,解密之后的最后一個數據塊,其結尾應該包含正確的填充序列。如果這點沒有滿足,那么加/解密程序就會拋出一個填充異常。#p#
利用Padding Oracle進行解密
我們現在來關注一下如何利用Padding Oracle Attack進行解密。我們將每次操作一個單獨的加密塊,因此我們可以獨立出第一塊密文(IV后的那塊),在前面加上全為NULL的IV值,并發送至應用程序。以下是URL極其相關回復:
Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537 Response: 500 - Internal Server Error
回復的500錯誤是意料之中的,因為這個值在解密后完全非法。下圖展示了應用程序在嘗試解密的時候究竟做了哪些事情。您會發現,因為我們只處理單個數據塊,因此它的結尾必須包含正確的填充字節,才能避免出現非法填充異常。
如上圖所示,在解密之后,數據塊的末尾并沒有包含正確的填充序列,因此出現了異常。現在我們將IV加一,并發送同樣的密文,看看會發生什么:
Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537 Response: 500 - Internal Server Error
與之前一樣,我們得到了500異常。這是因為在解密后我們還是沒有獲得合法的填充序列。稍有不同的是,我們在深入內部之后會發現,最后一個字節的值會有所變化(變成了0x3C而不是0x3D)。
如果我們重復發送這樣的請求,每次將IV的最后一個字節加一(直至0xFF),那么最終我們將會產生一個合法的單字節填充序列(0x01)。對于可能的256個值中,只有一個值會產生正確的填充字節0x01。遇上這個值的時候,你應該得到一個不同于其他255個請求的回復結果:
Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537 Response: 200 OK
同樣,我們從示意圖中了解一下此時發生了什么:
在這個情況下,我們便可以推斷出中間值(Intermediary Value)的最后一個字節,因為我們知道它和0x3C異或后的結果為0x01,于是:
因為 [Intermediary Byte] ^ 0×3C == 0×01,
得到 [Intermediary Byte] == 0×3C ^ 0×01,
所以 [Intermediary Byte] == 0×3D
現在我們可以更進一步。我們已經知道了中間值的最后一個字節,于是我們可以推斷出解密后的值是多少。您可以回憶一下,在解密的過程中,中間值的每個字節都會與密文中的前一個數據塊(對于第一個數據塊來說便是IV)的對應字節進行異或操作,于是我們使用之前示例中原來的IV中的最后一個字節(0x0F),與中間值異或一下便可以得到明文。不出意料,我們會得到0x32,這表示數字“2”(明文中第一個數據塊的最后一個字節)。
我們現在已經破解了示例數據塊中的第8個字節,是時候關注第7個字節了。在破解第8個字節時,我們使用暴力枚舉IV,讓解密后的最后一個字節成為0x01(合法填充)。在破解第7個字節的時候,我們要做的事情也差不多,不過此時要求第7個字節與第8個字節都為0x02(再重復一遍,這表示合法的填充)。我們已經知道,中間值的最后一個字節是0x3D,因此我們可以將IV中的第8個字節設為0x3F(這會產生0x02)并暴力枚舉IV的第七個字節(從0x00開始,直至0xFF)。
我們再次遭遇填充異常,直至遇上某個值,它使得解密后的第7個字節成為0x02(正確填充),此時IV中的字節為0x24:
使用這種技巧,我們可以從后往前破解中間值里的每個字節,最終得到解密后的值(盡管每次一個字節)。下圖展示了完全破解后的IV值,此時整個數據塊都為填充值(0x08):
使用PadBuster進行解密
(譯注:這段內容為PadBuster的使用指南,在此略過,如果您對這部分內容感興趣可以閱讀原文《Automated Padding Oracle Attacks with PadBuster》。)
加密任意的值
我們已經知道如何利用Padding Oracle和PadBuster來依次破解每個加密的數據塊。現在,我們就來觀察下如何使用同樣的漏洞來加密任意數據。
可能您已經發現,一旦我們可以推斷出密文數據塊的中間值,我們便能通過操作IV的值來完全控制解密所得到的結果。例如,在前面的示例中,如果想要將密文中第一個數據塊解密為“TEST”這個值,您可以計算出它所需要的IV值,只要將目標明文與中間值進行異或操作即可。因此,只要您將字符串“TEST”(自然,還包括四個0x04字節作為填充)與中間值異或之后,便可以得到最終的IV,即0×6D,0×36,0×70,0×76,0×03,0×6E,0×22,0×39:
這種做法對于單個數據塊來說自然沒有問題,但如果我們想要用它來生成長度超過一個數據塊的值又該怎么辦呢?我們來看一個簡單通俗的實際案例。這次我們要生成一個加密的字符串“ENCRYPT TEST”而不僅僅是“TEST”。第一步,還是將文本分拆成數據塊,并補上必須的填充字節,如下圖:
在構造超過一個數據塊的值時,我們實際上是從最后一個數據塊開始,向前依次生成所需的密文。在這里,最后的數據塊與之前的相同,因此我們已經知道以下的IV和密文能夠生成字符串“TEST”:
Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537
接下來,我們需要弄明白中間值6D367076036E2239在作為密文,而不是IV傳遞至應用程序時會被如何解密。在這里只要使用與破解過程相同的技巧就行了,我們把它作為密文傳遞給應用程序,并從全部為NULL的IV開始進行暴力破解:
Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239
一旦我們通過暴力破解得到中間值之后,IV便可以用來生成我們想要的任意值。新的IV可以被放在前一個示例的前面,這樣便可以得到一個符合我們要求的,包含兩個數據塊的密文了。這個過程可以不斷重復,這樣便能生成任意長度的數據了。
使用PadBuster加密任意的值
(譯注:這段內容為PadBuster的使用指南,在此略過,如果您對這部分內容感興趣可以閱讀原文《Automated Padding Oracle Attacks with PadBuster》。)