Bugku CTF web39(Web)

目录扫描,得到.index.php.swp
目录扫描结果
下载该文件,使用vim -r .index.php.swp打开审计源码。已经手动在代码里添加了注释

<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();

function get_random_iv(){	//随机生成16位初始化向量
    $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);	//加密
	//options:以下标记的按位或: OPENSSL_RAW_DATA 原生数据,对应数字1,不进行 base64 编码。OPENSSL_ZERO_PADDING 数据进行 base64 编码再返回,对应数字0。 
    $_SESSION['username'] = $info['username'];	//注册SESSION全局变量
	//以下两行设置cookie
    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!");
        }
    }
}

#第二个执行,检测用户名为admin时,打印flag
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>

源码审计

审计源码首先要找到程序起点,跟着程序走一遍,了解流程。
程序起点在这个if里:
在这里插入图片描述

我们以else为分割符,先看上面一段的代码。
程序接收到POST参数(username,password),并且禁止admin登陆。当用户名不是admin的时候,首先把用户名密码放入数组,传到login方法中。
login方法对传入的数组进行了序列化,并且使用aes-128-cbc对序列化进行加密。iv(初始化向量)是随机生成的。最终把cipher和iv放入cookie。
在这里插入图片描述

再到show_homepage()方法,检测$_SESSION中的username是admin时,打印flag。否则提示Only admin can see flag
在这里插入图片描述

然后审计else的下半部分,这里是上半部分操作执行过后,存在$_SESSION[‘username’]时执行。当不存在POST数据或者$_SESSION[‘username’]时,显示登陆页面。
有$_SESSION[‘username’]时,进入check_login()方法。
当cookie中存在cipher、iv时,对cipher进行解密。这里是解题的关键,可以通过修改cookie中的cipher值,将序列化数据的用户名修改成admin。从而绕过程序起点处禁止admin登陆的判断。
在这里插入图片描述

最后执行到show_homepage()方法,当我们在check_login()中把用户名修改为admin时,这里输出flag。

解题:
访问题目页面,使用用户名admil,密码123登陆。页面提示内容与审计的结果一致。此时程序已经执行了login()方法,在cookie中写入了cipher和iv。


使用burp抓包,刷新页面,内容如下:
通过上面审计源码可知,需要把post数据删掉,才能进入check_login()方法判断当前用户名。

通过最开始列出的“CBC字节翻转攻击原理”文章,这里需要修改cipher和iv的值来实现变更用户名。

扫描二维码关注公众号,回复: 12977386 查看本文章

基本原理(强塞内容):

在这里插入图片描述

这里讲下为什么能把admil修改成admin
根据上图,我们可以知道CBC解密过程:

密文1=>解密密文1=>解密密文1 XOR 初始化向量(iv) = 明文1
密文2=>解密密文2=>解密密文2 XOR 密文1 = 明文2
密文3=>解密密文3=>解密密文3 XOR 密文2 = 明文3
以此类推,除了第一次,后面所以数据解密后都需要跟上一个密文进行异或得到明文。
从上面的解密过程可以推断出,当我们修改前一个密文的第N个字节时,会影响到后一个密文解密出来的明文的第N个字节。
例如:当我们修改密文1的第6个字节时,密文2解密时,解密后的密文2跟密文1进行异或操作,明文2的第6个字节也会受到影响。
异或特性:
解密得出明文的步骤使用了异或运算,而异或运算有个特性,是可以自定义异或结果的。
这里的讲解借用到大佬文章的讲解思路。

 

假设:A ^ B = C,则可得
B = A ^ C
当人为修改A=A ^ C时,
A ^ B = A ^ C ^ B = B ^ B = 0
当人为修改A=A ^ C ^ x (x为任意数值)时,
A ^ B = A ^ C ^ x ^ B = B ^ B ^ x = x

举例:
密文1[4]的意思是密文1字符串第4个字节,相当于数组下标。
设:密文1[4] = A,解密(密文2)[4] = B,明文2[4] = C
因为A ^ B = C,根据结论有B = A ^ C
当人为修改A=A ^ C时,那么A ^ B = A ^ C ^ B = B ^ B = 0,这样明文2[4]的结果就为0了
当人为修改A=A ^ C ^ x (x为任意数值)时,那么
A ^ B = A ^ C ^ x ^ B = B ^ B ^ x = x,这是明文2[4] = x,这样就达到了控制明文某个字节的目的了。

编程:
根据上面的推论,就可以开始写程序修改cipher和iv来控制用户名了。

<?php
header("Content-Type: text/html;charset=utf-8");
	#计算cipher
	/*
	明文1:a:2:{s:8:"userna		//r
	明文2:me";s:5:"admil";		//l字母在第14个字节
	明文3:s:8:"password";s
	明文4::3:"123";}
	*/
	$cipher = base64_decode(urldecode('f0csYAAWDy%2FGlSsvWLr6NlCBad4p2U%2BXm2Rr2X07iytKd4r8V5tbO7%2FcxIib96eRDGUOMQclQgvxw2SZXOobWQ%3D%3D'));
	$temp = $cipher;
	/*
	设密文1[13]=A,	解密(密文2)[13]=B,	明文2[13]=C,	
	将A修改为A ^ C,则:
	A ^ B = A ^ C ^ B = B ^ B = 0 = C
	*/
	//						A				  C			 X
	$cipher[13] = chr(ord($cipher[13]) ^ ord('l') ^ ord('n'));
	echo urlencode(base64_encode($cipher));
	?>

执行结果:

在这里插入图片描述
当我们在burp将计算后的cipher替换发送后,发现提示错误。
这是因为我们修改了密文1中的数据,使第一次解密出的明文数据错乱,打乱了序列化数据的格式,反序列化失败。
但是当我们把返回的base64数据解码后,可以发现我们的username已经修改成admin了。

在这里插入图片描述

在这里插入图片描述

出现这种错误,根据CBC解密原理,我们只需要修改iv的值,使得iv XOR 解密(密文1) = 明文1 即可。
编写程序实现: 

<?php
	#计算iv
	$res = base64_decode('yeiydaYLG5RNzOPWaQgOkG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==');	//这里放burp放回的base64数据
	$iv = base64_decode(urldecode('TD9FI%2FvbrZn%2FSjbSD9bfSQ%3D%3D')); //这里放cookie中的iv
	$plaintext = 'a:2:{s:8:"userna';
	$new_iv = '';
	for ($i = 0; $i < 16; $i ++){
		$new_iv = $new_iv . chr(ord($iv[$i]) ^ ord($res[$i]) ^ ord($plaintext[$i]));
	}
	echo urlencode(base64_encode($new_iv));
?>

执行结果:
在这里插入图片描述

在burp中,将iv替换上去,go一下。得到flag。
需要注意的是,每次发送数据包,cipher和iv都会变化,所以前面的步骤要一气呵成哦~
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/ChaoYue_miku/article/details/115096330