0x01 CBC加密
首先贴上一张原理图,其中:
- Plaintext:待加密的数据块
- Initialization Vector(IV):用于随机化加密的比特块(初始化向量)
- key:随机生成的密钥
- Ciphertext:加密后的数据
加密过程:
- 将需要加密的明文分组(常见的为16字节一组),位数不足使用特殊字符进行填充,该原理图中就分为了3组
- 生成一个初始的随机向量(IV)和一个密钥(key)
- 将IV与第一组明文分组进行XOR(异或)运算
- 使用key对xor后产生的密文进行加密,产生一个Ciphertext
- 将上面产生的Ciphertext作为新的IV与第二组的明文进行xor运算,得到一个密文
- 使用key对5中产生的密文进行加密,再次得到一个Ciphertext
- 重复5和6两个步骤,得到第三个分组产生的Ciphertext
- 将最开始产生的IV和机密后的几个Ciphertext拼接到一起,得到最终的密文
这种加密模式的最重要的特点就是:使用前一块的密文来产生后一块的密文
0x02 CBC解密
原理图如上,解密过程:
- 从密文中提取出IV,然后将密文进行分组
- 使用key对第一组Ciphertext进行解密,然后与IV进行xor得到一个明文
- 使用key对第二组Ciphertext进行解密,再与2中Ciphertext进行xor得到一个明文
- 重复上诉步骤,得到第三组明文
- 将三组明文拼接,得到待求明文
0x03 CBC翻转攻击
我们已经知道前一块的Ciphertext会用来产生下一块明文,如果我们改变前一块Ciphertext中的一个字符,再去和下一个区块得到的密文进行xor,就可以得到一个不同的明文,而这个明文我们是可以控制的,我们就可以利用这一点对服务端进行欺骗或者绕过过滤器。
0x04 实例分析
打开页面后
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <?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 $flag; }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>'; } } ?>
|
在这个题目中,要求登录名为admin则输出flag,但是题目禁止了admin登陆,所以这里就可以用到CBC字节翻转攻击。
这里将用户名和密码存入数组,序列化之后进行AES-CBC模式的加密,其中IV和密文都以cookie方式存储,可以控制,所以就存在攻击的可能。
因为需要翻转的目标是admin,我已我们这里的登录名最好也是5位,我使用
用户名:emmmm
密码:qwert
登录后用户名和密码被存入数组然后序列化变成:
a:2:{s:8:"username";s:5:"emmmm";s:8:"password";s:5:"qwert";}
我们需要翻转的目标为:
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:5:"qwert";}
处理过程:
首先对明文进行分组,16字节一组,分为4组
- a:2:{s:8:”userna
- me”;s:5:”emmmm”;
- s:8:”password”;s
- :5:”qwert”;}
根据CBC攻击原理,我们只需要修改第一组的Ciphertext对应第二组’skctf’的位置的Ciphertext,就可以实现第二组明文的改变,这里我们需要修改的位置为第一组Ciphertext的10-14位
利用脚本重新生成Ciphertext
1 2 3 4 5 6 7 8
| import base64 cipher = 'rsvYuT0qD7XdLXi8D3DSs6J4xWpA82s6b6ofC/6JKJjxdijo0yzIaRW8D/TFyrXECDQWwUh4dDtXs+tCQq94WQ=='.decode('base64') old = "me\";s:5:\"emmmm\";" new = "me\";s:5:\"admin\";" for i in xrange(16): cipher = cipher[:i] + chr(ord(cipher[i]) ^ ord(old[i]) ^ ord(new[i])) + cipher[i+1:] print cipher.encode('base64').strip()
|
执行脚本得到:rsvYuT0qD7XdKXG8C3PSs6J4xWpA82s6b6ofC/6JKJjxdijo0yzIaRW8D/TFyrXECDQWwUh4dDtXs+tCQq94WQ==
- 这样我们修改第一区块的密文就可以打到修改第二块密文的效果,但是这时候也同时破坏了第一块的明文,我们需要对第一块的数据进行恢复,用上面生成的密文修改cookie的cipher(需要将符号url Encode),访问,返回:
正好获取到第一次翻转后的明文:RKIHz2N76jZOTfUPD/9/n21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6InF3ZXJ0Ijt9
- 通过修改IV来修改第一块的明文:
利用一下脚本重新生成IV:
1 2 3 4 5 6 7 8 9
| # -*- coding: utf-8 -*- import base64 plain = 'RKIHz2N76jZOTfUPD/9/n21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6InF3ZXJ0Ijt9'.decode('base64') iv = '9TKkeY91bZat0arrAaKiQw=='.decode('base64') old = plain[:16] new = "a:2:{s:8:\"userna"; for i in xrange(16): iv = iv[:i] + chr(ord(iv[i]) ^ ord(old[i]) ^ ord(new[i])) + iv[i+1:] print iv.encode('base64').strip()
|
运行得到:
0KqRjJd9vZjZviqXay+zvQ==
- s使用这里得到的VI和上面的cipher替换cookie
发包后,响应成功,应为我这里本地只有一个php文件,所以无法返回flag,但其实CBC攻击已经完成,已经作为admin登陆
参考链接:
https://blog.csdn.net/csu_vc/article/details/79619309
https://p0sec.net/index.php/archives/99/
https://mochazz.github.io/2018/05/06/CBC字节翻转攻击/#CBC原理