CBC字节翻转攻击
简介
CBC加密是AES加密的一种模式,中文名叫密码分组链接模式(Cipher Block Chaining (CBC)),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
看下图:
IV:随机生成的初始向量
Plaintxt:明文数据
Ciphertext:密文数据
Key:分组加密使用的密钥
加密过程
首先将我们需要将需要加密的明文按照十六个字节为一组分组,最后一组不满十六字节用特殊字符填补
接着系统产生一个十六字节的随机字符串作为初始向量,该向量与第一组明文进行异或操作,再与key进行CBC加密得到第一组密文
第一组密文再与下一组明文进行异或操作CBC加密得到第二组密文
以此类推……
将得到的密文组按顺序拼接到一起就是所得到的密文
解密过程
首先将密文按十六进制分组
第一组密文与key进行CBC解密,再将解密的数据与初始向量异或得到第一组明文
接着第一组密文再与下一组CBC解密得到的数据进行异或操作得到第二组明文
以此类推……
最后拼接得到明文
异或操作
CBC字节翻转的关键点就在异或上,所以首先我们要明白异或是什么。
当我们的一个值C是由A和B异或得到
C = A XOR B
那么
A XOR B XOR C很明显是=0的
当我们知道B和C之后,想要得到A的值也很容易
A = B XOR C
因此,A XOR B XOR C等于0。有了这个公式,我们可以在XOR运算的末尾处设置我们自己的值,即可改变。
攻击过程
攻击针对的是解密过程
第一步:修改
由图可知,你在密文中改变的字节,只会影响到在下一明文当中,具有相同偏移量的字节。
所以我们要找到要修改的明文中的那一个字节C,我,上一组密文对应字节为A,本组密文对应字节为B,我们知道了A xor B = C,我们现在现在要将C改变为c,那么知道A XOR B XOR C = 0,则A XOR B XOR C xor c= c。
这里我们知道B是解密后的数据未知我们不好修改,所以我们可以将A修改a=A xor C xor c ,这样我们就将C替换成了c。
第二步:修复
上一步我们将第二组明文修改为了我们想要得到的数据,但是,与此同时,我们也将第一组密文给修改了,这就会导致第一组明文数据被修改,那么我们不能去修改第一组密文,又要使第一组明文数据正确,我们只有利用异或对初始向量下手。
若原iv为O,新iv为N,错误的第一组明文M = O xor 第一组密文,我们要想得到正确的第一组明文m,那么就去改变N = O xor M xor m,这样就利用异或得到了正确的明文。
看个例子
这是iscc的一道题Only admin can see flag
查看源码发现一个TXT文件,打开得到PHP代码
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| <?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; }
//设置cookie的流程调用的函数,返回一个随机的iv和使用该iv加密的post提交的username和password的结果——cipher function login($info){ $iv = get_random_iv(); //序列化传入数组 #a:2:{s:8:"username";s:5:"Admin";s:8:"password";s:4:"test";} #第一组明文:a:2:{s:8:"userna #第二组明文:me";s:5:"Admin"; #第三组明文:s:8:"password";s #第四组明文::4:"test";} $plain = serialize($info); //cbc加密 $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)); }
//检查函数,这里是对cookie中cipher和iv进行CBC翻转的利用点 function check_login(){ if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){ $cipher = base64_decode($_COOKIE['cipher']); $iv = base64_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>base64_decode('".base64_encode($plain)."') can't unserialize</p>"); $_SESSION['username'] = $info['username']; }else{ die("ERROR!"); } } } //根据session中username参数,控制显示结果 //如果没有设置参数,进入判断cookie路径 function show_homepage(){ //session要为admin 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']; //post传参不能为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{ 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>'; } } ?>
|
我已经在其中做出详细的注释
首先从入口开始验证post是否传参username和password,这里要求传入的用户名不能为admin。
1 2 3 4 5 6 7 8 9 10 11
| if(isset($_POST['username']) && isset($_POST['password'])){ $username = (string)$_POST['username']; $password = (string)$_POST['password']; //post传参不能为admin if($username === 'admin'){ exit('<p>admin are not allowed to login</p>'); }else{ $info = array('username'=>$username,'password'=>$password); login($info); show_homepage(); }
|
接着对传入参数序列化,接着对起进行一次CBC加密,得到了COOKIE值iv和cipher,以及session值username,对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值,但是这里又要传入的username参数为admin。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function check_login(){ if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){ $cipher = base64_decode($_COOKIE['cipher']); $iv = base64_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>base64_decode('".base64_encode($plain)."') can't unserialize</p>"); $_SESSION['username'] = $info['username']; }else{ die("ERROR!"); } } }
|
所以我们利用CBC翻转字节,传入Admin绕过过滤,再在加密过程中将A翻转为a,通过验证。
修改代码如下:
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
| import urllib,base64,requests,re
url = "http://*.*.*.*/index.php" datas = { "username" : "Admin", "password" : "test" } #第一组明文:a:2:{s:8:"userna #第二组明文:me";s:5:"admin"; #第三组明文:s:8:"password";s #第四组明文::4:"test";} #修改过程 r = requests.post(url,data=datas) cipher = r.cookies.get("cipher")#获取初始密文 cipher = base64.b64decode(urllib.unquote(cipher)) offset = 9 new_cipher = cipher[:offset] + chr(ord(cipher[offset])^ord("A")^ord("a")) + cipher[offset+1:]#字节翻转 new_cookies = requests.utils.dict_from_cookiejar(r.cookies) new_cookies["cipher"] = urllib.quote_plus(base64.b64encode(new_cipher)) #修复过程 r2 = requests.get(url,cookies=new_cookies) #获得损坏的第一段明文 plain = base64.b64decode(re.findall("decode('(.*)')",r2.text)[0]) iv = base64.b64decode(urllib.unquote(new_cookies["iv"])) old = plain[:len(iv)] new = 'a:2:{s:8:"userna' new_iv = "".join([chr(ord(iv[i])^ord(old[i])^ord(new[i])) for i in xrange(16)]) new_cookies["iv"] = urllib.quote_plus(base64.b64encode(new_iv))
r3 = requests.get(url,cookies=new_cookies) print(r3.text)
|
最后将修改的iv和cipher设为cookies得到flag
CBC翻转字节攻击属于密码学的题目,关键是理解加密解密过程和异或操作,后面还遇到了翻转攻击与SQL注入的结合题型,值得继续了解。。。