Last Regrets

CBC: Padding Oracle 攻击

· sdttttt

****最近在研究TLS协议,了解到了TLS1.2时期使用CBC加密导致的 Padding Oracle 攻击(填充神谕攻击),这篇文章稍微讲一下,也顺便总结一下我的理解。

TLS这个协议本身常规的握手就不说了,核心在于他的加密方法CBC.

CBC 模式的异或延展性

要理解攻击原理,首先需要明确 CBC(密码块链接)模式的解密公式。对于任意一个密文块 Ci,其解密得到明文 Pi 的过程分为两步:

  1. 使用密钥解密当前密文块 Ci,得到一个攻击者不可见的中间状态 (Intermediate State),记为 Ii
  2. 将中间状态 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 的最后一个字节(从 0x000xFF),并发送给服务器。 当服务器没有返回 Padding Error 时,意味着当前被篡改的密文解密后,结尾恰好符合 PKCS#7 规范中长度为 1 的填充,即明文的最后一个字节极大概率是 0x01

  • 为什么是极大概率是 0x01 ?

    PKCS#7 填充规范中,按照字节填充长度来决定后需填充的字节,例如差3字节那么后续填充字节为 0x03 0x03 0x03 , 2字节为 0x02 0x02 , 按照这个规律,攻击者之修改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的攻击相当的精妙,也是非常著名的一种密码学攻击。