CBC反转攻击详解

0x00 源码

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login Form</title>
<link href="static/css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="static/js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$(".username").focus(function() {
$(".user-icon").css("left","-48px");
});
$(".username").blur(function() {
$(".user-icon").css("left","0px");
});

$(".password").focus(function() {
$(".pass-icon").css("left","-48px");
});
$(".password").blur(function() {
$(".pass-icon").css("left","0px");
});
});
</script>
</head>

<?php
define("SECRET_KEY", '9999999999999999');
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;
}

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));
}

function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is ctf{123cbcchange}</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>';
}

if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
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>';
}
}
?>
</html>

0x01 AES-CBC 加密、解密过程

加密过程

Plaintext:明文数据

IV:初始向量

Key:分组加密使用的密钥

Ciphertext:密文数据

明文都是先与混淆数据(第一组是与IV,之后都是与前一组的密文)进行异或,再执行分组加密的。

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

解密过程:

每组解密时,先进行分组加密算法的解密,然后与前一组的密文进行异或才是最初的明文。

对于第一组则是与IV进行异或。

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

0x02 攻击

异或的特性:

异或是不进位加法

1
2
3
1⊕1=0  //这里1+1=10 进位了,不需要管他
1⊕0=1
0⊕1=1

连续异或两次同一个内容,就相当于没有异或

比如:
1100110 ⊕1010100⊕1010100 = 1100110

而异或还有交换律,可以随意打乱顺序

1010100 ⊕ 1100110 ⊕1010100 = 1100110

1.对于解密时:

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

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

2.字节翻转

要改变Cipher1[0]的值为x

'a' ^ x = decrypt(cipher2[0])

而又因为:Plain2[0] ^ Cipher1[0]=decrypt(cipher2[0])

decrypt(cipher2[0]) 代入第一个式子得

'a' ^ x = Plain2[0] ^ Cipher1[0]

解得x= Plain2[0] ^ Cipher1[0] ^ 'a'

x是我们要赋值的,也就是Cipher1[0] ,所以最后写出的语句为:

Cipher1[0]=Plain2[0] ^ Cipher1[0] ^ 'a'

Plain2[0]是原明文组2的第一个字节(已知),Cipher1[0]是第一组密文的第一个字节(已知)

此时再把Cipher1和Cipher2连接起来生成Ciphertext,把C,就会成功变成我们想要的明文。

3.修复IV:

当我们破坏掉密文的第一组时,同样明文的第一组在解密的时候就并不是原来的明文了,这个时候我们需要修复初始向量IV,给它一个新的值,

使NewIv ^ NewCipher1 = Plain1(原)

推导过程:

Plain1(损坏) ^ iv = decrypt(newCipher1)

Newiv=Plain1(原) ^ decrypt(newCipher1) (目的是要给Newiv赋值)

把第一个式子代入到第二个式子中得到:(此时Plain(原) 已知,Plain1(损坏)已知)

Newiv=Plain1(原) ^ Plain1(损坏) ^ iv

有了Newiv和翻转好的Ciphertext,这时网站后端解密后的明文就是我们构造好的明文了。

0x03 例题

只有admin才能看flag,而登陆的时候过滤了admin。后端检测的时候使用的是cookie里面的cipher以及iv解密后得到的字符进行反序列化

原文:
a:2:{s:8:"username";s:5:"admix";s:8:"password";s:5:"admix";}

cipher:
540LbV57RP5K18c1jJeAOk7HOjcMLHthWSvcLRPMmOTUdyvcS8NkZsx%2BjR7cIgNOqIURlke333VwZ8Tv0Ajpkg==

iv:
Ne%2BoeXNBcE%2BuI%2BCrlXqjqw%3D%3D

这里cookie可控。

明文加密时分组为:

1
2
3
4
a:2:{s:8:"userna
me";s:5:"admix";
s:8:"password";s
:2:"123";} 

这里只需要把admixx反转成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
32
33
34
#-*- coding:utf8 -*-
import base64
import urllib.parse

# a:2:{s:8:"userna
# me";s:5:"admiN";
# s:8:"password";s
# :6:"123";}

#原始cipher
ciphertext = 'w1uvgfzxxuYHA%2Bo08ZL%2BCefhwr2jHuwglOIBAh8cP1w5TCiCmY0Yy%2BQxelAl9%2B%2FiZeRvLD7UjzlF58bTGFZ%2BWQ%3D%3D'

cipher = base64.b64decode(urllib.parse.unquote(ciphertext))
array_cipher = bytearray(cipher)
array_cipher[13] = array_cipher[13]^ ord('N') ^ ord('n')
#print(array_cipher)
print('newCipher:',urllib.parse.quote(base64.b64encode(array_cipher)))

#解密后的明文base64
decode_plain = base64.b64decode('sxvY7wUlyPgC+/iV7InfjG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==')
#原始iv
iv = base64.b64decode(urllib.parse.unquote('1HxERxo2%2FTuymbrPoVDB%2Bw%3D%3D'))
#原始明文
plain = 'a:2:{s:8:"userna'

newiv = list(iv)

for i in range(16):
newiv[i] = (ord(plain[i].encode('utf-8')) ^ iv[i] ^ decode_plain[i])

newiv = bytes(newiv)

print('newiv:',urllib.parse.quote(base64.b64encode(newiv)))

小饼干一换就行了。

0x04 Refence

CBC字节翻转攻击解析

[CTF]AES-CBC字节翻转攻击

  

:D 一言句子获取中...