Hed9eh0g

前进的路上总是孤独的

CBC Padding Oracle Attack

本文共计有2320个字

前言

这种攻击方法在《白帽子讲web安全》中提到过,算是一个比较有名的攻击方式了,本来想把这个知识点直接接在字节翻转攻击那篇文之后,后来觉得把它添上去可能篇幅会过长,所以打算就此篇做个介绍。另外本文就不再讲CBC的概念了,如果需要了解的请转到之前的一篇

PKCS #5

来源

我们知道CBC模式是结合AES或DES算法来实现加密解密的,而AES或DES属于分组加密算法,也就是说在加密文本之前,我们首先需要对文本进行分组,使每一组的长度都相等。
然而在大多数情况下,对文本分组的最后一组往往不够长,这个时候就要对最后一组进行填充,使其填充至与前面的分组等长,而填充的规则就包括PKCS #5和PKCS #7。DES遵循PKCS#5而AES遵循PKCS #7,两种规则的区别仅仅在于前者是用于填充每8字节为一组的,后者填充每16字节为一组的,因此本文只介绍PKCS #5的具体规则。

规则要求

当最后一组长度不够8位时,必须在其后填充一个固定的十六进制数值,这个十六进制数值是根据缺几个字节来取的:也即如果缺一个字节,便填充1个0x01;如果缺两个字节便填充2个0x02…如果缺七个字节便填充7个0x07。特别地,当最后一组长度为8位时,也需要填充8个0x08,如图:
《CBC  Padding Oracle Attack》

也就是说PKCS #5的填充范围为0x00至0x08,以此类推PKCS #7的填充范围为0x00至0x10。

攻击原理

Padding Oracle不是对算法进行攻击,而是利用服务器在计算时的响应信息进行推断最终实现攻击目的。

前提信息

1、攻击者能够知道密文cipher,以及附带在密文前面的初始化向量iv。
2、攻击者能够触发密文的解密过程,且能够知道密文的解密结果。

目标

在不知道解密算法和密钥的情况下,根据上面两个前提信息得到明文。

利用服务器响应

首先我们会向服务器提交密文,让它进行解密操作,再将解密结果与原本正确的明文进行比对。而提交的密文并不一定是可以正常解密的,根据CBC知识可知,密文必须满足与前一组密文相同长度才能够进行异或运算,才能正常解密。
服务器首先判断提交的密文能不能正常解密,第一步就是判断密文最后一组的填充值是否正确,也就是观察最后一组解密得到的结果的最后几位,如果错误将直接返回无法加密,如果正确,再将解密后的结果与服务器存储的结果比对,判断是不是正确的用户。也就是说服务器一共可能有三种判断结果:
1、密文不能正常解密,服务器内部抛出异常,返回 HTTP 500 Internal Server Error。如图:

《CBC  Padding Oracle Attack》
(画红圈的部分为0x3D,不符合填充规则,Padding的值只可能是0×01~0×08之间。)
2、密文可以正常解密但解密结果不对,返回 HTTP 200 OK,提示认证失败。如图:

《CBC  Padding Oracle Attack》
3、密文可以正常解密并且解密结果比对正确,返回 HTTP 200 OK,提示认证成功。如图:

《CBC  Padding Oracle Attack》
可以看到:第一种情况与第二、三种情况的返回值不一样。因此我们可以利用服务器的返回值判断我们提交的内容能不能正常解密,进一步讲,我们可以知道最后一组密文的填充位符不符合填充标准。(和SQL注入中的Blind Inject思想类似,这是一种称为边信道攻击(Side channel attack)的攻击方式)

核心

根据CBC加密原理,现在我们不知道解密的密钥key,但我们知道所有的密文,因此只要我们能够得到中间值便可以得到正确的明文(进行一次异或运算便可),而中间值是由服务器解密得到的,因此我们虽然不知道怎么解密但我们可以利用服务器帮我们解密,我们所要做的是能确定我们得到的中间值是正确的,这也是padding oracle attack的核心:找出正确的中间值。

过程

首先先明确好我们刚才提到的两个前提条件。
假定第一组明文只填充了一字节(也就是说假设第一组明文只有七位),对初始向量iv的最后一字节从0×00到0xff逐个赋值,并逐个向服务器提交iv,如果服务器返回值表示可以正常解密,这意味着构造后的iv的最后一位,与第一组的中间值的最后一位进行异或运算所得到的结果是0×01。
上述过程中,对于最后一字节(第8个字节),我们假设:原初始向量在这一位为iv1(已知前提中的iv),构造后的初始向量这一位为iv2(已知),第一组的cipher进行解密之后的中间值这一位为M(未知),原第一组明文这一位为p(未知,我们的目标就是想要知道它是多少)
那么易知:p = iv1 xor M
由于我们的cipher已知,也即cipher为固定值,那么解密之后的M也是不变的,所以有p = iv1 xor M = iv1 xor iv2 xor 0x01 ,因此我们就可以利用构造出来的iv1把p算出来了。
此时我们仅仅得到了第一组明文的最后一位,接下去我们假定第一组明文只填充了两位(也即假定第一组明文只有六位)。
根据前面的操作我们已经知道了中间值的最后一位的值M,那么接下来在对iv的构造前,我们可以先得到这个所要构造的iv的最后一位 = 0x02 xor M。接下来我们便把目光放在所要构造的iv的倒数第二位:
对iv的倒数第二位从0×00到0xff逐个赋值,并逐个向服务器提交iv,如果服务器返回值表示构造后的iv可以正常解密,这意味着构造后的iv,与第一组的中间值的倒数第二位进行异或运算结果是0×02。
同理通过公式我们就可以得知原来明文的倒数第二位的值。
对于倒数第三位、倒数第四位…都可以根据什么的过程去推断出来,最终便可以得到第一组明文全部值。
此时我们只得到第一组明文,对于第二组,我们所改变的”iv”就变成第一组的密文了,同样的操作同样的原理,最终就可以把全部明文推出来。

参考文章

1、我对padding oracle的理解
2、padding oracle attack 详细解析

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注