CBC-编码攻击

CBC反转攻击、Padding oracle原理

Posted by BY Diego on April 17, 2020

一、CBC 基础

CBC简介

CBC模式的全称是Cipher Block Chaining模式(密文分组链接模式),之所以叫这个名字,是因为密文分组像链条一样相互连接在一起。 在CBC模式中,首先将明文分组与前一个密文分组进行XOR运算,然后再进行加密。

CBC实现原理

这里用很经典的两张图,很形象的表示出来实现原理

加密过程

需要的值如下:

1.初始化向量IV 2.用来实现图中加密过程的密钥 3.需要加密的明文

JVgZHf.png

从图中很容易看出是属于分组加密方法,先将明文以每组八个字节或十六个字节分割(这里按照八字节说明,8个还是16个根据使用的加密算法决定,在php中 DES-CBC取8位 AES-128-CBC取16位),然后从左到右按图进行。

除了加密部分,以外操作基本是异或运算,所以要求 初始化向量IV、明文分组、密文分组字节数相同。 这里都为8字节。

这里举一个两个八字节异或运算的例子,abcdefgh 与 87654321 异或

#先取每个的第一个字符,转化为10进制 然后异或 再取16进制
hex(ord("a") ^ ord("1"))
#..........
# 依次取到最后一个
hex(ord("h") ^ ord("8"))
# 最后结果如下

0x59 0x55 0x50 0x51 0x51 0x55 0x55 0x59

从例子就可以看出所有分组长度必须相同,不同又该如何处理

如字符串”123456789abc”被分割为”12345678” 和 “9abc”, 而”9abc”显然不够8字节。CBC分组遵循PKCS#5标准,填充的字符为余下字节的个数。 继续引用图片 JVfySe.png

如果为16字节的话填充的范围为 0x01 - 0x10 看图可能就会有一个疑问,刚满8字节为何也要进行填充呢,正是这种即使恰好是8个字节也需要再补充字节的规定,可以让解密的数据很确定无误的移除多余的字节。

通过填补规则 字符串”123456789abc”被分割为”12345678” 和 “9abc”0x04 0x04 0x04 0x04

明文分组都被分割成 相同长度之后就可以进行加密了,还是拿123456789abc简单说明一下,

  • 1.首先用自己定义的初始化向量(”长度要相同如 aaabbbcc”)与明文xor运算
  • 2.运算的结果,用自己定义的密钥进行加密,加密的结果就是第一段密文分组
  • 3.然后用第一段的密文分组与第二段名文分组异或 运算结果继续用密钥进行加密 ,加密结果作为第二段密文分组,直到结束
  • 4.把密文拼在一起,最后就是加密的结果

解密

解密需要的值:

加密时用的密钥 加密时用的初始化向量 密文

先上图 JVonUI.png

解密过程: 先将密文进行分组,然后第一组密文用加密的密钥解密,结果与加密用的初始化向量异或得到第一段明文,剩下的与加密类似。

如果我们随便修改至密文或者iv的值 那么会对多端明文造成影响。 下面两个图就很形象的说明了这个问题

若某一段密文的某位出现问题影响如下 JZ9WFK.png

若某一段密文的某位丢失问题影响如下 JZCLu9.png

更多详细看 CBC模式解读

二、 php 实现简单的CEC加密

<?php
//加密
function encrypt($str , $key , $iv) {
    return @base64_encode(openssl_encrypt($str,"DES-CBC",$key, OPENSSL_RAW_DATA,$iv ));
}
//解密
function decrypt($encrypt , $key ,$iv){
    $encrypt = base64_decode(urldecode($encrypt));//解密的时候要先urldecode
    return @openssl_decrypt($encrypt, "DES-CBC", $key, OPENSSL_RAW_DATA, $iv);
}

$sercet_key = "sercet_key"; //加密解密用的密钥
$sercet_str = "admin"; // 待加密的字符串
$iv = "12345678";  // 初始化向量iv
$encrypt_str = encrypt($sercet_str,$sercet_key,$iv); //加密
$decrypt_str = decrypt($encrypt_str,$sercet_key,$iv); //解密
?>

JVHosU.png

base编码的目的时防止在传输过程出现问题。

三、CBC攻击

Padding Oracle Attack

目的 : 已知iv 和密文 根据服务器反应获取明文 因为Padding Oracle Attack是针对CBC链接模式的攻击,和具体的加密算法无关(分组)。这种漏洞不能算是密码学算法本身的漏洞,但是当这种算法在实际生产环境中使用不当就会造成问题。

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

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

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

主要理解一下什么情况下能正常解密,什么情况下不能。这里主要涉及填充部分内容。

比如我们的admin填充之后成为

admin0x03 0x03 0x03

解密时先判断填充符是否小于等于0x08(也可以是0x10根据协议)满足而且 为0x03 那么他就会从后往前寻找三个0x03,若存在就会正常解密,若不能则异常php里直接return false

还有下面这个例子 JZPGEq.png

Encrypted input : 密文段
Intermediary Value : 密文用密钥解密后的值 **称为中间值**
Initialization Vector : IV
Decrypted Value : Intermediary与IV异或后 得到明文

判断最后为0x04 往前找 再往前找3个0x04 符合则正常解密否则 错误 若我们篡改图中IV的0x6D 也能够正常解密,因为后面仍然满足

举一个简单的小例子说明一下

<?php

//加密
function encrypt($str , $key , $iv) {
    return @openssl_encrypt($str,"DES-CBC",$key, OPENSSL_RAW_DATA,$iv );
}
//解密
function decrypt($encrypt , $key ,$iv){
    //$strBin = base64_decode(urldecode($strBin));//解密的时候要先urldecode
    return @openssl_decrypt($encrypt, "DES-CBC", $key, OPENSSL_RAW_DATA, $iv);
}


$sercet_key = "sercet_key";
$sercet_str = "admin";
$iv = "12345678";
$encrypt_str = encrypt($sercet_str,$sercet_key,$iv);

echo base64_encode($encrypt_str)."<br>";
echo $iv."<br>";


if(decrypt(base64_decode($_GET["encrypt_str"]),$sercet_key,$_GET["iv"])===false){
	die("error");
}else{

	if (decrypt(base64_decode($_GET["encrypt_str"]),$sercet_key,$_GET["iv"]) == "admin") {
		die("okkk");
	}else{
		die("not admin");
	}
}
?>

一般在使用CBC时候 ,会给用户IV和密文 或者 IV跟明文,而加密部分的密钥是保存在服务器上的。

php代码生成密文为(16进制 无base加密)

密文 0x61 0xc4 0x48 0x22 0xe7 0x64 0x4b 0x29  //admin 只有5个字节生成的密文只有1段

iv(16 进制)

iv  0x31 0x32 0x33 0x6c 0x35 0x36 0x37 0x38

想要获取明文(假设只知道IV和密文),就必须要知道中间值(下图蓝色箭头),中间值xor初始化向量 = 明文分组

这里先列出我们想要求的中间值和明文,来帮助理解攻击过程(自己测试是知道明文的,用明文xor IV 得到的就是中间值)

mid 0x50 0x56 0x5e 0x5d 0x5b 0x35 0x34 0x3b
明文 a d m i n 0x03 0x03 0x03

这里需要通过修改iv 通过判断服务器反应 来确定中间值

我们先假设明文填充了一个字节,我们就可以通过修改iv让明文的末位为0x01其位不变,那么就有 iv的值

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x? #0x00 异或不影响对应位置的值

满足的式子

mid[8] ^ 修iv[8] = 0x01
mid[8] = 修iv[8] ^ 0x01

此时明文为

明文 a d m i n 0x03 0x03 0x01

明文符合条件正常解密,服务器返回正常,但认证失败。我们通过从0x00开始到0xff 遍历,第一个返回正常的即为 ”修iv[8]” 然后异或 1 得到 mid[8]的值,其他情况均解密失败(除去第二个成功的值,遍历时只需要第一个出现not admin的值)

测试时当iv为下值时 出现认证失败

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3a

0x3a 与 1异或 得到0x3b 所以 mid[8] = 0x3b 与我们已知的mid[8]相同

同样的道里,再假设填充两位,及

明文 a d m i n 0x03 0x02 0x02

此时iv 遍历倒数第二位

0x00 0x00 0x00 0x00 0x00 0x00 0x? 0x3b^0x02

得到

mid[7] = 修iv[7] ^ 0x02 = 0x36 ^ 0x02 = 0x34

最后得到mid的值

mid值与 iv 异或 及解密的明文 JZ82JU.png

当密文为多段时,从右往左依次推出

CBC 反转攻击

理解了Padding Oracle Attack,那么 反转就更好理解了 还是关注这个解密过程 但这时,我们是已知明文,想利用iv和密文,改变解密后的明文

举一个简单的例子 比如一个题目限制admin注册,但要求admin登入,信息用cbc加密,因为注册我们是知道自己的用户名(已知明文),然后密文和iv作为cookie被设置(得到了密文和iv)。 如何修改密文和iv 让明文变成admin 这就是cbc反转攻击的任务

还是使用这个例子

<?php
//加密
function encrypt($str , $key , $iv) {
    return @openssl_encrypt($str,"DES-CBC",$key, OPENSSL_RAW_DATA,$iv );
}
//解密
function decrypt($encrypt , $key ,$iv){
    return @openssl_decrypt($encrypt, "DES-CBC", $key, OPENSSL_RAW_DATA, $iv);
}

$sercet_key = "sercet_key";
$sercet_str = "{'username':'adm1n'}";
$iv = "12345678";
$encrypt_str = encrypt($sercet_str,$sercet_key,$iv);
?>

假如明文是 {“username”:”adm1n”} 目的是让明文变成 {“username”:”admin”}

我们只需要让1 反转成i即可 先把明文分组,每八位一组 1位于第三组的第一位

{"userna
me":"adm
1n"}

看一下解密流程 第三组明文所在的位置 JeaVKO.png

想让1变为i 通过修改第二段密文的第一位即可,

第三组的中间值的第一位 [3][1] ^ 第二组密文的第一位 [2][1] = ord("i") # 原来
#我们想修改达到如下效果
[3][1] ^ ()[2][1] = ord("i")
#通过公式很容易可得
()[2][1] = ord("i") ^ ord("1") ^ [2][1]

但问题在于修改密文第二段,经过密钥解密,会导致第二段明文乱码 JedQw4.png 图中很容易看出1成功被修改位i 但第二段已经乱码了,这样程序时无法识别的,因此我们要修复第二段明文 JewUCn.png

这里用上帝视角观察一下修复过程 修改第二段密文导致第二段明文部乱码(中间值发生改变) JeBeSI.png 因此要修复第二段明文(修复成原来的数值), 修复第二段就要修改第一段密文。

# 被修改后的中间值 就等于 乱码的明文 异或 第一段密文
()[2]  = ()[2] ^ [1]
()[1] = ()[2] ^ ()[2]

()[1] = ()[2] ^ ()[2] ^ [1]
# 代入数据
()[1] = [0x6d,0x65,0x27,0x3a,0x27,0x61,0x64,0x6d] ^ [0x34,0x5d,0xac,0x70,0xb0,0xa3,0xec,0xeb] ^ [0x73,0x9a,0x4d,0x81,0x84,0x71,0xa6,0xc8]
=[0x2a,0xa2, 0xc6, 0xcb, 0x13, 0xb3, 0x2e, 0x4e]

修改第一段密文结果 JesN6I.png 第二段成功被修复,同样,因为第一段密文被修改 导致第一段明文乱码,因此继续修复第一段明文,那么这里需要用同样的道理修改iv。

JeyfxA.png 成功修改为我们想要的内容

修改一个i需要改动的内容 Je6Dzj.png

修改的话可以用破神的脚本

# -*- coding: utf-8 -*-
import base64
plain = 'tjxwLb7ntUZzvvEfoiiKKG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6InNrY3RmIjt9'.decode('base64')
iv = 'in+Lglg80v0qJ2PW/c11pg=='.decode('base64')

old = plain[:16]
new = "a:2:{s:8:\"userna";
for i in xrange(16):
    iv = iv[:i] + chr(ord(iv[i]) ^ ord(old[i]) ^ ord(new[i])) + iv[i+1:]

print iv.encode('base64').strip()

总结一下实现反转需要的条件

1.能控制密文和iv
2.知道修改后的明文

四、实例

待更新

五、参考

Padding oracle attack详细解析

CBC模式解读

我对Padding Oracle攻击的分析和思考(详细)

padding oracle和cbc翻转攻击

CBC字节翻转攻击

CBC字节翻转攻击和Padding Oracle