博客本文地址:https://www.yourhome.ren/index.php/sec/366.html
经典的CBC字节翻转攻击,还是有必要再通过一篇博文来记录一下的,也是作为一枚菜鸡的备忘录。
0x01 CBC加密流程
CBC加密的特性,简述来说:
明文首先被分割成等长的若干个明文块,每个明文块将作为下一个明文块加密过程中输入的一部分,从而影响到下一个明文块的加密结果。在一般情况下,明文会被分成等长的16字节的明文块,不足16字节部分,会用特殊填充符填充,图示如下:

这其中有一个重要输入IV,是随机生成的初始向量,它影响第一个明文块的结果,并且将传递到整个加密链中,以保证对同一明文每次的加密结果都是不同的。
需要注意的是,CBC模式中涉及到的异或运算,是本次进行CBC翻转攻击的关键所在。
0x02 CBC翻转攻击利用点
类似于CBC加密的过程,CBC解密的过程也是将当前的密文块作为下一个密文块参与运算的输入,具体如图:

假设我们修改了第一个密文块的第4个字节,那么根据CBC模式的解密,它将影响下一个密文块的解密结果,如图标红部分:

而在上述的影响过程中,只涉及到异或运算,这里异或运算的特性就十足重要了!
已知A=B^C可以得到结论B=A^C并且也能得到A^C^B=0上述结论拿到这里的CBC翻转攻击假设中看
设第一块的第4个字节设为变量A,第二块的明文中第4个字节设为变量C,在第二块加密产生的第4个字节设为变量B因为A^B=C,根据结论有B=A^C如果人为将A变量值改变为A^C的结果,那么参与运算后A^B将等于A^C^B=B^B=0,第二个产生的明文的第4个字节将变为0如果将A变量值改变为A^C^X(这里X是任意字符),那么参与运算后,A^B将等于A^C^X^B=B^X^B=X,第二个产生的明文的第4个字节将变为X字符,这样,第二块密文块解密的结果就可控了!上面这个简单的阐述,就是CBC翻转攻击实现的核心,下面以实例来继续说明。
0x03 Bugku的例题
题目名称:Login4
题目地址:http://118.89.219.210:49168/
访问题目是一个登陆界面:

先直接扔到扫描器,找到备份文件:

这里也推一波自己也日常用的CTF扫描器scanCTF:https://github.com/MRdoulestar/scanCTF拿到源码进行代码审计,这里列出主要逻辑代码(已给出注释):
//设置cookie的流程调用的函数,返回一个随机的iv和使用该iv加密的post提交的username和password的结果——cipherfunction 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", 64_encode($iv)); setcookie("cipher", 64_encode($cipher));}//检查函数,这里是对cookie中cipher和iv进行CBC翻转的利用点function check_login(){ //如果cookie中设置了cipher和iv参数 if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){ //将cipher和iv参数都进行 64解码 $cipher = 64_decode($_COOKIE['cipher']); $iv = 64_decode($_COOKIE["iv"]); //进行CBC模式的AES解密 if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){ //对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值 $info = unserialize($plain) or die("<p> 64_decode('". 64_encode($plain)."') can't unserialize</p>"); $_SESSION['username'] = $info['username']; }else{ die("ERROR!"); } }}//根据session中username参数,控制显示结果function show_homepage(){ //如果session中的username为admin,则返回flag 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>';}//检查是否带有username和password参数,如果有参数,进入设置cookie的路径//如果没有设置参数,进入判断cookie路径if(isset($_POST['username']) && isset($_POST['password'])){ $username = (string)$_POST['username']; $password = (string)$_POST['password']; //不允许提交的username为admin if($username === 'admin'){ exit('<p>admin are not allowed to login</p>'); }else{ $info = array('username'=>$username,'password'=>$password); login($info); show_homepage(); }}else{ //如果session中的username字段已经存在了,既登陆过了,则进入 if(isset($_SESSION["username"])){ check_login(); show_homepage(); }else{ ................. }以上代码考察了CBC翻转攻击的利用。它禁止直接提交用户名为admin进行登陆,但是返回flag的条件又是session中用户名要是admin。所以需要构造类似"xdmin"形式或者"axmin"等形式的用户名,之后通过修改iv和cipher,通过CBC翻转攻击将字符x还原成"admin"
代码中涉及到了序列化,数组序列化结果参考如下(这里使用ydmin和123作为username和password):a:2:{s:8:"username";s:5:"ydmin";s:8:"password";s:3:"123"}
攻击过程如下:
1、修改能够得到的密文的第一块(前16个字节),使得第二块密文块结果的字符中ydmin能变成admin2、由于操作1导致第一块密文块解密后得到错误结果,修正IV初始化向量,将第一块的明文结果还原成a:2:{s:8:"userna攻击实现Python脚本:
#!/bin/env pythonimport 64import urllibimport requestsimport resession = "uubpkpbv56dtkiorccjc8md3r0"cipher = "gn5OKkTDH1CXJv9w11bK7JEqnhzhGSzVs84FNwZ2Zfi1c1PEeQdF1jUXn2lIy3SInj8pJiVajWEF73Fl%2BgLThA%3D%3D"iv = "PGUt3F%2FPwu93r7DGTBQvKg%3D%3D"#进行url编码解码和 64解码操作iv_raw = 64.b64decode(urllib.unquote(iv))cipher_raw = 64.b64decode(urllib.unquote(cipher))#a:2:{s:8:"username";s:5:"ydmin";s:8:"password";s:3:"123"}#计算修正cipher,使得第二块的y可以变成a(原理回顾上述异或结论)cipher_new = cipher_raw[0:9] + chr(ord(cipher_raw[9]) ^ ord('y') ^ ord('a') ) + cipher_raw[10:]cipher_new = urllib.quote( 64.b64encode(cipher_new))print cipher_new##########################################################拿新的cipher作为cookie访问,拿到继续修正iv的数据url="http://118.89.219.210:49168/index.php"cooky={ "iv":iv, "cipher":cipher_new, "PHPSESSID":session}#这里本地测试,使用了Burpsuit代理proxy={ "http":"http://192.168.155.1:8082"}x = requests.session()res = x.get(url, cookies=cooky, proxies=proxy)text = str(res.text)#print text#########################################################使用正则提取目前cipher解密后的结果(第一块损坏)prog = re.compile("decode('(.*)')")match = prog.search(text)new_plain = ""if match: new_plain = match.group(1)#print new_plainnew_plain = 64.b64decode(new_plain)#########################################################对iv进行修正,使得新的iv可以使得第一块密文块解密得到正确结果real_first_blk = 'a:2:{s:8:"userna'iv_new=""for i in range(0,16): iv_new += chr(ord(iv_raw[i]) ^ ord(new_plain[i]) ^ ord(real_first_blk[i])) print urllib.quote( 64.b64encode(iv_new))最终得到计算得到的cipher和iv:
root@dbstar-virtual-machine:~# python cbc_cipher.pygn5OKkTDH1CXPv9w11bK7JEqnhzhGSzVs84FNwZ2Zfi1c1PEeQdF1jUXn2lIy3SInj8pJiVajWEF73Fl%2BgLThA%3D%3DWsHICjaxJEd2co8CBheYAA%3D%3D访问即可:

0x04 总结和参考
CBC翻转攻击的文章和博客已经很多了,但不亲手实现配合着写一篇,仍然觉得不能彻底掌握,因此有了这篇文章,也是自己的一个备忘录。
如有不足和不对的地方,还请指出和谅解。
继续阅读与本文标签相同的文章
几道密码题练习记录
又一个PHP的奇怪特性—[.空格
-
nginx配置location语法
2026-05-25栏目: 教程
-
Java编程题目(三)
2026-05-25栏目: 教程
-
[难]令人惊艳的最短路问题
2026-05-25栏目: 教程
-
JavaScript设计模式之命令模式
2026-05-25栏目: 教程
-
华住4.6亿收购花间堂71.2%股权,加大布局中高端产业
2026-05-25栏目: 教程
