CBC字节翻转攻击

2018/09/19 安全 漏洞研究 8735 words views

最近做到了一道 CBC 字节翻转攻击的题目,这里单独拿出来分析一下,感觉这个攻击还挺有意思的。

0x01 题目

题目:http://ctf.bugku.com/challenges#login4

关于CBC字节翻转攻击乌云知识库上有一篇特别好的文章:

CBC字节翻转攻击:CBC字节翻转攻击-101Approach

关于上面这道题目的解法主要参考这两篇文章:

https://rhythmmark.github.io/bugku-web-login4-WP/

https://www.cnblogs.com/s1ye/p/9021202.html

0x02 tips

在上面一部分基本给出了不错的分析过程,那么我这里主要总结一下CBC字节翻转攻击在这里主要的一些tips.

CBC字节翻转攻击之前

对于index.php.swp文件里面是16进制,首先要将其转换成源码

$vi -r index.php.swp

进入 vim 模式,然后按任意键

:w index.php
:q!

这样swp文件16进制就会转换成源码保存在 index.php 文件中
<!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", 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;
}

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 $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>';
}

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>

CBC字节翻转攻击分析

主要看下面这两个函数

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

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

CBC字节翻转攻击的原理这里就不再介绍了,一图胜千言:

CBC1

CBC字节翻转攻击的本质是:通过修改密文来达到修改下一个明文的效果。

但是修改之后,该密文对应的明文会乱码,所以还要进行该明文的还原。

所以,实现CBC字节翻转攻击主要就两点:1.通过修改第i组密文从而达到修改第i+1组明文的效果;2.修改第i组密文会造成第i组明文乱码,故需要复原第i组明文。

所使用的唯一且关键的技术就是:密文 = 密文 ⊕ 我们要改掉的 ⊕ 我们要改成的

0x03 求解过程

1.通过修改第i组密文从而达到修改第i+1组明文的效果

下面这是目标明文,每组16个字符

a:2:{s:8:"userna
me";s:5:"admi2";
s:8:"password";s
:8:"Password";}
# -*- coding:utf-8 -*-

import base64
from urllib import unquote
from urllib import quote_plus

bs = 'J67IvH4OY9t6QfcCP4Mp88bRfPKdsYszlev3LNbgPQ872VR633trbWfCqhcDYm6c3Eysp36W2SFkx4CPBX9nDQ%3D%3D'
#密文cipher,从Chrome的插件EditThisCookie可以很轻松get到
bs = unquote(bs)
#url解码
bs_de = base64.b64decode(bs)
#base64解码

ch = chr(ord(bs_de[13]) ^ ord('2') ^ ord('n'))
bs_de=bs_de[0:13]+ch+bs_de[14::]
#其实就是bs_de[13]=chr(ord(bs_de[13]) ^ ord('2') ^ ord('n'))
#因为python里面字符串不可变,即你直接bs_de = 'a'这样会报错

rs = base64.b64encode(bs_de)
#print rs
print quote_plus(rs)
bs = unquote(bs)
bs_de = base64.b64decode(bs)

以上是解除附加于密文上的编码效果
ch = chr(ord(bs_de[13]) ^ ord('2') ^ ord('n'))
通过修改第1组密文的第13个字符,从而达到修改第2组明文第13个字符的效果

bs_de=bs_de[0:13]+ch+bs_de[14::]
重组修改后的密文

rs = base64.b64encode(bs_de)
print quote_plus(rs)
复原密文上base64和url的编码效果

以上就完美地完成了第一步,上面所达到的效果就是我们已经成功的修改了第2组明文的第13个字符,也就是说我们将2变成了n但是,又带来了另一个麻烦就是我们一旦修改了第1组的密文,就导致第1组明文的变化(明文和密文有着对应关系。)所以就要就行第二步。

2.修改第i组密文会造成第i组明文乱码,故需要复原第i组明文

C0 = IV

C1 = EK(Z1)

C2 = EK(Z2)

我们要第一个分组的明文,当然就要对C0 = IV下手了

# -*- coding:utf-8 -*-
import base64
from urllib import unquote
from urllib import quote_plus

mingwen_de='nj7W9vwn0gxyOdX3xUN/lG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjg6IlBhc3N3b3JkIjt9'
#base64_decode('这里面的') can't unserialize
mingwen = base64.b64decode(mingwen_de)
print mingwen

iv = 'cFDYuzPtN1Q5ETd1E7s1Zw%3D%3D'
#此时cookie里的iv
iv = unquote(iv)
iv_de = base64.b64decode(iv)
new = 'a:2:{s:8:"userna'
for i in range(16):
    iv_de = iv_de[:i] + chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i])) + iv_de[i+1:]
#iv_de[i]=chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i]))

print(quote_plus(base64.b64encode(iv_de)))
#用这个结果把原来的iv换掉

做事之前首先要明确我们要做什么

要注意我们始终是逆后台php代码的login过程

此时我们cookie里面的cipher里面的数据就是目标明文加密之后且被处理之后的密文。(也就是第2组明文第13个字符被处理过了,第1组密文乱码)。

这一步所要做的就是修改iv,让其在经过后台的php代码处理之后能把cipher第1组乱码的明文复原。

CBC加密过程

Ciphertext-0 = Encrypt(Plaintext XOR IV)—只用于第一个组块

en

CBC解密过程

de

从CBC解密过程可以看到,处理后的第1组的密文用于修改第二组的明文,就可以解决问题。它所引发的第1组明文乱码的问题就通过修改iv来解决。

所以,总而言之,言而总之,就是在这里修改下iv就行了。

mingwen = base64.b64decode(mingwen_de)
print mingwen
这里的mingwen就是 Decrypt(Ciphertext)

iv = unquote(iv)
iv_de = base64.b64decode(iv)
以上是解除附加于密文上的编码效果

密文 = 密文 ⊕ 我们要改掉的 ⊕ 我们要改成的

Plaintext-0 = Decrypt(Ciphertext) XOR IV—只用于第一个组块

new = 'a:2:{s:8:"userna'
for i in range(16):
    iv_de = iv_de[:i] + chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i])) + iv_de[i+1:]
#iv_de[i]=chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i]))
这样iv就改成了我们想要的iv了

使用修改后的iv解密,可以保证第1组的明文不会乱码,这样才能保证后台php代码反序列化的顺利进行。

iv_de[i]=chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i]))
等价于
IV = IV ⊕ Decrypt(Ciphertext) ⊕ new
这里的IV就是我们处理之后的IV

下面我们分析一下后台用处理之后的IV来进行解密的过程:

下面是这些是最核心的部分:

Plaintext-0 = Decrypt(Ciphertext) XOR IV
上面的整个解密跟:
i=2
Pi[13] = DK(Ci)[13] ⊕ Ci-1[13] = DK(Ci)[13] ⊕ crypto[13] ⊕ ‘2’ ⊕ ‘n’ = ‘n’
(这里的 DK(Ci)[13]='2')
本质是一样的,在这里就是让 Plaintext-0 = new
Plaintext-0 = Decrypt(Ciphertext) ⊕ IV ⊕ Decrypt(Ciphertext) ⊕ new = new

Search

    Table of Contents