这题考的是CBC分组加密攻击,反正dirsearch先扫一波,index.php.swp
然后就vim -r index.php.swp查看源码
<!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"); //采用aes-128-cbc加密
session_start();
function get_random_iv(){ //随用随机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'];//info里面的username存入cookie
}else{
die("ERROR!");
}
}
}
function show_homepage(){
if ($_SESSION["username"]==='admin'){//flag在这里。也就是在运行到show_homepage的时候需要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'){//不能直接post 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" οnfοcus="this.value=\'\'" />
<input name="password" type="password" class="input password" value="Password" οnfοcus="this.value=\'\'" />
</div>
<div class="footer">
<input type="submit" name="submit" value="Login" class="button" />
</div>
</form>
</div>
</body>';
}
}
?>
</html>
主函数的主要逻辑是哪几个if-else循环,所以,必须执行show_message函数,而第一个else因为之前if的判断无法进入,所以只能考虑执行第二个else,那就存在必须要过check_login的问题
也就是session里面的username得是admin,而第一次post来的不能是admin,结合提示,考虑CBC字节翻转
cookie里要加从服务器发过来的iv,和cipher,过第一个if,而且要把username和password都给删了,才能进到当前判断
结合CBC的原理图,我们知道,我们是要改变第二个16字节的块(128/8=16)里面的第14个字节,下标13,就是将对象序列化后按16字节切块,借用一下大佬的
Block1: a:2:{s:8:"userna
Block2: me";s:5:“admia”;
Block3: s:8:“password”;s
Block4: :3:“123”;}
那么第一个小目标就是通过变动第一个块的内容将Block里面的admia变为admin
“a”=密文第一块的第十三字符(已知)^密文第二块的第十三字符加密后的结果C = A ^ B(C和A已知)
由于加密算法不可逆,所以,我们要想办法把这个都是替换,两边同时异或A,得到A^C=B
C=A^B,左右同时异或C,0=C^A^B,AC均已知,C^A是第一块密文,再次异或任意字符x
x=x^C^A^B,也就是通过将得到密文的特定位异或要变成的那个字符再异或之前那个原来的明文字符,就可以保证解密出来的字符是自己想要的
我都快绕晕了,简单来说就是结果可控
看看别的大佬写的可能清楚点:
因为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,这样就达到了控制明文某个字节的目的了。
下面就上代码:
<?php
$enc=base64_decode(urldecode("wggNb%2BH4qqIDHh3yghh0le7Um1aSYsRZi%2BHrv%2FrInlmgh0GM3AcRdPhKfCcQnuzYNPcPewNQxgV6eHQs2Jgb3A%3D%3D"));
$enc[13] = chr(ord($enc[13]) ^ ord("k") ^ ord ("n"));//我登陆的时候是admik,要变成admin enc是解密后的密文,我们要动他的第一组第十三位
echo base64_encode($enc);
echo "\n";
echo urlencode(base64_encode($enc));
?>
由于动了密文,没有动iv,所以第一组解密的时候会报错,这次的事情比较简单iv XOR 解密(密文1) = 明文1,我们要求的是新iv,同样的原理
新iv XOR 解密密文1=明文1
旧iv XOR 解密密文1=错误明文1
错误明文1,明文1,旧iv均已知道,那么就有等式关系
旧iv xor 新iv xor 解密密文1 xor解密密文1=错误明文1 xor 明文1
推导出 旧iv xor 新iv=错误明文1 xor 明文1
新iv=错误明文1 xor 明文1 xor 旧iv
以此等式进行编程
<?php
$enc=base64_decode("ZuQeEreZfAGVPqHUEYhPf21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjY6IjEyMzQ1NiI7fQ==");
$iv=base64_decode(urldecode("gUHb63JR92WP7hMXq4JvFQ%3D%3D"));
$cleartext = 'a:2:{s:8:"userna';
$newiv = '';
for ($i=0;$i<16;$i++){
$newiv=$newiv.chr(ord($iv[$i]) ^ ord($enc[$i]) ^ ord ($cleartext[$i]));
}
echo urlencode(base64_encode($newiv));
?>
空请求服务器会报408,正常来说应该不会有这个错误。反正多试几次就好了,也可以在正文里加点不含有username和password的东西也行
参考视频链接:https://www.bilibili.com/video/BV1C3411677b