道哥的《白帽子講web安全》有一章提到Padding Oracle Attack的攻擊方式,聽說這貨在2011年的Pwnie Rewards上還被評爲"最具價值的服務器漏洞"。php
抱着書看了半天,感受也不是很理解,和密碼學結合的比較緊,有一些理論的東西在裏面。這裏作個學習筆記,研究一下。html
1. 相關閱讀材料python
https://github.com/GDSSecurity/PadBuster PadBuster - Automated script for performing Padding Oracle attacksgit
http://hi.baidu.com/aullik5/item/49ab45de982a67db251f40f6 道哥的分析github
http://www.di-mgt.com.au/cryptopad.html Padding原則web
http://hi.baidu.com/306211321/item/faa44923c3c07d98b7326387 分組密碼的連接模式算法
http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html 做者寫的分析安全
http://www.cnblogs.com/JeffreyZhao/archive/2010/09/25/1834245.html 老趙寫的分析(和ASP.NET結合)服務器
http://www.isg.rhul.ac.uk/~kp/secretIV.pdf 國外的牛牛寫的分析(拓展Padding Oracle的知識面)cookie
http://www.icylife.net/yunshu/attachments/Padding-Oracle-Attack.pdf 雲舒寫的分析
http://netifera.com/research/poet/PaddingOraclesEverywhereEkoparty2010.pdf EKOPARTY 2010的演講PPT
http://netsecurity.51cto.com/art/201101/244089_1.htm 51CT上看到的分析
2. 前引知識
2.1 分組的填充Padding
分組密碼Block Cipher須要在加載前確保每一個每組的長度都是分組長度的整數倍。通常狀況下,明文的最後一個分組頗有可能會出現長度不足分組的長度:
這個時候,廣泛的作法是在最後一個分組後填充一個固定的值,這個值的大小爲填充的字節總數。即假如最後還差3個字符,則填充0x03。
這種Padding原則遵循的是常見的PKCS#5標準。http://www.di-mgt.com.au/cryptopad.html#PKCS5
2.2 CBC(Cipher Block Chaining CBC)模式
這是一種分組連接模式,目的是爲了使本來獨立的分組密碼加密過程造成迭代,使每次加密的結果影響到下一次加密。這行能夠強化加密算法的"敏感性",即實現所謂的"雪崩效應",在香濃理論中這就是"擾亂原則"。
以前在《密碼學》課後作的筆記:http://hi.baidu.com/306211321/item/faa44923c3c07d98b7326387
這裏我我的感受要注意的一點是:
在連接模式中,初始化IV的長度要和對稱加密算法的分組長度一致。緣由是連接模式中的異或操做是等長操做。
3. Padding Oracle Attack攻擊的原理
由於Padding Oracle Attack是針對CBC連接模式的攻擊,和具體的加密算法無關(分組)。因此這裏咱們選擇DES爲例進行闡述。
假設明文爲: LittleHann(明文長度爲10 8 < 10 < 16 即便用2個分組)
通過DES加密(CBC模式)後,其密文爲: EFC2807233F9D7C097116BB33E813C5E
加密程序: http://pan.baidu.com/s/1e2o7
密文采用了"ASCII十六進制的表示方法",即兩個字符表示一個字節的十六進制數。這是由於密碼學算法中獲得的密文常常會出現不可打印字符,爲了保證在網絡上傳輸的正確而不受不一樣系統間編碼方案的影響,就有必要對密文進行"可視化"轉化(即轉化成可打印字符)。除了"ASCII十六進制的表示方法"以外,還能夠採用"base64編碼方法"。
PS:
這裏插個題外話:PHP的DES及其餘密碼學算法的加密是經過"PHP加密擴展庫Mcrypt"來實現的。
http://www.php100.com/cover/php/2651.html
整個加密過程以下:
初始化向量IV與明文(第一組明文)XOR後,再通過運算獲得的結果做爲新的IV,用於下一分組(分組2),若是迭代下去。
解密過程是加密過程的逆過程:
這裏要注意,前幾個分組的解密結果對咱們都沒有意義,咱們重點關注的是最後一個分組的解密結果。看這張圖可能會清楚一點:
注意到最後一個分組的末尾的數值爲0x04,即表示填充了4個Padding。若是最後的Padding不正確(值和數量不一致),則解密程序每每會拋出異常(Padding Error)。而利用應用的錯誤回顯,咱們就能夠判斷出Paddig是否正確。
這裏有幾個概念要先理清一下:
1. 基於密碼學算法的攻擊,每每第一個要搞清楚的是,咱們在攻擊誰,或者準確的說咱們的攻擊點在哪裏?在一個密碼學算法中,有不少的參數(指攻擊者能夠控制的參數),攻擊者每每是針對其中某一個或某一些參數進行破解,窮舉等攻擊。
在Padding Oracle Attack攻擊中,攻擊者輸入的參數是IV+Cipher,咱們要經過對IV的"窮舉"來請求服務器端對咱們指定的Cipher進行解密,並對返回的結果進行判斷。
2. 和SQL注入中的Blind Inject思想相似。我以爲Padding Oracle Attack也是利用了這個二值邏輯的推理原理,或者說這是一種"邊信道攻擊(Side channel attack)"。http://en.wikipedia.org/wiki/Side_channel_attack
這種漏洞不能算是密碼學算法自己的漏洞,可是當這種算法在實際生產環境中使用不當就會形成問題。
和盲注同樣,這種二值邏輯的推理關鍵是要找到一個"區分點",即能被攻擊者用來區分這個的輸入是否達到了目的(在這裏就是尋找正確的IV)。
好比在web應用中,若是Padding不正確,則應用程序極可能會返回500的錯誤(程序執行錯誤);若是Padding正確,但解密出來的內容不正確,則可能會返回200的自定義錯誤(這只是業務上的規定),因此,這種區別就能夠成爲一個二值邏輯的"注入點"。
3. 攻擊成立的兩個重要假設前提:
1. 攻擊者可以得到密文(Ciphertext),以及附帶在密文前面的IV(初始化向量)
2. 攻擊者可以觸發密文的解密過程,且可以知道密文的解密結果
4. 可能出現的狀況
明文分組和填充就是Padding Oracle Attack的根源所在,可是這些須要一個前提,那就是應用程序對異常的處理。當提交的加密後的數據中出現錯誤的填充信息時,不夠健壯的應用程序解密時報錯,直接拋出"填充錯誤"異常信息(這個錯誤信息在不一樣的應用中是不一樣的體現,在web通常是報500錯誤)。
攻擊者就是利用這個異常來作一些事情,假設有這樣一個場景,一個WEB程序接受一個加密後的字符串做爲參數,這個參數包含用戶名、密碼。參數加密使用的最安全的CBC模式,每個block有一個初始化向量IV(注意:這個IV在服務器第一次生成這個密文的時候就產生了,並保存在服務器上,攻擊者須要在提交數據的時候也提交這個IV,後面咱們會看到,攻擊者實際上就是在"窮舉"這個IV)。
當提交參數時,服務端的返回結果會有下面3種狀況:
a. 參數是一串正確的密文,分組、填充、加密都是對的(程序運行自己沒出問題),包含的內容也是正確的(業務邏輯是對的),那麼服務端解密、檢測用戶權限都沒有問題,返回HTTP 200。
b. 參數是一串錯誤的密文,包含不正確的bit填充(程序運行自己出現致命錯誤),那麼服務端解密時就會拋出異常,返回HTTP 500 server error。
c. 參數是一串正確的密文(程序運行自己沒出問題),包含的用戶名是錯誤的(業務邏輯是錯的),那麼服務端解密以後檢測權限不經過,可是依舊會返回HTTP 200戒者HTTP 302,而不是HTTP 500。
攻擊者無需關心用戶名是否正確,只須要提交錯誤的密文(由於這裏有4中變量狀況,爲了構造出二值邏輯推理,咱們要定住其中2個狀況,即讓業務邏輯恆錯,對Bit Padding的狀況進行邏輯推理),根據HTTP Code便可作出攻擊。
咱們繼續回到原理分析上來。
假設有這樣一個應用 http://sampleapp/home.jsp?UID=0000000000000000EFC2807233F9D7C097116BB33E813C5E
(中間用箭頭隔開了,前面的16個字母(即8字節 ASCII十六進制表示法兩個字母爲一個字節)爲攻擊者輸入的IV。後面的32個字母(即16字節)爲攻擊者輸入的密文)
咱們向服務器發送這樣一個請求:
Request: http://sampleapp/home.jsp?UID=0000000000000000EFC2807233F9D7C097116BB33E813C5E
Respose: 500 - Internal Server Error
此時在解密時Padding是不正確的(填充的值和填充的數量不一致)
例如:
程序判斷Padding是否出錯通常是去檢驗末尾的那個字節的值,這裏是0x3D,顯然不對。這裏咱們再次回憶一下Padding原則:
1個字節的Padding爲0x01
2個字節的Padding爲0x02
3個字節的Padding爲0x03
4個字節的Padding爲0x04
5個字節的Padding爲0x05
6個字節的Padding爲0x06
7個字節的Padding爲0x07
8個字節的Padding爲0x08(當原始的明文正好是分組的整數倍的時候,Padding一個整組的填充值)
也就是說,Padding的值只多是0x01~0x08之間。
接下來就是最關鍵的部分了,也是我一開始看比較難理解的地方。
咱們接下來要利用選擇密文攻擊的思想,不斷調整,修正IV。來對Intermediary Value進行猜想。
1) Padding 0x01
咱們不斷地調整IV的值,以但願解密後,最後一個字節的值爲正確的Padding Byte,這裏是0x01。由於Intermediary Value是固定的(咱們此時不知道Intermediary Value),所以從0x00~0xFF之間,只可能有一個IV的值與Intermediary Value的最後一個字節進行XOR後,結果是0x01(思考: 由於0x01只有最後1 bit爲1,其餘都是0,因此根據XOR的性質,只能存在一個值能XOR獲得0x01)。攻擊者經過遍歷這255個值,能夠找出IV須要的最後一個字節。
Request: http://sampleapp/home.jsp?UID=0000000000000066EFC2807233F9D7C097116BB33E813C5E
Respose: 200 OK
經過XOR運算,能夠立刻推導出此Intermediary Byte的值:
if(Inermediary Byte) ^ 0x66 == 0x01 { Inermediary Byte = 0x66 ^ 0x01 } so: Inermediary Byte = 0x67
在回過頭來看看加密過程:
初始化向量IV與明文進行XOR運算獲得了Inermediary Value,所以將剛纔獲得的Inermediary Byte(0x67)與"真實"的IV的最後一個字節0x0F(攻擊者事先獲取到的)進行XOR運算,即能獲得明文:
0x67 ^ 0x0F = 0x68 : H
即獲得明文(第一個分組)的最後一個字母H!
這裏我們要稍微停一下,把思路理一理:
1. 咱們在得出明文的那次XOR計算中用到的IV(0x0F)和咱們攻擊者不斷"注入"的IV(0x01~0xFF)不是一回事,要區分開來。在計算明文的那個IV(0x0F)是咱們事先就獲取到的,回想咱們以前說的這個Padding Oracle Attack攻擊的成立條件:
這個IV(0x0F)是服務器端在發送密文的時候(多是cookie形式)附帶在密文的頭部發給咱們的。 通常狀況下,若是跨系統發送這種"帶鹽"的密文,都要把"鹽(IV)"附帶在密文的頭部或其餘位置一塊兒發送給接收方。這裏咱們接收到的IV就是0x0F,在不一樣的環境中這個IV必然是不同的,關鍵是要理解原理。
2. 爲何咱們能夠不斷嘗試IV?
按理來講,IV不是在服務器第一次生成這段密文的時候就生成好了嗎?而後在每次發送的時候都附帶在密文的頭部,不會再變了......
的確是這樣,但這說的是在正常的解密狀況下發生的事,而咱們攻擊者如今作的事並非在解密(事實上攻擊者這個時候也不知道IV和KEY),咱們只是在經過不斷的修改IV來對目標解密系統進行"試探",從返回的結果來進行"側信道攻擊",從而進行二值邏輯推理。
這就是一種所謂的"選擇密文攻擊"方式,好像也有叫Bit-Flipping Attack:
http://www.vnsecurity.net/2010/03/codegate-2010-challenge-8-bit-flipping-attack-on-cbc-mode/
繼續回到思路分析上來:
在正確"匹配"了Padding 0x01以後,須要作的是繼續推導出剩下的Intermediary Byte。根據Padding 原則,在須要Padding兩個字節的時候,其值應該是0x02。
2) Padding 0x02
這個時候咱們要注意,以前說過,這個攻擊過程是個循環迭代的過程,上一步的結果就是做爲下一步的基礎。
咱們以前已經知道Intermediary Byte的最後一個字節爲0x61,所以能夠"更新"IV(攻擊者輸入的IV)的第8個字節爲 0x66 ^ 0x02 = 0x64。
(思考: 這個Padding Oracle Attack的過程當中,攻擊者須要不斷地調整輸入IV的值,以前由於在Padding 0x01中,咱們只是在假設Padding 0x01的狀況,在這個假設下,咱們經過得出IV的最字節,從而計算出Intermediary Byte,進而算出明文的最後一個字節"H"。這裏要注意的是,這個假設的IV沒有任何意義,只是咱們進行"選擇密文攻擊"過程當中的一個路人甲而已。而接下來咱們要繼續假設Padding 0x02的狀況,爲了使在假設Padding 0x02中,Intermediary Value的最後一個字節依然爲"0x67(以前算出來的)",因此咱們要對IV的最後一個字節進行迭代更新: 0x66 ^ 0x02 = 0x64)。
這個時候,本質上攻擊者是固定住了IV的最後一個字節不變,開始循環"盲注"倒數第二個字節。開始依照第一步時的方法對倒數第二個字節進行"盲注"邏輯判斷。
Request: http://sampleapp/home.jsp?UID=0000000000007064EFC2807233F9D7C097116BB33E813C5E
Respose: 200 OK
經過遍歷能夠得出,IV的第7個字節爲0x70。
if(Inermediary Byte) ^ 0x70 == 0x02 { Inermediary Byte = 0x70 ^ 0x02 } so: Inermediary Byte = 0x72
對應的Intermediary Byte爲0x72。知道了Intermediary Byte的倒數第二個字節爲0x72,就能夠得出明文的的倒數第二個字節:
0x72 ^ 0x17 = 0x65 : e
(這裏的IV: 0x17是咱們從服務端接收到的附帶在密文頭部的IV的倒數第二個字節)
接下來,要繼續對IV進行假設,同理,此次"選擇密文攻擊"的假設是Padding 0x03,對IV進行迭代更新,而後對IV的倒數第三個字節進行"窮舉"循環探測。
Padding 0x03
Padding 0x04
Padding 0x05
Padding 0x06
Padding 0x07
Padding 0x08
最終獲得這個分組的明文LittleH。這是第一個分組的明文。
注意,Padding Oracle Attack是以單個分組進行的。到了這一步,咱們會發現,咱們的攻擊目標其實就是那個臨時中間值變量Intermediary Value,獲得了這個值,再加上咱們原本就能夠獲取到IV(服務器端生成的附在密文頭部的那個IV),咱們能夠經過XOR運算獲得這個分組的明文。
對於多個分組的密文來講,咱們繼續觀察一下CBC的解密流程:
第二個分組使用的IV(對於第一組來講是附帶在密文頭部的那段)是第一組分組的密文。所以咱們就把第一組的密文帶入帶第二組的計算中。繼續第對二組的Intermediary Value進行邏輯推導,最終獲得第二組的密文:ann。
多分組的密文能夠依次類推,由此便可以僅根據密文和IV還原出明文。
4. Padding Oracle Attack的攻擊利用場景
一旦咱們經過暴力破解獲得中間值Intermediary Value以後,IV即可以用來生成咱們想要的任意值。新的IV能夠被放在前一個示例的前面,這樣即可以獲得一個符合咱們要求的,包含兩個數據塊的密文了。這個過程能夠不斷重複,這樣便能生成任意長度的數據了。
使用PadBuster加密任意的值
https://github.com/GDSSecurity/PadBuster
5. 模擬實驗
這是道哥寫的python腳本,能夠用來模擬實驗出Padding Oracle Attack的原理:
能夠參考源代碼來進行深刻的學習,加深理解。
6. 後記
本身按照書上講的理解了一下,若有不對之處,望指正。