CBC字节翻转攻击

iscc2018线上赛开始两周多了,学到了很多,写几篇文章总结一下遇到的知识点,做一个归纳,方便以后查找。

web300-----CBC字节翻转攻击

cbc是AES加密的cbc模式 即密码分组链模式:

     先将铭文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,在于密钥进行加密。

直接看图理解一下加解密原理:

明文以16个字节为一组进行分组,给出初始化向量iv于明文进行异或然后利用密钥key在进行加密得到密文a,密文a就相当于下一段明文的初始化向量,以此类推。

解密时,同样将密文分组,每组密文先利用密钥解密在与“iv”异或得到明文,过程类似加密。

我们的攻击发生在解密的时候,即我们可以控制解密时所生成的明文。

根据解密方式我们可以知道,A=ciphertext(N-1),B=plaintext(N),C为第N块待异或且经过解密的字符,C'为我们经过翻转要得到的明文。

所以我们可以打得到关系:

A = B ^ C

C = A ^ B

A ^ B ^ C = 0

A ^ B ^ C ^ C' = C'

根据关系式可以得到 A' = A ^ C ^ C'

所以说我们只需要修改前一组密文所对应的本组明文相同位置的字符,即可得到想要的明文。

下面贴上源代码以便理解:

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

阅读源代码,我们可以知道,只有admin用户才能读取flag,但是admin用户又不允许登录。虽然相互矛盾,由于题目用到了aes的cbc模式加密,所以我们可以利用cbc字节翻转攻击来得到我们想要的明文。我们来看一下流程:

以账号 admiN 密码 123456登录

题目将用户名密码传入数组并序列化得到a:2:{s:8:"username";s:5:"admiN";s:8:"password";s:5:"123456";

接下来进行aes加密,并将得到的cipher和iv进行base64编码放入cookie中(cookie对于攻击者来说可控,所以存在cbc字节翻转攻击)

明文加密时分组为:

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

因此我们只需要将"N"字节翻转为"n"即可得到flag。

根据我们得到的关系,已知只需修改前一组密文即可。

$newcipher[13]=chr(ord(13) ^ ord('N') ^ ord('n'))

这时我们就会得到

a:2:{s:8:"username";s:5:"admin";s:8:"password";s:5:"123456";

但是由于前一组密文被修改了 所以前一组的明文会出现乱码,因此接下来我们再生成新的iv将前一组明文改回a:2:{s:8:"userna 即可得到flag。

写了一个脚本(代码烂的不行  凑活看....)

#-*- coding:utf8 -*-
import base64
import urllib
# a:2:{s:8:"userna
# me";s:5:"admiN";
# s:8:"password";s
# :6:"123456";}

def Module1():
	ciphertext = raw_input("Please input the first round cipher:\n")
	cipher = base64.b64decode(urllib.unquote(ciphertext))
	new_cipher = cipher[:13] + chr(ord(cipher[13]) ^ ord('N') ^ ord('n')) + cipher[14:]
	print urllib.unquote(base64.b64encode(new_cipher))

def Module2():
	errorcipher = base64.b64decode(urllib.unquote(raw_input('Please input errorcipher: \n')))
	ivtext = raw_input("Please input iv:\n")
	iv = base64.b64decode(urllib.unquote(ivtext))
	cleartext = 'a:2:{s:8:"userna'
	newiv = ''
	for i in range(16):
		newiv += chr(ord(iv[i]) ^ ord(errorcipher[i]) ^ ord(cleartext[i]))
	print urllib.unquote(base64.b64encode(newiv))

option = raw_input("Please input option [1 or 2]:")
if option == '1':
	Module1()
elif option == '2':
	Module2()
else:
	pass

先登录网站

在地址栏处回车,抓包得到cipher和iv。

运行脚本 选择 1;

 复制cipher值粘贴到脚本中 得到新的cipher。

用新的cipher替换数据包中的cipher

 

 

这个时候我们的admiN其实已经变成了admin了  接下来就是生成新的iv。(将报错的信息解码看一下)

重新抓包

将iv和cipher改为 新生成的iv和之前生成的cipher发包 得到flag。

 

 理解的时候大费周章,记录的时候也还是有点难写的  有的地方不知道怎么说, 以此记录学习成果。

任重而道远。

猜你喜欢

转载自www.cnblogs.com/s1ye/p/9021202.html