CBC字节翻转攻击

CBC字节翻转攻击

简介

CBC加密是AES加密的一种模式,中文名叫密码分组链接模式(Cipher Block Chaining (CBC)),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
看下图:
IV:随机生成的初始向量
Plaintxt:明文数据
Ciphertext:密文数据
Key:分组加密使用的密钥

加密过程

image

首先将我们需要将需要加密的明文按照十六个字节为一组分组,最后一组不满十六字节用特殊字符填补
接着系统产生一个十六字节的随机字符串作为初始向量,该向量与第一组明文进行异或操作,再与key进行CBC加密得到第一组密文
第一组密文再与下一组明文进行异或操作CBC加密得到第二组密文
以此类推……
将得到的密文组按顺序拼接到一起就是所得到的密文

解密过程

image
首先将密文按十六进制分组
第一组密文与key进行CBC解密,再将解密的数据与初始向量异或得到第一组明文
接着第一组密文再与下一组CBC解密得到的数据进行异或操作得到第二组明文
以此类推……
最后拼接得到明文

异或操作

CBC字节翻转的关键点就在异或上,所以首先我们要明白异或是什么。
image
当我们的一个值C是由A和B异或得到
C = A XOR B
那么
A XOR B XOR C很明显是=0的
当我们知道B和C之后,想要得到A的值也很容易
A = B XOR C
因此,A XOR B XOR C等于0。有了这个公式,我们可以在XOR运算的末尾处设置我们自己的值,即可改变。

攻击过程

image
攻击针对的是解密过程
第一步:修改

由图可知,你在密文中改变的字节,只会影响到在下一明文当中,具有相同偏移量的字节。

所以我们要找到要修改的明文中的那一个字节C,我,上一组密文对应字节为A,本组密文对应字节为B,我们知道了A xor B = C,我们现在现在要将C改变为c,那么知道A XOR B XOR C = 0,则A XOR B XOR C xor c= c。

这里我们知道B是解密后的数据未知我们不好修改,所以我们可以将A修改a=A xor C xor c ,这样我们就将C替换成了c。

第二步:修复

上一步我们将第二组明文修改为了我们想要得到的数据,但是,与此同时,我们也将第一组密文给修改了,这就会导致第一组明文数据被修改,那么我们不能去修改第一组密文,又要使第一组明文数据正确,我们只有利用异或对初始向量下手。

若原iv为O,新iv为N,错误的第一组明文M = O xor 第一组密文,我们要想得到正确的第一组明文m,那么就去改变N = O xor M xor m,这样就利用异或得到了正确的明文。

看个例子

这是iscc的一道题Only admin can see flag
image
查看源码发现一个TXT文件,打开得到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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
//设置随机初始向量
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}

//设置cookie的流程调用的函数,返回一个随机的iv和使用该iv加密的post提交的username和password的结果——cipher
function login($info){
$iv = get_random_iv();
//序列化传入数组
#a:2:{s:8:"username";s:5:"Admin";s:8:"password";s:4:"test";}
#第一组明文:a:2:{s:8:"userna
#第二组明文:me";s:5:"Admin";
#第三组明文:s:8:"password";s
#第四组明文::4:"test";}
$plain = serialize($info);
//cbc加密
$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(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$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参数,控制显示结果
//如果没有设置参数,进入判断cookie路径
function show_homepage(){
//session要为admin
if ($_SESSION["username"]==='admin'){
echo $flag;
}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>';
}
//入口
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
//post传参不能为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{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
echo '<body class="login-body">
<div id="wrapper">
<div class="user-icon"></div>
<div class="pass-icon"></div>
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>Fill out the form below to login to my super awesome imaginary control panel.</span>
</div>
<div class="content">
<input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />
<input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<input type="submit" name="submit" value="Login" class="button" />
</div>
</form>
</div>
</body>';
}
}
?>

我已经在其中做出详细的注释
首先从入口开始验证post是否传参username和password,这里要求传入的用户名不能为admin。

1
2
3
4
5
6
7
8
9
10
11
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
//post传参不能为admin
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}

接着对传入参数序列化,接着对起进行一次CBC加密,得到了COOKIE值iv和cipher,以及session值username,对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值,但是这里又要传入的username参数为admin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$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!");
}
}
}

所以我们利用CBC翻转字节,传入Admin绕过过滤,再在加密过程中将A翻转为a,通过验证。
修改代码如下:

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
import urllib,base64,requests,re

url = "http://*.*.*.*/index.php"
datas = {
"username" : "Admin",
"password" : "test"
}
#第一组明文:a:2:{s:8:"userna
#第二组明文:me";s:5:"admin";
#第三组明文:s:8:"password";s
#第四组明文::4:"test";}
#修改过程
r = requests.post(url,data=datas)
cipher = r.cookies.get("cipher")#获取初始密文
cipher = base64.b64decode(urllib.unquote(cipher))
offset = 9
new_cipher = cipher[:offset] + chr(ord(cipher[offset])^ord("A")^ord("a")) + cipher[offset+1:]#字节翻转
new_cookies = requests.utils.dict_from_cookiejar(r.cookies)
new_cookies["cipher"] = urllib.quote_plus(base64.b64encode(new_cipher))
#修复过程
r2 = requests.get(url,cookies=new_cookies)
#获得损坏的第一段明文
plain = base64.b64decode(re.findall("decode('(.*)')",r2.text)[0])
iv = base64.b64decode(urllib.unquote(new_cookies["iv"]))
old = plain[:len(iv)]
new = 'a:2:{s:8:"userna'
new_iv = "".join([chr(ord(iv[i])^ord(old[i])^ord(new[i])) for i in xrange(16)])
new_cookies["iv"] = urllib.quote_plus(base64.b64encode(new_iv))

r3 = requests.get(url,cookies=new_cookies)
print(r3.text)

最后将修改的iv和cipher设为cookies得到flag

CBC翻转字节攻击属于密码学的题目,关键是理解加密解密过程和异或操作,后面还遇到了翻转攻击与SQL注入的结合题型,值得继续了解。。。

文章目录
  1. 1. CBC字节翻转攻击
    1. 1.1. 简介
    2. 1.2. 加密过程
    3. 1.3. 解密过程
    4. 1.4. 异或操作
    5. 1.5. 攻击过程
    6. 1.6. 看个例子
,