关于CBC中Padding Oracle攻击的再解读——如何攻破HTTPS

为什么说是再解读呢?目前关于Padding Oracle攻击的介绍,比较好的文章包括的内容,都取自于这篇外文。但是,该文在论述一个关键问题,即如何确认Padding位数时,并没有提及,这让许多较真的读者会有很大的疑惑。本人亦如此。因此,我想再把思路做个梳理

1.分组密码和填充

常用的对称加密算法,如DES和AES,在用密钥加密数据时,只能加密和密钥长度相同的数据。对于超长数据,我们需要将其切分成块。这就带来一个问题,可能最后一个块,无法和密钥“对齐”(当然,这也包括原始数据本来就比密钥短的情况)。这就需要一些数据去填充最后的几位。常用的填充算法即***PKCS#5***,在数据填充中,使用缺失的位数长度来统一填充,说起来麻烦,上图即可明白:
填充

即缺5位,就用0x05填充;缺2位,就用0x02填充;如果刚刚好,还要扩展出一个块,全用0x08填充。

2.初始向量和CBC加解密过程

如果每一个块都用同一个密钥加密,很容易使用统计学原理去分析和破解密文(原理大致如同e字母在英文单词中出现的概率是非常高的,因此通过统计分析,大致可能猜出X代表了e),为此CBC加密过程引入了一个初始向量,使得每一个块的加密密钥,都包涵有上一个块的密文的反馈,从而解决了统计学攻击问题。还是上图比较容易理解,因为仅仅涉及到一个异或运算,所以不做解释,其中的Encryption/Decryption,就是加密算法所在。但是,Padding Oracle攻击,恰恰是避过了这一块的实现细节,因此大家可以忽律那个框框,如图:
加密

而解谜,就是一个反过程
解密

因此,双方除了要交换密文外,还需要交换初始向量

3.Padding Oracle的攻击条件

并不是使用CBC的服务都有弱点.我们可以构想如下一个可以被利用的漏洞服务器,来解释其特点:对于请求,会有如下反馈:

  • 如果解密过程没有问题,明文验证(如用户名密码验证)也通过,则会返回正常 HTTP 200
  • 如果解密过程没有问题,但是明文验证出错(如用户名密码验证),则还是会返回 HTTP 200,只是内容上是提示用户用户名密码错误
  • 如果解密过程出问题了,比如Padding规则核对不上,则会爆出 HTTP 500错误。

这样举例,仅仅是为了说明,可被利用的服务器的特点是:对于解密过程自身的异常,会有一个特殊的错误提示,有别于明文验证出错。

4.Padding Oracle攻击过程

该例子还是沿用这篇外文中的例子。

假设一个服务的请求如下:

8ED4AAC6```,注意其前8字节```7B216A634951170F```是初始化向量。

我们来看看这样一个服务,是如何加解密一个字符串```BARIN;12;1```的(加密本身用了3DES,但这不是问题的重点,可以忽略)。
![字符串加密](/content/images/2016/03/po_fig3.png)

解密过程如下
![字符串解密](/content/images/2016/03/po_fig4.png)

注意最终的Padding是符合验证的。

***通过解密过程我们可以看出,因为初始化向量是可以知道的,如果我们知道中间值,即```Intermediary Value```,在不需要知道加密过程时,通过简单的异或,就可以知道明文是什么了。甚至,我们还可以通过中间值仿造任意的明文字段做暴力攻击。***

现在假设有一个中间人,截获了报文,那么它可以这样操作:首先,向服务器发送请求时,把初始化向量全部设为0x00,且只保留第一个块,最终报文是```http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537```。其解密过程如下:
![0x00填充](/content/images/2016/03/po_fig5_sm.png)

因为最终填充校验有误,自然服务器会报错HTTP 500。

之后,中间人将初始向量递增1,用```http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537```去试探,自然也是报错
![0x01试探](/content/images/2016/03/po_fig6_sm.png)

因为对于固定的中间值,即图中的```Intermediary Value```的最后一位,必然有一个字节和它异或时,能够达成Padding为0x01使其满足Padding规则。因此,我们不断递增测试,必然有一个初始向量,使得最后一位的Padding规则满足条件,如```000000000000003C```,如图
![满足条件](/content/images/2016/03/po_fig7_sm.png)

***但是这里有一个问题,是其它几篇文章没有分析到的,我在这里继续说明一下***

> 是不是当我们递增初始向量最后一位时,如果碰到服务器返回200时,必然Padding最后一位是0x01呢??答案并不是

比如,当中间值最后两位是```0x02 0x00```,而我们测试的初始向量最后两位是```0x00 0x02```时,也就是探测最后一位是```0x02```时,最终的Padding的最后两位是```0x02 0x02```,必然也满足Padding规则,服务器当然也会返回200。可见,仅仅依靠我们递增最后一位和测试服务器是否返回200,是没办法确认最终的Padding是0x01的。

那么怎么才能确认呢?观察异或的过程,可以看出,如果padding是0x01,那么,倒数第二位是什么,并不会影响服务器测试结果(因为改变倒数第二位,仅仅是改变了解码后的明文,会导致明文验证过程异常,但是解密过程是没有任何异常的),此时服务器还是返回200。但如果Padding是```0x02 0x02```,则改变倒数第二位,会导致解密异常,服务器返回500。因此,我们通过测试倒数第二位,确认了探测过程中得到的Padding是0x01。

有了确定的初始向量最后一位,和确定的Padding最后一位0x01,我们就可以推出确定的中间值最后一位。

之后,我们再通过碰撞Padding最后两位是```0x02 0x02```,来测试出中间值的第二位是什么。这里有一个技巧要注意,因为中间值最后一位,已经碰撞出来,而要得到Padding最后一位是0x02,势必初始向量的最后一位也是固定了(就是个简单的异或),因此,我们要递增的是初始向量的倒数第二位,如图
![碰撞第二位](/content/images/2016/03/po_fig9_sm.png)

依此类推,我们最终可以确认出全部中间值来
![全部中间值](/content/images/2016/03/po_fig10_sm.png)

正如前面所说,有了确认的中间值,和已知的初始化向量,我们就可以知道第一块的明文了。依此类推,我们可以测试出所有块的明文来。

### 5.关于HTTPS中的CBC的Padding Oracle攻击防御

那么HTTPS中,如何防范CBC攻击呢。答案是禁用SSLv3。但是为了浏览器兼容性(比如IE6仅支持到SSLv3),可能没法废除SSLv3时,那么可以禁用以下CipherSuite

> IDEA-CBC-SHA, EXP-DES-CBC-SHA, DES-CBC-SHA, DES-CBC3-SHA, EXP-DH-DSS-DES-CBC-SHA, DH-DSS-DES-CBC-SHA, DH-DSS-DES-CBC3-SHA, EXP-DH-RSA-DES-CBC-SHA, DH-RSA-DES-CBC-SHA, DH-RSA-DES-CBC3-SHA, EXP-DHE-DSS-DES-CBC-SHA, DHE-DSS-CBC-SHA, DHE-DSS-DES-CBC3-SHA, EXP-DHE-RSA-DES-CBC-SHA, DHE-RSA-DES-CBC-SHA, DHE-RSA-DES-CBC3-SHA, EXP-ADH-DES-CBC-SHA, ADH-DES-CBC-SHA, ADH-DES-CBC3-SHA, EXP-RC2-CBC-MD5, IDEA-CBC-SHA, EXP-DES-CBC-SHA, DES-CBC-SHA, DES-CBC3-SHA, EXP-DHE-DSS-DES-CBC-SHA, DHE-DSS-CBC-SHA, DHE-DSS-DES-CBC3-SHA, EXP-DHE-RSA-DES-CBC-SHA, DHE-RSA-DES-CBC-SHA, DHE-RSA-DES-CBC3-SHA, ADH-DES-CBC-SHA, ADH-DES-CBC3-SHA, AES128-SHA, AES256-SHA, DH-DSS-AES128-SHA, DH-DSS-AES256-SHA, DH-RSA-AES128-SHA, DH-RSA-AES256-SHA, DHE-DSS-AES128-SHA, DHE-DSS-AES256-SHA, DHE-RSA-AES128-SHA, DHE-RSA-AES256-SHA, ADH-AES128-SHA, ADH-AES256-SHA

>注意,并不是都包涵CBC字样,有些没有包涵,一样要禁用了

关于这个问题,详见[这篇文章](https://www.tinfoilsecurity.com/blog/how-to-fix-poodle-and-why-you-are-probably-still-vulnerable)

 
### 6.结语

首先感谢[V2EX论坛的用户讨论](https://www.v2ex.com/t/260993),正是因为彼此之间的交流,才让本人弄清楚了Padding Oracle攻击中一个很重要的点:Padding长度的确认。

本文的部分内容来自于[该篇文章](http://www.icylife.net/yunshu/attachments/Padding-Oracle-Attack.pdf)以及其使用的[原始文章](http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html),我补充了个人认为非常重要的一个章节,即刚才谈及的Padding长度的确认。

好了,本文至此。

Show Comments