CBC字节翻转攻击

CBC字节翻转攻击

CBC模式简介

现代密码中的加密体制一般分为对称加密体制(Symmetric Key Encryption)和非对称加密体制(Asymmetric Key Encryption)。对称加密又分为分组加密和序列密码。
分组密码,也叫块密码;序列密码,也叫流密码
而CBC模式,属于块密码的一种,为密码分组链接模式
模式中每一个分组要先和前一个分组加密后的数据进行XOR异或操作,然后再进行加密。
CBC模式是一种最常用的加密模式,它主要缺点是加密是连续的,不能并行处理,并且与ECB一样消息块必须填充到块大小的整倍数。

成因

刚刚只是简述,要了解成因得先理解解密,要理解解密,先好好认识加密逻辑

加密


我们可以看到,把数据分为若干明文块之后,位数不足的使用特殊字符填充
导入随机化初始向量与一个密钥
初始向量与块1进行异或,再加入key值进行加密
将生成的密文1,继续重复初始操作,以此往复,得到最后的完整密文

解密

将密文分为若干块,位数不足用字符填充
同样的初始向量与密钥
第一块解密之后,与初始向量进行异或,得到明文1
再将第一块密文作为下一块密文解密的初始向量

1
2
3
4
5
6
7


(加密的逆过程)

设明文为X,密文为Y,解密函数为k。

X[i] = k(Y[i]) Xor Y[i-1]

攻击原理

核心原理:通过破坏一个比特的密文来篡改一个比特的明文
举个例子,如果我们改变第一块密文里的一个比特,那么它和下一块密文解密后的数据异或生成的第二块明文就会改变一个比特,但是改变了第一块密文之后哪怕是一个比特,经过解密函数也会变一个样子,这个时候就需要我们也能控制初始化向量,这样尽量维持第一块明文不变,这样,成功翻转一个比特的内容
而我们知道,在CBC模式中,分组长度必须是128Byte,一个字符为8byte,这样一块密文对应着16个字符
为什么我们会特地来学习CBC字节翻转?
我们要知道,在实际的鉴权系统中,假如运用了CBC,那么我们可以控制一个字符的内容
比如我们的身份是{"user":"Admin",但是我们想要变成{"user":"admin"},那么我们就可以通过CBC字节翻转来实现,把A变成a,因为他们只有一个比特的差异
但是我们知道如果我们控制的字符在第一块明文(第一组16个字符),我们可以通过控制初始向量来控制第一块明文
但是如果控制的字符在n>=2块时,我们就不能通过控制初始向量来控制明文了,因为初始向量只对第一块明文有效
这时候我们就需要通过控制第n-1块密文来控制第n块明文了
但是,如果我们改变n-1块密文来修改第n块明文,确实可以修改第n块明文,但是,第n-1块明文将会沦为完全的乱码,因为哪怕改变一个比特的第n-1块密文,解密后也会有个极其大的改变

数学实现

了解了基本原理之后,我们来看具体如何调整第n块密文(或者初始向量)

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
设第n-1块密文为C[n-1],第n块密文为C[n],第n块明文为P[n],第n-1块明文为P[n-1],初始向量为vl,有@encrypt函数与@decrypt函数   
假设明文={"user":"Admin","password":"1234566666","role":"test"}
P={"user":"Admin","password":"1234566666","role":"test"}
C=@encrypt(P)
P[1]={"user":"Admin",
令Q={"user":"admin",
设函数magic把字符转化为二进制
我们需要改变Admin->为admin,但是不知道key,因此要字节翻转
知我们要改变第十个字符,即P[1][9],说明第一块明文偏移量为9的字符
得到公式
vl'=vl^magic(C[1])^magic(Q)
具体到某个字符就是
vl[9] = chr(ord(C[1][9]) ^ ord("a") ^ ord ("A"));
而对于这个公式需要经历一个异或证明
设攻击者希望将 Pi​ 改为 Pi′。当前解密过程为:
Pi=Ii⊕Ci−1
攻击者修改 Ci−1 为 Ci−1′,则解密后得到:
Pi′=Ii⊕Ci−1′
Pi′​=Ii​⊕Ci−1′​
攻击者希望 Pi′ 是特定值,因此:
Pi′=Ii⊕Ci−1′

由于 Ii​ 是解密 Ci​ 的结果,而攻击者不修改 Ci​(只修改 Ci−1​),所以 Ii​ 保持不变。因此,我们可以写出:
Pi=Ii⊕Ci−1
Pi′=Ii⊕Ci−1′
将两式异或:
Pi⊕Pi′=(Ii⊕Ci−1)⊕(Ii⊕Ci−1′)=Ci−1⊕Ci−1′

因此:
Ci−1′=Ci−1⊕Pi⊕Pi′

应用

不止时在鉴权系统上可以通过字节翻转获取权限用户,在特定情形比如php反序列化也可以通过字节翻转来获取注入恶意字符
比如这一道题

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

define('MY_AES_KEY', "abcdef0123456789");
define('MY_HMAC_KEY', "1234567890123456");
#define("FLAG","CENSORED");

function aes($data, $encrypt) {
$aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($aes), MCRYPT_RAND);
$iv = "1234567891234567";
mcrypt_generic_init($aes, MY_AES_KEY, $iv);
return $encrypt ? mcrypt_generic($aes, $data) : mdecrypt_generic($aes, $data);
}

define('MY_MAC_LEN', 40);

function hmac($data) {
return hash_hmac('sha1', $data, MY_HMAC_KEY);
}

function encrypt($data) {
return aes($data . hmac($data), true);
}

function decrypt($data) {
$data = rtrim(aes($data, false), "\0");
$mac = substr($data, -MY_MAC_LEN);
$data = substr($data, 0, -MY_MAC_LEN);
return hmac($data) === $mac ? $data : null;
}

$settings = array();

if (@$_COOKIE['settings']) {
echo @decrypt(base64_decode($_COOKIE['settings']));
$settings = unserialize(@decrypt(base64_decode($_COOKIE['settings'])));
}

if (@$_POST['name'] && is_string($_POST['name']) && strlen($_POST['name']) < 200) {
$settings = array(
'name' => $_POST['name'],
'greeting' => ('echo ' . escapeshellarg("Hello {$_POST['name']}!")),
);
setcookie('settings', base64_encode(@encrypt(serialize($settings))));
}

if (@$settings['greeting']) {
passthru($settings['greeting']);
} else {
echo "</pre>
<form action=\"\" method=\"POST\">
What is your name?
<input type=\"text\" name=\"name\" />
<input type=\"submit\" name=\"submit\" value=\"Submit\" />
</form>
<pre>";
}
?>

我们可以提交一个name,目的是为了改变greeting的值,闭合引号逃出echo,方可命令执行,但是无法绕过escapeshellarg函数,可以通过字节翻转来绕过,注意的是,反序列化时,尽量把乱码内容控制在字符串内,即得填充无用字符
比如我们提交的name为

1
name = 'z'*17 + 'X' + ';cat *;#' + 'z'*16

翻转攻击

1
2
3
pos = ?;//找到字符偏移量
val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))
exploit = cookie[0:pos] + val + cookie[pos + 1:]

最终成功逃逸命令执行

后记

CBC字节翻转攻击,虽然看起来很简单,但是在实际的攻击或者做题中,还是非常实用的,尤其是对于鉴权系统和php反序列化等场景
类似的这种思想,也体现在md5长度拓展攻击上,学无止境