1, 首先CBC是一种加密方式,下面是CBC加密得原理图
initialization vector是伪随机数,由此可以使同样的明文每次加密得到的结果都不一样.key是密钥.首先将明文分段,一般都是16字节为一段.而后将第一段与IV异或,加密之后生成第一段密文;
将第一段密文作为第二段明文加密使用得IV,以此类推,最终将得到的密文拼接起来就完成了加密操作.
2, 解密过程如图:
解密过程就是反过来,首先将密文按照之前得分段,第一段用解密算法解密,而后与IV再次异或
这里涉及到异或得性质:a^a=0;
b^a^a=b;
因此这里IV再次参与异或就将IV消掉了,得到原来的明文.之后将第一段密文作为第二段得VI,以此类推,将所有密文分段解密,再拼接起来即得到明文.
而CBC字节翻转攻击就利用到CBC加密过程得特点,这里以SKCTF得一道题也即bugku内得一道题来示范CBC字节翻转攻击.
bugku:ctf训练网站 http://ctf.bugku.com
进入之后看到得是一个很常见的网页登陆框
试着登陆一下
发现要求使用admin账号登陆,使用admin登陆却发现:
这里就非常蹊跷,猜测应当是要构造绕过.这里没有头绪时,尤其时对这种特别简介没有任何提示信息得题目,很大可能会设置有源码泄露.
使用sourceleakheaker工具扫描网站(使用方法网上很多,大家可以去搜索)
在搜索结果中发现果然有源码泄露,是index.php.swp文件,swp文件时linux下vi编辑器错误退出产生得.我们在linux环境下处理这个swp文件.执行
vi -r index.php.swp
在vi中打开后,按住esc,而后shift+q
输入
w index.php
将文件写入一个index.php文件中,
输入q退出
得到的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>
代码得讲解这里不过多讲述,大佬一看就懂,像我们这样得菜鸟我建议还是多看看弄懂到底是个什么逻辑.
整个得代码思路大概就是,将用户输入得username,password构造成为一个数组而后进行序列化,再用CBC加密,同时随机生成一个随机数,将加密后的结果与随机数均进行base64加密后作为页面得两个cookie存在:
setcookie("iv", base64_encode($iv)); setcookie("cipher", base64_encode($cipher));
正常登陆时先判断username是否为admin,是的话就说
admin are not allowed to login
不是的话就说
Only admin can see flag
这显然是一个矛盾得关系.我们再次查看源代码,发现有一个check_login函数.当用户登陆没有输密码但是输入了用户名时会触发.而后进行页面得展示.
在check_login函数中,首先会判断是否有前文得两个cookie,有的话就会对两个cookie进行判别,解析处cookie中含有得username,而后进入该username对应得界面.
这里我们就发现,check_login函数之后没有对username进行检测,如果我们修改了cookie使cookie解析出来的username为admin,那麽我们就绕过了对username得检测,成功进入admin页面取得flag.
这个过程就是CBC字节翻转攻击发挥作用的时候了
首先我们用zdmin作为用户名登陆.而后会获得两个cookie值:
iv:cMIh5SDj890k6v1HGPYHLw%3D%3D
cipher:pkUm0C%2F0ruGbG9vsGrWTu1IwoLNQmRPGZ9%2BX62pQw3Gk9%2BTqN0B%2Fy9gjq%2FfXDLiHrB7GwxuQhq%2Br5UMgBL9QBw%3D%3D
这里cipher就是我们传入的{username:zdmin,password:123}反系列化而后进程CBC加密,再base64加密得到得结果.
iv则是一个随机数进行CBC加密而后base64加密得结果.
我们要做的就是想办法修改cookie,使后面cbc解密分析cookie中含有的username得值变为admin,而不是我们传进去得zdmin,只有以为需要修改.
再次回顾CBC解密得过程:
我们可以发现,一旦我们控制了密文,那麽我们就可以通过异或得方式更改后面一段得明文.我们知道CBC以16字符为一段.
我们传进去的值序列化然后分段得结果为:
#a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"zdmin";
#s:8:"password";s
#:3:"12345";}
我们想要修改第二段得z这个字母,就应该对应的修改第一段密文中的z对应位置得字母
修改得脚本如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- import base64 import requests import urllib iv_raw='%2F8iEm4jh%2BjbgVGwlQ31ycg%3D%3D' #这里填写第一次返回的iv值 cipher_raw='8WdhbPxjZy9xYAgoCeghiOUQu0ri1Y3dv7cX44MbvOfIC6zZxCbR%2FPFpeMatL5qIgT%2BYA66tIdCBpxtWsWxV9Q%3D%3D' #这里填写第一次返回的cipher值 cipher = base64.b64decode(urllib.unquote(cipher_raw)) xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:] xor_cipher=urllib.quote(base64.b64encode(xor_cipher)) print "反转后的cipher:" + xor_cipherxi
修改后得到的cookie,再次参与cbc解密后,得到的第二段明文中的zdmin就会变为admin
我们执行后结果:
这里判断解密之后反序列化出现错误,我们查看cbc解密得过程可以发现.在我们修改了第一段密文之后,第一段明文整个就变了.而正确的第一段明文我们是知道的.应该为:
s:2:{s:8:"userna
这里我们就可以对iv进行修改,使第一段产生得明文更改为s:2:{s:8:"userna
代码如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- import base64 import urllib cipher = 'Bc6oENSSAEPpPdv/rbqRZG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9'#填写提交后所得的无法反序列化密文 iv = '%2F8iEm4jh%2BjbgVGwlQ31ycg%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))
成功取得flag:
以上便是对CBC字节翻转攻击原理得讲解和一道ctf题目中的实际利用.这些只是我自己的一点理解,有不对的地方大家批评指正.
参考链接:
https://blog.csdn.net/csu_vc/article/details/79619309
https://mochazz.github.io/2018/05/06/CBC%E5%AD%97%E8%8A%82%E7%BF%BB%E8%BD%AC%E6%94%BB%E5%87%BB/