CBC字节翻转攻击是web中一种常见的密码学题目,利用加解密过成程中的漏洞进行攻击。遇到过好几次CBC字节翻转的题目,padding Oracle是用来获取明文的,CBC翻转攻击是用来改变明文的,两者经常结合起来使用。参考大佬们的文章学习记录一下CBC字节翻转攻击和Padding oracle attack。

1、CBC字节翻转攻击

1、CBC模式

CBC模式是分组加密模式的一种,通常利用DES或者AES算法(两种分组密码算法)作为加密使用的算法。所谓分组加密就是按一定规则把明文分成一块一块的小组,DES分组长度是8字节,AES分组长度是16字节,每组长度一致,加密时是按组进行加密的。

CBC加密过程:

IV:初始化向量,用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。
plaintext:明文分组,即待加密的数据。
cipertext:密文分组,即加密后的密文。
key:用于异或后进行加密的密钥。
CBC工作于一个固定的比特组,称为一个块,即加密前分成的一块块小组。本文我们把16字节作为一个块,即16字节一个组。
CBC模式加密过程是:
1、将明文进行分组,每组长度一致(常见16字节或8字节,取决于利用的加密算法),对于长度不足的进行填充,填充的字符即为需要填充的字符的个数(十六进制表示)。
2、随机生成一个 Initialization Vector(IV),用于第一个明文分组的异或。
3、利用key对2中的结果进行加密,得到第一组明文的密文。
4、从第2个明文分组开始,先将明文分组与上一组的加密后的密文作异或运算,再用key将结果进行加密,得到该分组的密文。
5、最后将IV和这n块密文连在一起就得到CBC模式加密后的密文。
简单来说就是将每个明文分组与前一个密文分组进行XOR运算再通过key加密,其中第一组明文与初始化向量IV进行XOR运算得到第一组密文,最后将IV与得到的密文拼接得到最终的密文。
从加密的方式我们不难发现各组都是有关联的,这是与ECB得不同之处,这是与ECB模式比较相对比较安全的方面。
CBC模式的解密过程:
理解了加密,解密也容易理解,就是加密得逆过程:
1.首先按照字节长度将密文分组,其中密文的第一组就是初始的IV值,第二组密文对应第一组明文。
2.然后从第二组密文开始分别用key对密文进行解密,得到中间值。再进行异或操作,第二组解密后的值与初始IV值异或得到明文,第三组密文解密后的值与前一组的密文异或得到第二组明文,以此类推最后一组解密后的值与倒数第二组密文进行异或得到最后一组明文,将所有的明文连在一起便是最终的明文。
简单来说就是从密文中提取出IV,然后分组,每组密文先用key解密,然后再与上一组得密文XOR运算得到明文,其中第一组用key解密之后再和IV XOR运算得到明文。

2、CBC字节翻转攻击

从CBC模式的加解密原理我们不难发现 如果我们想改变解密后的某一明文,得到我们想得到得明文,如果是第一组,只需要改变IV,否则只需要改变上一组密文,就可以控制我们得到的明文。
如下图:
我们要改变1处想要得到特定明文,就可以修改2处的密文,这就是CBC字节翻转攻击的简单理解。

3、实现攻击

异或基础知识:

要实现利用这种攻击方式,首先我们要了解异或,运算规则就是把二进制数字进行异或运算,相同的得0,不同的得1,例如:

11100  XOR  10110 =01010
//其中十进制进行异或操作前先转化为二进制。

字符都有对应的ascii码值,对字符进行异或运算先将两串字符转化为对应的ascii值再进行异或,异或后再把ascii值转化为对应的字符。

A XOR B = C

A XOR C = B

A XOR B XOR C = 0

攻击原理:

假如原明文为old_plaintext,原IV为old_IV,那么加密前的中间值为middlevalue:

middlevalue = old_plaintext XOR old_IV

middlevalue再经过加密算法得到old_cipher。解密时先经过加密算法解密得到middlevalue,再进行异或:

old_plaintext = middlevalue XOR old_IV

由解密过程可知我们要想得到一个new_plaintext,就需要构造一个new_IV,而new_IV的构造利用:

new_IV = middlevalue XOR new_plaintext

可知只要我们知道了middlevalue就可以就可以构造出new_IV,用于解密便可得到new_plaintext,因为

middlevalue = old_plaintext XOR old_IV

所以我们知道了old_plaintext和old_IV就可以得到midllevalue,就可以构造出new_IV,解密得到我们想要的new_plaintext。

已知明文old_plaintext和old_IV可以构造new_IV来改变明文为目标明文new_plaintext。(这里只能改变第一个分组)

附上大佬的py脚本演示cbc字节翻转攻击:

#python 3
import os
import codecs
from Crypto.Cipher import AES
from Crypto import Random
SECRET_KEY=codecs.encode(os.urandom(8),'hex_codec').upper() #16byte密钥
IV=Random.new().read(16)    #16byte初始向量

old_plaintext='hello,world'
aes = AES.new(SECRET_KEY,AES.MODE_CBC,IV)
length = 16
count = len(old_plaintext)
add = length - (count % length)
old_plaintext = old_plaintext + (chr(0) * add) #正常情况下这里应该是补\x05 5由16-11得来,但由于我省事不想处理结果后面出现的乱码,就省事改为\x00了
cipher=aes.encrypt(old_plaintext)
print("明文为:",old_plaintext)
print("秘钥为:",SECRET_KEY)
print("IV为:",IV)
# print("明文序列为:",list(old_plaintext))
print("密文为:",aes.encrypt(old_plaintext))

old_IVList=[]
old_plaintextList=[]
for i in range(0,len(codecs.encode(IV,'hex_codec').decode()),2):
    old_IVList.append(int(codecs.encode(IV,'hex_codec').decode()[i:i+2],16))
new_IVList=old_IVList
for i in list(old_plaintext):
    old_plaintextList.append(ord(i))

new_IVList[9]=old_plaintextList[9]^old_IVList[9]^0  #让第10位消失
new_IVList[10]=old_plaintextList[10]^old_IVList[10]^ord('D')  #让解出来的明文第11位为D
new_IVList[11]=old_plaintextList[11]^old_IVList[11]^ord('!')  #再加个!
new_IV=''

for i in new_IVList:
    if len(hex(i)[2:])==1:  #为了保证构造出来的IV是16byte,1位的16进制补0变成2位
        new_IV+='0'+hex(i)[2:]
    else:
        new_IV += hex(i)[2:]
new_IV=codecs.decode(new_IV,'hex_codec')  #恶意构造的16byte IV
print("恶意构造出的IV:",new_IV)
aes = AES.new(SECRET_KEY,AES.MODE_CBC,new_IV)
dsc=aes.decrypt(cipher).decode()
print("利用恶意构造的IV结出来的明文:",dsc)

#这是在知道明文和IV的情况下并可以提交我们构造的IV的情况下,我们可改变第一个明文分组的值

2、Padding oracle attack

1、PKCS #5填充规则

既然CBC模式涉及到分组,那么就一定存在不能恰好被平均分组的情况,也就是说最后一组的长度可能不够长,这时候就需要对最后一组分组进行填充,使其和其他分组保持长度一致,这时候就需要了解一下填充所要遵循的规则了。对于采用DES算法进行加密的内容,填充规则遵循的是PKCS #5,而AES则是PKCS #7,实际上两者的要求基本一样,区别在于PKCS #5填充是八字节分组而PKCS #7是十六字节,换句话说就是填充的最大位数不一样,一个是0到8一个是0到16。主要说一下PKCS #5规则。

为了保证每一组的长度一致,做法是在最后一个分组后填充一个固定的值,这个值的大小为填充的字节总数(十六进制表示)。例如最后还差4个字符,则填充四个0×04在最后,对于PKCS #5最多填充八位也就是八个0×08,所以填充字节的取值范围是0×01到0×08。需注意即便分组内容能正好平均分为n组,仍需要在最后一组后面填充一个八位分组,如下图所示:

以上,便是对PKCS #5的简要介绍,16字节的AES采用的是PKCS #7,只是分组长度不一样,也就是说填充字节的取值范围是0×00到0×10,填充的规则基本一样。

2、Padding oracle attack攻击原理

首先我们了解一下使用CBC模式加密敏感信息的服务器是怎么处理我们提交的内容的。假设我们向服务器提交了正确的密码,我们的密码在经过CBC模式加密后传给了服务器,这时服务器会对我们传来的信息尝试解密,如果可以正常解密会返回一个值表示正确,如果不能正常解密则会返回一个值表示错误。而事实上,判断提交的密文能不能正常解密,第一步就是判断密文最后一组的填充值是否正确,也就是观察最后一组解密得到的结果的最后几位,如果错误将直接返回错误,如果正确,再将解密后的结果与服务器存储的结果比对,判断是不是正确的用户。即服务器对解密后数据Padding规则的校验。若不符合Padding规则,则返回500.其它,返回200,是一种边信道攻击方式。服务器一共可能有三种判断结果:

1.密文不能正常解密;
2.密文可以正常解密但解密结果不对;
3.密文可以正常解密并且解密结果比对正确;

其中第一种情况与第二 三种情况的返回值一定不一样,这就给了我们可乘之机——我们可以利用服务器的返回值判断我们提交的内容能不能正常解密,进一步讲,我们可以知道最后一组密文的填充位符不符合填充标准。

如上图所示,明文填充了四位时,如果最后一组密文解密后的结果(Intermediary Value也就是中间值)与前一组密文(Initialization Vector也就是IV值)异或得到的最后四位是0×04,那么服务器就会返回可以正常解密。

回忆一下前面我们说过的CBC模式的解密过程,第n组密文解密后的中间值与前一组的密文异或便可得到明文,现在我们不知道解密的密钥key,但我们知道所有的密文,因此只要我们能够得到中间值便可以得到正确的密文(进行一次异或运算便可),而中间值是由服务器解密得到的,因此我们虽然不知道怎么解密但我们可以利用服务器帮我们解密,我们所要做的是能确定我们得到的中间值是正确的,这也是padding oracle attack的核心——找出正确的中间值。服务器会根据我们提交的密文能否正确解密给我们返回不同的值,这里就有一个可以利用的逻辑判断,判断的标准是最后几位的填充值是否符合标准,再回忆一下前面说的PKCS #5填充和CBC模式的解密过程,我们不难理解下面的攻击过程:

(1)假设我们捕获到了传输的密文并且我们知道是CBC模式采用的什么加密算法,我们把密文按照加密算法的要求分好组,然后对
倒数第二组密文进行构造; (2)先假定明文只填充了一字节,对倒数第二组密文的最后一字节从0×00到0xff逐个赋值并逐个向服务器提交,直到服务返回值表
示构造后的密文可以正常解密,这意味着构造后的密文作为中间值(图中黄色的那一行)解密最后一组明文,明文的最后一位是0×01
(如图所示),也就是说构造的倒数第二组密文的最后一字节与最后一组密文对应中间值(绿色的那一行)的最后一位相异或的结果
是0×01;

(3)利用异或运算的性质,我们把我们构造的密文的最后一字节与0×01异或便可得到最后一位密文解密后的中间值是什么,这里我们设为M1,这一过程其实就是对应下图CBC解密过程中红圈圈出来的地方,1就是我们想要得到的中间值,二就是我们构造的密文也就是最后一组密文的IV值,我们已经知道了plaintext的最后一字节是0×01,从图中可以看到它是由我们构造的IV值与中间值的最后一字节异或得到的;

(4)再假定明文填充了两字节也就是明文最后两字节是0×02,接着构造倒数第二组密文,我们把M1与0×02异或可以得到填充两字节时密文的最后一位应该是什么,这时候我们只需要对倒数第二位进行不断地赋值尝试(也是从0×00到0xff),当服务器返回值表示可以正常解密时,我们把此时的倒数第二位密文的取值与0×02异或便可得到最后一组密文倒数第二字节对应的中间值;

(5)后再构造出倒数第三倒数第四直到得到最后一组密文的中间值,把这个中间值与截获的密文的倒数第二位异或便可得到最后一组分组的明文;

(6)舍弃掉最后一组密文,只提交第一组到倒数第二组密文,通过构造倒数第三组密文得到倒数第二组密文的明文,最后我们便可以得到全部的明文。

3、小结

Padding oracle attack的关键思路就在于通过构造IV值(也就是前一组密文)利用服务器的返回值结合PKCS #5来找到正确的中间值,从而绕过加密算法,直接得到解密后的内容。

其实就是通过服务器的返回值判断自己构建的IV值对不对,进而猜测出正确的中间值Intermediary Value,中间值再和获取到的原本的IV值异或便可得到明文,因此攻击成立的两个重要假设前提:

(1)  攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量)

(2)  攻击者能够触发密文的解密过程,且能够知道密文的解密结果

能够获得密文我们才能得到正确的明文,而密文的第一组分组需要有正确的初始化向量才能解密。能触发密文的解密过程才能在不知道密钥的情况下绕过加密算法直接得到我们需要的结果,而知道密文的解密结果是通过服务器返回值来判断结果是不是符合填充标准来实现的,例如符合填充一位的话解密结果就是0×01,而我们正是通过把自己构造的IV值与解密结果异或才得到正确的中间值进而得到明文的。

3、总结

有关CBC字节翻转攻击和Padding oracle attack的基础内容大概就这些,CBC字节翻转攻击用于得到可控的密文解密后的明文,Padding oracle 攻击主要用在当cbc模式中old_plaintext未知的情况下使用,这个时候我们就无法根据公式来得到我们需要的new_IV了,Padding oracle的主要过程是通过对padding 的过程中的报错信息来穷举爆破middlevalie。比较难理解,关键还是在于实践中去理解。

参考文献:

https://www.liuxianglai.top/?p=96

http://www.freebuf.com/articles/database/151167.html

http://n3k0sec.top/2018/01/14/Padding-Orlace/

http://www.blogsir.com.cn/safe/498.html

发表评论

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