最近在研究TLS協議,瞭解到了TLS1.2時期使用CBC加密導致的 Padding Oracle 攻擊(填充神諭攻擊),這篇文章稍微講一下,也順便總結一下我的理解。
TLS這個協議本身常規的握手就不說了,核心在於他的加密方法CBC.
CBC 模式的異或延展性
要理解攻擊原理,首先需要明確 CBC(密碼塊鏈接)模式的解密公式。對於任意一個密文塊 Ci,其解密得到明文 Pi 的過程分為兩步:
- 使用密鑰解密當前密文塊
Ci,得到一個攻擊者不可見的中間狀態 (Intermediate State),記為Ii。 - 將中間狀態
Ii與前一個密文塊Ci−1進行異或運算,得到真實的明文 Pi。
公式表達為:Pi=Ii⊕Ci−1
我並不是密碼學的專家,對於這些符號的意義,只需要知道C = cipher(密文), P = plain (明文) 即可.
我的初步疑惑在於“修改密文的意義”。實際上,攻擊者的目標不是當前密文塊 Ci,而是前一個在網絡上明文傳輸的密文塊 Ci−1。根據上述公式,如果攻擊者篡改了 Ci−1 的某個字節,那麼最終計算出的明文 Pi 的對應字節也會發生完全可控的改變。這就賦予了攻擊者在不掌握密鑰的情況下,操控解密結果的能力。
攻擊的實施路徑與狀態倒推
攻擊者將服務器本身當作了一個“神諭(Oracle)”。在早期的協議設計中(如 TLS 1.2 的某些套件),服務器通常採用“先解密、後校驗填充、再校驗完整性”的處理順序。如果解密後的數據不符合 PKCS#7 填充規範,服務器會返回一個特定的 Padding Error;如果填充規範正確但後續的 MAC 校驗失敗。
利用這一邏輯,攻擊過程可分為以下幾個嚴謹的步驟:
1. 爆破單字節的中間狀態
攻擊者截獲 Ci−1 和 Ci 後,開始遍歷修改 Ci−1 的最後一個字節(從 0x00 到 0xFF),併發送給服務器。
當服務器沒有返回 Padding Error 時,意味著當前被篡改的密文解密後,結尾恰好符合 PKCS#7 規範中長度為 1 的填充,即明文的最後一個字節極大概率是 0x01。
- 為什麼是極大概率是
0x01? PKCS#7 填充規範中,按照字節填充長度來決定後需填充的字節,例如差3字節那麼後續填充字節為0x030x030x03, 2字節為0x020x02, 按照這個規律,攻擊者之修改1字節的情況下,就讓服務器響應了PaddingError以外的錯誤,有理由直接得出最後1位為0x01的結論。當然也不排除0x02的情況,但是發生其他字節的情況極小,首先是解密運算正好是0x0[len] 並且前面len位也正好是 0x0[len] 的情況。
此時,攻擊者掌握了一個確定的等式:
Ii[最後字節]⊕Ci−1′[最後字節]=0x01
在這個方程中,攻擊者構造的 Ci−1′ 是已知的。通過簡單的數學變換,攻擊者就可以直接算出該字節的中間狀態:
Ii[最後字節]=0x01⊕Ci−1′[最後字節]
拿到中間狀態後,將其與網絡抓包獲取的原始 Ci−1 進行異或,原始明文的最後一個字節就被成功還原了。
2. 鏈式反應與全塊破解 在破解了最後一個字節的中間狀態後,目前只拿到 1 個字節的中間狀態,後續字節該如何繼續攻擊?
答案: 是通過已知的中間狀態,人為構造更長的合法 Padding。
為了破解倒數第二個字節,攻擊者需要讓服務器在解密時,認為當前的填充是 0x02 0x02。因為最後一個字節的中間狀態 Ii 已知,攻擊者可以精準計算出需要向服務器發送什麼數值,才能讓解密出的最後一個字節固定為 0x02:
Ci−1[最後字節]=Ii[最後字節]⊕0x02
固定住最後一個字節後,攻擊者開始遍歷爆破 Ci−1 的倒數第二個字節,直到服務器再次判定填充合法(即結尾變為 0x02 0x02)。此時,倒數第二個字節解密出的明文必然是 0x02,據此便可算出倒數第二個字節的中間狀態。
依此類推,攻擊者可以將目標依次設定為 0x03 0x03 0x03 直至 16 個 0x10。平均每個字節僅需 128 次請求,就可以將整個數據塊的 16 字節中間狀態全部反推出來,進而還原完整的明文。
Padding Oracle的攻擊相當的精妙,也是非常著名的一種密碼學攻擊。