CBC
CBC全称Cipher Block Chaining模式(密文分组链接模式),每一个分组大小一般为128bits(16字节)
- 如果明文的长度不是16字节的整数倍,需要对最后一个分组进行填充(padding),CBC的填充规则有PKCS5和PKCS7的区别,这里使用的是PKCS7 ,即缺少N字节,就用 N 个
\xN
填充,如缺少7位则用 7 个\x07
填充,- 如果刚好是整数倍时,Padding一个整组的填充值
密文:(加密后可能有不可见字符,为了方便网络传输和适应不同系统的编码方案)
- 用 ASCII 十六进制表示,一个字节(2^8-1)用 0xMN 表示
- base64 表示
TIPS:CBC 字节翻转和 Padding Oracle Attack 攻击与具体的加密算法无关(分组),如 AES 等
加密
- 分组填充
- 生成初始向量IV(这里的初始向量如果未特定给出则随机生成)和密钥
- 将初始向量与第一组明文异或生成 middle_A
- 用密钥加密 middle_A 得到密文 Cipher_A
- 重复3 将密文 Cipher_A 与第二组明文异或生成 middle_B
- 重复4 用密钥加密密文Cipher_B
- 重复3-6 直到最后一组明文
- 将IV和加密后的密文拼接在一起,得到最终的密文(也可以不拼接)
解密
- 首先从最终的密文中提取出IV (IV为加密时指定的X位) //如果加密时没有加入IV则不用提取
- 将密文分组
- 使用密钥对第一组密文A解密得到 middle_A,然后用 IV 进行异或得到第一组明文
- 使用密钥对第二组密文解密得到 middle_B,然后用A与B进行异或得到第二组明文
- 重复3-4 直到最后一组密文
TIPS:
加密:plain –xor–> middle –AES–> cipher
解密:plain <–xor– middle <–AES– cipher
加密和解密过程中的 middle 并不相同
字节翻转攻击
Flipped Ciphertext Bits,解密过程中,先进行AES解密,再用 Cipher[N-1] 异或 Middle[N] ,得到 Plain[N],如果对 Cipher[N-1] 进行修改,就可以达到修改 Plain[N] 的目的
异或运算
1
2
3
4
5
6
7
8
9
10
11
12
13-1] ^ Middle[N] Cipher[N
Plain[N]
-1] ^ Middle[N] Modification_Cipher[N
Modification_Plain[N]
# A ^ B = C --> B ^ C = A
Middle[N] ^ Modification_Plain[N]
Modification_Cipher[N-1]
# Middle[N] 是中间值,不好获取,通过第一个等式可以得 Cipher[N-1] ^ Plain[N] == Middle[N]
-1] ^ Plain[N] ^ Modification_Plain[N] Cipher[N
Modification_Cipher[N-1]
iscc2018 Web300
1 |
|
题目逻辑:不允许admin账户登录,将 cookie 解密并且提取出username字段,为admin才能cat flag
思路:构造 admiN 登录生成,再通过修改cookie中的密文,实现CBC字节翻转,将username字段改为 admin
cookie分组
1
2
3
4a:2:{s:8:"userna
me";s:5:"admiN";
s:8:"password";s
:6:"123456";}翻转关系
1
2
3// Modification_Cipher[N-1] == Cipher[N-1] ^ Plain[N] ^ Modification_Plain[N]
// N在分组中的第14
$new_cipher[13] = chr(ord(13) ^ ord('N') ^ ord('n'))由于第一组的第14位修改了,所以解密之后是乱码,但是 iv 也是从 cookie 中获取,可以通过修改 iv
1
2
3
4
5# 上面推导的关系式:
Modification_Cipher[N-1] = Cipher[N-1] ^ Plain[N] ^ Modification_Plain[N]
# 根据算法,当N=1时,Cipher[N-1] 改为 iv
Error_Cipher = iv ^ Erroe_Plain ^ Right_Plain脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31#-*- coding:utf8 -*-
import base64
import urllib
# a:2:{s:8:"userna
# me";s:5:"admiN";
# s:8:"password";s
# :6:"123456";}
def Module1():
ciphertext = raw_input("Please input the first round cipher:\n")
cipher = base64.b64decode(urllib.unquote(ciphertext))
new_cipher = cipher[:13] + chr(ord(cipher[13]) ^ ord('N') ^ ord('n')) + cipher[14:]
print urllib.unquote(base64.b64encode(new_cipher))
def Module2():
errorcipher = base64.b64decode(urllib.unquote(raw_input('Please input errorcipher: \n')))
ivtext = raw_input("Please input iv:\n")
iv = base64.b64decode(urllib.unquote(ivtext))
cleartext = 'a:2:{s:8:"userna'
newiv = ''
for i in range(16):
newiv += chr(ord(iv[i]) ^ ord(errorcipher[i]) ^ ord(cleartext[i]))
print urllib.unquote(base64.b64encode(newiv))
option = raw_input("Please input option [1 or 2]:")
if option == '1':
Module1()
elif option == '2':
Module2()
else:
pass
Padding Oracle Attack
条件
- 攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量)
- 攻击者能够触发密文的解密过程,且能够知道密文的解密结果
- 能进行二值逻辑推理(即解密正确和解密错误的返回效果不同)
第一组密文的解密过程
1
2# Intermediary_Value 为中间值
Cipher[0] --DES--> Intermediary_Value --( ^ IV)--> Plain[0]padding 格式
根据上面的分组padding 规则,正确的 padding 格式:
1
2
30x01
0x02 0x02
0x03 0x03 0x03
attack 思路
每次发送一个分组,则解密时都会用到IV
爆破分组最后一个字节:
根据 New_IV[-1] ^ Intermediary_Value [-1] = 0x01 爆破
New_IV[-1] 从0x00-0xFF 进行爆破,其中只有一个值能满足与 Intermediary_Value[-1] 异或结果为 0x01,也仅有这种 padding 情况能被认为是正常解密(二值推理)
TIPS:IV长度与分组长度相同,可能为 16 Bytes,其他位全部置零即可
根据逻辑运算得到最后一字节的明文
1
2New_IV[-1] ^ Intermediary_Value [-1] = 0x01
=> Intermediary_Value [-1] = New_IV[-1] ^ 0x01将 Intermediary_Value [-1] 与 IV[-1] (第一个分组)或 前一个密文分组的最后一位(其他分组)异或可以得到 Cipher[-1]
爆破分组倒数第二个字节
构造 New_IV[-1] ^ Intermediary_Value [-1] = 0x02
上一步已经得到了Intermediary_Value [-1] 则
1
New_IV[-1] = Intermediary_Value [-1] ^ 0x02
根据 New_IV[-2] ^ Intermediary_Value [-2] = 0x02 爆破
与爆破最后一个字节的思路相同
防御
key:
应用程序对异常的处理:当提交的加密后的数据中出现错误的填充信息时,不够健壮的应用程序解密时报错,直接抛出”填充错误”异常信息(这个错误信息在不同的应用中是不同的体现,在Web一般是报500错误)
Web 应用中将 username 和 password 进行 CBC 加密后,和 IV 拼接作为用户标识 UID
服务端的返回结果
- 参数是一串正确的密文,分组、填充、加密都是对的(程序运行本身没出问题),包含的内容也是正确的(业务逻辑是对的),那么服务端解密、检测用户权限都没有问题,返回HTTP 200。
- 参数是一串错误的密文(包含不正确的bit填充),程序运行本身出现致命错误,那么服务端解密时就会抛出异常,返回HTTP 500 server error
- 参数是一串正确的密文(程序运行本身没出问题),包含的用户名是错误的(业务逻辑是错的),那么服务端解密之后检测权限不通过,但是依旧会返回HTTP 200戒者HTTP 302,而不是HTTP 500。
攻击者无需关心用户名是否正确,只需要提交错误的密文(因为这里有4中变量情况,为了构造出二值逻辑推理,我们要定住其中2个情况,即让业务逻辑恒错,对Bit Padding 的情况进行逻辑推理),根据HTTP Code即可做出攻击
Padding Oracle Attcak 并不是密码学算法本身的漏洞,但是当这种算法在实际生产环境中使用不当才会造成问题
防御思路:不让用户得知解密的结果是否符合Padding,如增加 try-catch 机制
题目
1 | #!/usr/bin/ruby -w |
代码逻辑
- 1选项:
输出经过aes-256-cbc加密的flag- 2选项:
提供你的IV和要加密的数据,返回加密后的密文- 3选项:
提供你的IV和要解密的数据,不返回解密明文,只返回解密成功是否
信息:
- 加密flag所采用的IV为16个字符
A
- 不能获取到加密flag所用的密钥
- 解密时IV与密文可控
- 解密是否成功
1 | from pwn import * |
参考
声明
- 博主初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,否则后果自负,与本人无关!
- 部分学习内容来自网络,回馈网络,如涉及版权问题,请联系删除 orz
Author: AMao
Link: https://passenger-amao.github.io/2020/09/22/CBC/
Copyright: 本站所有文章均采用 署名-非商业性使用-相同方式共享 4.0 国际(CC BY-NC-SA 4.0) 许可协议。转载请注明出处!