# [NPUCTF2020]webdog
源码:
<?php | |
error_reporting(0); | |
include('config.php'); # $key,$flag | |
define("METHOD", "aes-128-cbc"); // 定义加密方式 | |
define("SECRET_KEY", $key); // 定义密钥 | |
define("IV","6666666666666666"); // 定义初始向量 16 个 6 | |
define("BR",'<br>'); | |
if(!isset($_GET['source']))header('location:./index.php?source=1'); | |
#var_dump ($GLOBALS); // 听说你想看这个? | |
function aes_encrypt($iv,$data) | |
{ | |
echo "--------encrypt---------".BR; | |
echo 'IV:'.$iv.BR; | |
return base64_encode(openssl_encrypt($data, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)).BR; | |
} | |
function aes_decrypt($iv,$data) | |
{ | |
return openssl_decrypt(base64_decode($data),METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv) or die('False'); | |
} | |
if($_GET['method']=='encrypt') | |
{ | |
$iv = IV; | |
$data = $flag; | |
echo aes_encrypt($iv,$data); | |
} else if($_GET['method']=="decrypt") | |
{ | |
$iv = @$_POST['iv']; | |
$data = @$_POST['data']; | |
echo aes_decrypt($iv,$data); | |
} | |
echo "我摊牌了,就是懒得写前端".BR; | |
if($_GET['source']==1)highlight_file(__FILE__); | |
?> |
# Padding oracle attack 与 CBC 翻转字节攻击
# 异或(XOR)
何为异或?异或是一种运算方法,简要概括就是同假异真
代码语言:javascript
复制
true xor true = false; true xor false = true; | |
在二进制中: | |
011 xor 110 = 101; 二进制数每位数分别进行xor运算 | |
上面算式的运算步骤是这样的 0 xor 1 = 1;1 xor 1 = 0;1 xor 0=1 | |
然后把运算结果写在一起就是101了 | |
在更高的进制中: | |
以十进制为例: 65 xor 42 = 107; 原理就是把十进制数字先变为二进制数进行异或,再将异或得到的二进制数结果变为十进制数 | |
在字符中: | |
对字符进行异或运算其实就是对字符的ascii码进行异或计算,计算得到的结果视作新的一个ascii码再将其转换为字符。 |
xor 还有一个性质,就是 已知
a xor b = c 那么 b xor c =a; a xor c = b.
即满足异或运算里只需知道任意两个数就能得到另一个数。
CBC 模式
CBC 是一种加密模型,采用的是分组链接模式,把明文分为一组一组的进行加密
上图为 CBC 加密流程,最开始一个特别别分组 IV(初始向量)去和第一段密文 XOR,得到的结果呗密钥加密,加密得到该组的密文,同时这个密文会充当最开始的特别分组 IV 的作用去参与下一组的加密,以此类推。
CBC 的每一个分组的加密结果都可以影响到下一个分组的加密结果,使原本独立的分组密码加密过程形成迭代,这可以强化加密算法的 “敏感性”,即实现所谓的 “雪崩效应”,在香浓理论中这就是” 扰乱原则 “。
CBC 只是一种模式,他经常把 aes 或 des 作为加密使用的算法,DES 分组长度式八字节而 AES 分组长度式十六字节。
接下来就是 CBC 解密
顺序依旧是从左到右,第一组密文被 KEY 解密后与 IV xor 得到第一组明文,同时第一组密文参与下一组的解密充当 IV 的作用
# PKCS #5
竟然 CBC 模式涉及到分组,那么就会出现最后一组字节没有被占满的情况。 比如原本一个分组是 8 字节,我们有 15 个字节的明文需要被加密,此时最后一个分组就不会被占满(还差一个字节占满),那么这个时候要怎么办呢?
这时候就需要对最后一个分组进行填充,将其填充满。 对于采用 des 算法加密的内容,填充规则是 PKC #5, 而 AES 是 PKC #7. 这两者唯一区别是 PKCS #5 填充是八字节分组而 PKCS #7 是十六字节 ,还记得上面我们说过的 DES 分组长度是八字节而 AES 分组长度是十六字节 吗?就是这个分组字节数影响了填充方式。
那么具体是怎么填充的呢,我们以 PKC #5 为例
当最后一组还剩 n 个字节未被填充时,就会填充 n 个 0xn 字符上去。上图是 PKCS #5, 其实 PKCS #7 和 PKCS #5 原理是一样的,不过是分组字节数大了点罢了(0~16)
就是如果是 alsdawgfqa 这个按照 4 个一组的话就留了 qa 两个,在 qa 后加上 \x02\x02, 若是删除 a,只有 q,那就是 \x03\x03\x03
# Padding oracle attack 攻击场景举例
首先我们假设一个场景,从而引出这个攻击。
假设我们有一个任意文件包含场景
代码语言:javascript
复制
url?file=/etc/passwd |
黑客们看见了就会很轻而易举的去包含想要的文件。管理者发现了这个问题,对 file 参数采用了 CBC 加密,即当提交请求时,file 参数的值是被加密的,然后服务器用算法解密得到其想包含的文件,然后返回给客户端。
代码语言:javascript
复制
url?file=e28b2e3c972edab8 其中前8位数是IV,后八位数是密文。(这里是我瞎写的密文= =,你只需要理解到这里是一个CBC加密后的密文就行了) |
那么如何去实现我们的任意文件包含呢?padding oracle attack 出现了。
# 攻击原理
假设我们向刚刚那个任意文件包含的提交了一段密文。服务器就会尝试解密,就会出现三种结果。
1. 密文不能正常解密,这种原因是在于最后一组的填充字节出现了错误 2. 密文能正常解密但解密出来的文件路径不存在 3. 密文能正常解密且能成功包含
其中第 1 种情况和 2.3 种情况网页返回的内容肯定是不同的。 比如说第一种情况可能就直接返回 500 了,2.3 可能就是 302 跳转啥啥的,通过这个网页返回的信息,我们就有了可乘之机。
我们先通过一个图感受一下第一组的解密流程
好的好的,感受了这个解密流程后,我们来说说攻击的事。 如果我们得到了 Intermediary Value (中间值),并且可以手动修改 IV,那么我们岂不是可以构造任意 Decrypted Value(明文)了? 所以 padding oracle attack 的核心就是去获得中间值。 那么我们怎么去获取呢?
还记得刚刚提到的 3 种情况吗?我们可以通过修改 IV,通过判断网页返回内容来判断中间值,具体做法如下:
我们先把 IV 全部设置为 0x00,然后修改 IV 的最后一个数,当其与中间值 XOR 后的值为 0x01 则此时解密就会成功,若不是 0x01 解密就会失败,网页会返回不同的内容,以此来判断何时解密成功。然后把解密成功时的 IV 的最后一位数与 0x01 进行异或计算,即可得到中间值的最后一位
然后我们把 IV 最后一位数设置为能和中间值最后一位数异或后值为 0x02 的数,穷举 IV 倒数第二个数看看哪个数能和中间值倒数第二个数异或运算后值为 0x02,然后我们就可得到中间值倒数第二个数,以此类推可以获得第一组的中间值(有点绕) 然后破解到了中间值我们再用最开始的 IV(不是我们后面构造的 IV)去和中间值异或就得到明文了 当然,你也可以再次构造 IV,从而构造解密出来的字符(通过中间值与 IV 异或)
先获取密文:
ly7auKVQCZWum/W/4osuPA==
然后对其进行 base64 解密,发现其刚好是有 16 个字节,那么我们可以直接穷举 IV 得到中间值,然后凭此与初始 IV(16 个 6)进行异或得到明文 $flag.
话不多说,爆破中间值的脚本安排上。(借鉴了一个大佬的 WP)
import requests | |
import base64 | |
import time | |
Intermediary="" | |
url="http://de1650aa-2b24-40e0-bb51-736ff5d38269.node3.buuoj.cn//index.php?source=1&method=decrypt" | |
iv="" | |
hexs="" | |
IV="6666666666666666" | |
def xor(a,b): | |
return "".join([chr(ord(a[i])^ord(b[i])) for i in range(len(a))]) | |
for step in range(1,17): | |
padding=chr(step)*(step-1) | |
print("第%s轮"%step) | |
for i in range(0,256): | |
iv=chr(0)*(16-step)+chr(i)+xor(Intermediary,padding) | |
post={ | |
"iv":iv, | |
"data":"ly7auKVQCZWum/W/4osuPA==" | |
} | |
r=requests.post(url=url,data=post,proxies={"http":"http://127.0.0.1:8080"}) | |
time.sleep(0.1) | |
print(r.text+"第%s轮i=%s "%(step,i)) | |
if "False" != r.text: | |
Intermediary=xor(chr(i),chr(step))+Intermediary | |
print(Intermediary) | |
break | |
for k in range(len(Intermediary)): | |
hexs="%"+str(ord(Intermediary[k]))+hexs | |
print(hexs) | |
print(xor(Intermediary,IV)) |
爆出了中间值以及 $flag 明文
# CBC 翻转字节攻击
在对 CBC 模式加密的数据进行解密时,若 iv 可控,则可以任意控制解密后的内容。 CBC 翻转字节攻击不同于 padding oracle attack,后者的核心是 IV 可控情况下获取中间值,从而可以获得明文或者任意控制密文解密后的数据。 而 CBC 翻转字节攻击的核心思想就不是获取中间值了,而是在 IV 可控的情况下,通过算法缺陷来直接控制密文解密后的数据。
现在假定有中间值 A, 明文 B1,IV C1
那么就有
代码语言:javascript
复制
A^B1=c1 |
一点错误都没有对吧。 现在又假定有中间值 A, 我们想要解密出的明文 B2 和与之对应的 IV C2 那么就有
代码语言:javascript
复制
A^B2=C2 | |
结合以上两个式子,有 | |
A=B1^C1=B2^C2 | |
于是有 | |
B2=B1^C1^C2 | |
或C2=B1^B2^C1 | |
若我们已知B1,C1,且C2可控,那么B2即可控 | |
既满足刚刚我们说的,通过算法缺陷来直接控制密文解密后的数据。 |
FlagIsHere.php:
ryino46QYgekMUTu0JFPMQ== | |
<?php | |
#error_reporting(0); | |
include('config.php'); //$fl4g | |
define("METHOD", "aes-128-cbc"); | |
define("SECRET_KEY", "6666666"); | |
session_start(); | |
function get_iv(){ // 生成随机初始向量 IV | |
$random_iv=''; | |
for($i=0;$i<16;$i++){ | |
$random_iv.=chr(rand(1,255)); | |
} | |
return $random_iv; | |
} | |
$lalala = 'piapiapiapia'; | |
if(!isset($_SESSION['Identity'])){ | |
$_SESSION['iv'] = get_iv(); | |
$_SESSION['Identity'] = base64_encode(openssl_encrypt($lalala, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $_SESSION['iv'])); | |
} | |
echo base64_encode($_SESSION['iv'])."<br>"; | |
if(isset($_POST['iv'])){ | |
$tmp_id = openssl_decrypt(base64_decode($_SESSION['Identity']), METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_POST['iv'])); | |
echo $tmp_id."<br>"; | |
if($tmp_id ==='weber')die($fl4g); | |
} | |
highlight_file(__FILE__); | |
?> |
此时我们已知初始 IV, 初始密文,且 IV 可控,那么 CBC 翻转字节攻击条件成立,可以攻击。
这里贴一个别人写的python2 CBC字节反转攻击脚本 | |
自己拿python3写了半天都没写出很好的效果... | |
import base64 | |
def bxor(b1, b2): # use xor for bytes | |
parts = [] | |
for b1, b2 in zip(b1, b2): | |
parts.append(bytes([b1 ^ b2])) | |
return b''.join(parts) | |
iv = base64.b64decode("h34HL5RbMPw8oTaQ+P58nw==") | |
text = b"piapiapiapia\x04\x04\x04\x04" | |
result = b"weber\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" | |
middle = bxor(iv,text) | |
iv = bxor(middle,result) | |
print(base64.b64encode(iv)) |
把跑出来的结果 POST 过去,就会得到下一步了…
直接有了链接,这个是 buu 的话,直接下载附件就行了
// | |
// Source code recreated from a .class file by IntelliJ IDEA | |
// (powered by FernFlower decompiler) | |
// | |
public class HelloWorld { | |
public HelloWorld() { | |
} | |
public static void main(String[] var0) { | |
System.out.println("众所周知,你是一名WEB选手,掌握javaweb也是一项必备技能,那么逆向个java应该不过分吧?"); | |
byte[] var10000 = new byte[]{102, 108, 97, 103, 123, 119, 101, 54, 95, 52, 111, 103, 95, 49, 115, 95, 101, 52, 115, 121, 103, 48, 105, 110, 103, 125}; | |
} | |
} |
获取了这个 class 文件,有一些字节东西可以查看
a = bytearray( | |
[102, 108, 97, 103, 123, 119, 101, 54, 95, 52, 111, 103, 95, 49, 115, 95, 101, 52, 115, 121, 103, 48, 105, 110, 103, 125] | |
) | |
print(a.decode()) |
获取 flag
参考链接:https://cloud.tencent.com/developer/article/1942522
参考链接:https://blog.csdn.net/weixin_43610673/article/details/105898440