CBC字节翻转攻击

CBC加密流程

首先将明文分组,常见的以16个字节为一组,位数不足的使用特殊字符填充——>生成一个随机的初始化向量(IV)和一个密钥——>将IV和第一组明文异或——>用密钥对3中XOR后产生的密文加密——>用4中产生的密文对第二组明文进行XOR操作——>用密钥对5中产生的密文加密——>c重复步骤4-7,到最后一组明文——>将IV和加密后的密文拼接在一起,得到最终的密文。

img Plaintext:待加密的数据——也就是明文 IV:初始化向量,用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。 Ciphertext:加密后的数据——也就是密文 Key:密钥

CBC解密过程

img

从密文中提取出IV,然后将密文分组——>使用密钥对第一组的密文解密,然后和IV进行xor得到明文——>使用密钥对第二组密文解密,然后和2中的密文xor得到明文——>重复2-3,直到最后一组密文

攻击原理

Ciphertext-N-1(密文-N-1)是用来产生下一块明文;这就是字节翻转攻击开始发挥作用的地方。如果我们改变Ciphertext-N-1(密文-N-1)的一个字节,然后与下一个解密后的组块异或,我们就可以得到一个不同的明文了。
img
注意在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。

参考:https://maplege.github.io/2018/11/19/CBC-reverse/

异或运算

1
2
3
已知A=B^C
可以得到结论B=A^C
并且也能得到A^C^B=0

攻击核心

1
2
3
4
设第一块的密文第4个字节设为变量A,第二块的明文中第4个字节设为变量C,在第二块加密产生的第4个字节设为变量B
因为A^B=C,根据结论有B=A^C
如果人为将A变量值改变为A^C的结果,那么参与运算后A^B将等于A^C^B=B^B=0,第二个产生的明文的第4个字节将变为0
如果将A变量值改变为A^C^X(这里X是任意字符),那么参与运算后,A^B将等于A^C^X^B=B^X^B=X,第二个产生的明文的第4个字节将变为X字符,这样,第二块密文块解密的结果就可控了

实践

首先先扫一下目录,看看有没有源码
img
可以看到有一个有文件,打开以后发现,由于不管怎么打开都是乱码,所以直接复制了网上wp的源码:

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
//设置cookie的流程调用的函数,返回一个随机的iv和使用该iv加密的post提交的username和password的结果——cipher
function login($info){
$iv = get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}

//检查函数,这里是对cookie中cipher和iv进行CBC翻转的利用点
function check_login(){
//如果cookie中设置了cipher和iv参数
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
//将cipher和iv参数都进行base64解码
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
//进行CBC模式的AES解密
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
//对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}
//根据session中username参数,控制显示结果
function show_homepage(){
//如果session中的username为admin,则返回flag
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is $flag</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
}
//检查是否带有username和password参数,如果有参数,进入设置cookie的路径
//如果没有设置参数,进入判断cookie路径
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
//不允许提交的username为admin
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
//如果session中的username字段已经存在了,既登陆过了,则进入
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
.................
}

上面说,不让我们直接提交admin,但是又说结果需要admin,联想到上面的攻击原理,改变解码中的某个字节,指挥影响该段明文,以及下一段明文对应的字节,也就是说我们要构造xdmin,或者axmmin,然后通过CBC字节翻转攻击,是的x还原为a或d。。。

攻击过程

代码中涉及到了序列化,数组序列化结果参考如下(这里使用ydmin和123作为username和password):
a:2:{s:8:"username";s:5:"ydmin";s:8:"password";s:3:"123"}
到这里,算是大致理解这个CBC翻转字节攻击是如何实现的了,首先我们知道前一段密文会影响后一段明文的解密,所以我们就对第一段a:2:{s:8:"userna的密文进行修改,使得在于第二段密文异或运算时ydmin变成admin
这个时候,我们将修改后的密文直接拿去也是不行的,因为这个时候IV没有更换,前面的数据会出现匹配乱码
我们知道 第一节明文=IV^第一节密文
所以 新IV=第一节明文^第一节密文(修改之前的,因为修改之前的密文所对应的明文才是正确的)

编写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import base64
import urllib

cipher="ut%2F2iZbrbm1dq9PpRb0dUGN5xneMrTkNo1pMCTL6DjhABvPsODND9N%2BLwENXWiXXPyy5PQ7YXn2HCfcIkYNAIw%3D%3D"
iv="Ub5Pn6gmMNuctLwfJggaOA%3D%3D"

cipher_de=base64.b64decode(urllib.unquote(cipher))
tran='a:2:{s:8:"username";s:5:"bdmin";s:d";s:3:"123"}8:"passwor'

#tran[16:32]=me";s:5:"bdmin";
# 这里用之前的攻击核心的字母来指代一下 A ^ c ^ x
cipher_new=cipher_de[0:9]+chr(ord(cipher_de[9])^ord('b')^ord('a'))+cipher_de[10:]
cipher_new=urllib.quote(base64.b64encode(cipher_new))
print(cipher_new)
cipher_new=base64.b64decode('wXAR7J/AKHmukMZ9U0lgO21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==')#这里记得用burp跑出来的那个,不然待会又解码不吻合。
print(cipher_new)
iv_raw=base64.b64decode(urllib.unquote(iv))
iv_new=''
for i in range(0,16):
#
iv_new+=chr(ord(tran[i])^ord(iv_raw[i])^ord(cipher_new[i]))
iv_new=urllib.quote(base64.b64encode(iv_new))
print(iv_new)

img 最后也终于是拿到了flag

小结

一开始做的时候,就看不下去了,因为感觉很陌生,而且又感觉很绕,但其实只要专注看一会,其实很快就可以理解了
卡了比较久的地方是,删除下面post 的内容的时候,把那一行删掉就行了,不要不要把那一行上面那行空白的也删掉,这样是跑不出来的,每次都手贱直接整个删掉,然后就好久都没出来结果,无语了。。
参考:https://blog.csdn.net/u013577244/article/details/86310881?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant.no

https://www.jianshu.com/p/a958f757a2b2

Author

vague huang

Posted on

2021-02-05

Updated on

2021-02-07

Licensed under

Comments