Sky's blog

CBC字节翻转攻击

字数统计: 1,402阅读时长: 6 min
2017/06/16 Share

0x00 相关介绍

首先让我们看看CBC是如何工作的:
加密过程:

Plaintext:待加密的数据。
IV:用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。
Key:被一些如AES的对称加密算法使用。
Ciphertext:加密后的数据。
在这里重要的一点是,CBC工作于一个固定长度的比特组,将其称之为块。在本文中,我们将使用包含16字节的块。
所以正如你所见:前一块的密文用来产生后一块的密文。
所以由此我们就可以得出解密过程:

值得注意的地方:
前一块的密文是用来产生下一块明文;这就是字节翻转攻击开始发挥作用的地方。
如果我们改变前一块密文的一个字节,然后与下一个解密后的组块异或,我们就可以得到一个不同的明文了!
与此同时,下面的这张图也可以很好地说明这种攻击:

0x01 实战演练

在此用Bugku上的一道题来做分析:
题目链接:http://47.93.190.246:49168/
进入后发现页面存在源码泄露:index.php.swp
恢复后审计源码:

1
2
3
4
5
6
7
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}

首先随机生成了一个16位的iv;
然后从

1
2
$info = array('username'=>$username,'password'=>$password);
login($info);


1
2
3
4
5
6
7
8
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));
}

可见,对传入的账号和密码进行序列化,作为明文,然后对其进行AES加密,其中使用到了随机生成的iv
后将加密后的内容进行base64编码,放入cookie中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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!");
}
}
}

然后再对传入的cookie中的密文解密,然后对明文就行反序列化(如果反序列化失败就打印出明文的base64编码),后把反序列化后的明文,即Info中的username还给username,最后进行判定:

1
2
3
4
5
6
7
8
9
10
function show_homepage(){
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>';
}

如果用户名是admin,就给出flag,否则不给,但值得一提的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}
}

登录时做出了限制,如果用admin登录是显然不行的。
所以这时就需要CBC字节翻转攻击来实现了
登录时:可以使用

1
2
username=1dmin;
password=sky;

此时我们要做的就是把1dmin通过翻转攻击,把’1‘变成‘a’,即可变成admin登录成功得到Flag
首先按照步骤:将我们传入的Info进行序列化,得到明文:
a:2:{s:8:"username";s:5:"1dmin";s:8:"password";s:5:"12345";}
然后对其进行分组:

1
2
3
4
block 1 : a:2:{s:8:"userna
block 2 : me";s:5:"1dmin";
block 3 : s:8:"password";s
block 4 : :5:"12345";}

我们所要改变的即block 2中的偏移量为9的那个明文,即1
所以按照攻击方式,应该改变block 1中有相同偏移量的那个密文,即偏移量为9的值
所以得到以下公式:
cipher = cipher[:9] + chr(ord(cipher[9]) ^ ord(‘1’) ^ ord(‘a’)) + cipher[10:]
故可将1dmin变成admin,但有一点需要注意,这样改变后,密文的值发生了改变,将其进行解密后反序列化,是会失败的,从而会把无法反序列化的明文打印出来(因为我们对block 1进行了改变,虽然block 2变成了我们所希望的值,但block 1却变成了未知的量)
所以此时,需要改变iv的值来改变block 1的值
(注:这就是cbc的处理方式,iv的值改变block1的值,block1的值改变block2的值……)
故此,我们需要一个正确的Iv,使block 1依旧为a:2:{s:8:"userna
故此得到第二个公式:(plain为无法反序列化打印出来的被base64编码的明文)

1
2
3
4
5
6
want = 'a:2:{s:8:"userna'
first_16 = ''
iv = base64.b64decode('你一开始随机生成的被base64编码过的iv')
for i in range(16):
first_16 += chr(ord(plain[i]) ^ ord(iv[i]) ^ ord(want[i]))
newiv = first_16

这样就可以将密文前16位的值,即block 1的明文变成我们想要的a:2:{s:8:"userna
故此即plain伪造成功,此时的plain是绝对可以被反序列化的
最后,我们将伪造的密文和伪造的iv传入cookie,即可被解密还原成我们伪造的plain
即:
a:2:{s:8:"username";s:5:"1dmin";s:8:"password";s:5:"12345";}
变成了
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:5:"12345";}
最后即可以admin的身份成功登入网站,拿到flag

0x02 萌萌哒结尾

第一次接触crypto和web结合起来的题目,感觉的确比一般的web有了一些难度(毕竟加密毕竟难)。也算学到了不少知识,以后如果遇到别的加密和web结合的题目,我还会回来哒~~~~

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 0x00 相关介绍
  2. 2. 0x01 实战演练
  3. 3. 0x02 萌萌哒结尾