CBC字节翻转攻击
参考链接: https://rhythmmark.github.io//bugku-web-login4-WP/ 这个贼详细
https://www.cnblogs.com/s1ye/p/9021202.html
题目代码
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
| <?php include 'sqlwaf.php'; define("SECRET_KEY", "................"); define("METHOD", "aes-128-cbc"); session_start();
function get_random_iv(){ $iv=''; for($i=0;$i<16;$i++){ $iv.=chr(rand(1,255)); } return $iv; // 随机生成从1到255中的一个数字并且将它进行ascii编码,重复16次,将它们合在一起为$vi,返回$vi } function login($info){ $iv=get_random_iv(); #初始向量 $plain = serialize($info); #info序列化后得到明文 $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); #CBC模式下AES的加密明文 $_SESSION['username'] = $info['username']; setcookie("iv", base64_encode($iv)); setcookie("cipher", base64_encode($cipher)); // 初始向量加密,aes后的明文再加密(base64) } function show_homepage(){ if ($_SESSION["username"]==='admin'){ ## 得到flag条件是我们Session里面的username为admin echo '<p>Hello admin</p>'; echo '<p>Flag is *************</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>'; die(); } 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!"); ## 这里全部解密,反序列化,来检查login } } }
if (isset($_POST['username'])&&isset($_POST['password'])) { $username=waf((string)$_POST['username']); $password=waf((string)$_POST['password']); if($username === 'admin'){ #如果username是admin的话,就会显示下面这句话然后退出 exit('<p>You are not real admin!</p>'); }else{ $info = array('username'=>$username,'password'=>$password); login($info); show_homepage(); } } else{ if(isset($_SESSION["username"])){ check_login(); show_homepage(); } }
|
这里我加了一些注释方便理解代码
- define(“METHOD”, “aes-128-cbc”) 说明采用的是aes-128-cbc模式加密
- 在show_homepage()函数中,只要$_SESSION[“username”]===’admin’就可以得到flag
- 然后观察哪里会用到show_homepage()这个函数
- 如图可见有两个地方,这两个分别有login和check_login函数,其实就是互逆关系,下面给出login大概步骤图
- login()函数中
1
| $_SESSION['username'] = $info['username'];
|
但后面说传入username的若为admin,则exit,显然不行的
1 2 3
| if($username === 'admin'){ #如果username是admin的话,就会显示下面这句话然后退出 exit('<p>You are not real admin!</p>');
|
- 另外一个check_login其实就是login逆过来了
在正常情况下,如果我们正正经经地post一个账户名和口令字,这个网页就会把你post的东西经过login那一系列的变化,用cookie保存下来,然后下次我们不需要再post东西了,凭借cookie,网站通过check_login得到我们post过得账户名和口令字,就能知道我们的身份。
这样经过check_login函数之后,$_SESSION[‘username’]就还是我们最开始post的那个username。
但我们是正经人吗?我们是要搞事get flag的人,所以我们就要想如何把$_SESSION[‘username’]变成admin,
即使我们最开始post的可能是admi2,
但是它经过了AES加密和解密
这里就要用到CBC字节翻转攻击
攻击的point就在check_login()的解密过程里。
总的来说就是只有admin用户才能读取flag,但是admin用户又不允许登录。虽然相互矛盾,由于题目用到了aes的cbc模式加密,所以我们可以利用cbc字节翻转攻击来得到我们想要的明文。
CBC字节翻转攻击原理
CBC,就是每个明文块先与前一个密文块进行异或后,再进行加密
(下面复制一个例子参考)
【复制粘贴开始】
比方说,我们有这样的明文序列:
a:2:{s:4:”name”;s:6:”sdsdsd”;s:8:”greeting”;s:20:”echo ‘Hello sdsdsd!’”;}
我们的目标是将“s:6”当中的数字6转换成数字“7”。我们需要做的第一件事就是把明文分成16个字节的块:
• Block 1:a:2:{s:4:”name”;
• Block 2:s:6:”sdsdsd”;s:8
• Block 3::”greeting”;s:20
• Block 4::”echo ‘Hello sd
• Block 5:sdsd!’”;}
因此,我们的目标字符位于块2,这意味着我们需要改变块1的密文来改变第二块的明文。
有一条经验法则是(注:结合上面的说明图可以得到),你在密文中改变的字节,只会影响到在下一明文当中,具有相同偏移量的字节。所以我们目标的偏移量是2:
• [0] = s
• 1 = :
• 2 =6
因此我们要改变在第一个密文块当中,偏移量是2的字节。正如你在下面的代码当中看到的,在第2行我们得到了整个数据的密文,然后在第3行中,我们改变块1中偏移量为2的字节,最后我们再调用解密函数。
$v = “a:2:{s:4:”name”;s:6:”sdsdsd”;s:8:”greeting”;s:20:”echo ‘Hello sdsdsd!’”;}”;
$enc = @encrypt($v);
$enc[2] = chr(ord($enc[2]) ^ ord(“6”) ^ ord (“7”));
$b = @decrypt($enc);
运行这段代码后,我们可以将数字6变为7:
但是我们在第3行中,是如何改变字节成为我们想要的值呢?
基于上述的解密过程,我们知道有,A = Decrypt(Ciphertext)与B = Ciphertext-N-1异或后最终得到C = 6。等价于:
C = A XOR B
所以,我们唯一不知道的值就是A(注:对于B,C来说)(block cipher decryption);借由XOR,我们可以很轻易地得到A的值:
A = B XOR C
最后,A XOR B XOR C等于0。有了这个公式,我们可以在XOR运算的末尾处设置我们自己的值,就像这样:
A XOR B XOR C XOR “7”会在块2的明文当中,偏移量为2的字节处得到7。
【复制粘贴结束】
题目进行CBC字节翻转攻击
这里我们尝试admi2,首先在本地phpstudy生成明文序列
1 2 3 4
| <?php $info = array('username'=>'admi2','password'=>'Password'); echo serialize($info); ?>
|
1
| a:2:{s:8:"username";s:5:"admi2";s:8:"password";s:8:"Password";}
|
同样地分成16个字节的块
1 2 3 4
| a:2:{s:8:"userna me";s:5:"admi2"; s:8:"password";s :8:"Password";}
|
要修改第二行的第13个2 -> n ,按照CBC字节翻转攻击的套路,我们要修改的是上一个分组对应位置的密文, 即修改第一行的第13个r
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| # -*- coding:utf-8 -*- import base64 from urllib import unquote from urllib import quote_plus bs = 'PFTchi6G9vZxttcQECnYNOQYYfo9qENaEX80uR45Wulnekb0WfZMTH7QKYTm3Lqk4WYwKIYmPv8GahlmDsQYCw%3D%3D' #密文cipher,从Chrome的插件EditThisCookie可以很轻松get到/抓包也可以 bs = unquote(bs) #url解码 bs_de = base64.b64decode(bs) #base64解码
ch = chr(ord(bs_de[13]) ^ ord('2') ^ ord('n')) bs_de=bs_de[0:13]+ch+bs_de[14::] #其实就是bs_de[13]=chr(ord(bs_de[13]) ^ ord('2') ^ ord('n')) #因为python里面字符串不可变,即你直接bs_de = 'a'这样会报错
rs = base64.b64encode(bs_de) #print rs print quote_plus(rs)
|
cipher应该是经过cbc模式下aes加密后最后的密文(挺不好理解的多看看几遍就行了)
首先第一行填admi2,第二行碰都不要碰(它会自动帮你填充Password,如果点击了一下Password就gg为空了)然后抓包(重新按回车)
然后上刚刚的脚本生成密文替换抓包里面的密文,然后运行一下看看
报错是因为我们换成了新密文,不是刚才的密文,反序列化失败是正常的,把里面的base64码进行编码如下:
可以看到有admin,我们刚开始输入的是amin2,说明转换成功,所以下一步我们就要用相同的套路,来复原第一个密文分组,也就是初始向量。接下来改变一下初始向量vi,上脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| # -*- coding:utf-8 -*-
import base64 from urllib import unquote from urllib import quote_plus
mingwen_de='nj7W9vwn0gxyOdX3xUN/lG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjg6IlBhc3N3b3JkIjt9' #base64_decode('这里面的') can't unserialize mingwen = base64.b64decode(mingwen_de) print mingwen
iv = 'cFDYuzPtN1Q5ETd1E7s1Zw%3D%3D' #此时cookie里的iv iv = unquote(iv) iv_de = base64.b64decode(iv) new = 'a:2:{s:8:"userna' for i in range(16): iv_de = iv_de[:i] + chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i])) + iv_de[i+1:] #iv_de[i]=chr(ord(iv_de[i]) ^ ord(mingwen[i]) ^ ord(new[i]))
print(base64.b64encode(iv_de)) #用这个结果把原来的iv换掉
|
之后就容易多了,这其中,iv_de就是IV,mingwen就是当前它解密出来的明文,也就是我们要改的,new是正确的明文,就是我们想要改变的.
修改后运行得到flag