# [NPUCTF2020]webdog

image-20241114192253842

源码:

<?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 是一种加密模型,采用的是分组链接模式,把明文分为一组一组的进行加密

image-20241114193437702

上图为 CBC 加密流程,最开始一个特别别分组 IV(初始向量)去和第一段密文 XOR,得到的结果呗密钥加密,加密得到该组的密文,同时这个密文会充当最开始的特别分组 IV 的作用去参与下一组的加密,以此类推。

CBC 的每一个分组的加密结果都可以影响到下一个分组的加密结果,使原本独立的分组密码加密过程形成迭代,这可以强化加密算法的 “敏感性”,即实现所谓的 “雪崩效应”,在香浓理论中这就是” 扰乱原则 “。

CBC 只是一种模式,他经常把 aes 或 des 作为加密使用的算法,DES 分组长度式八字节而 AES 分组长度式十六字节。

接下来就是 CBC 解密

image-20241114194056628

顺序依旧是从左到右,第一组密文被 KEY 解密后与 IV xor 得到第一组明文,同时第一组密文参与下一组的解密充当 IV 的作用

# PKCS #5

竟然 CBC 模式涉及到分组,那么就会出现最后一组字节没有被占满的情况。 比如原本一个分组是 8 字节,我们有 15 个字节的明文需要被加密,此时最后一个分组就不会被占满(还差一个字节占满),那么这个时候要怎么办呢?

这时候就需要对最后一个分组进行填充,将其填充满。 对于采用 des 算法加密的内容,填充规则是 PKC #5, 而 AES 是 PKC #7. 这两者唯一区别是 PKCS #5 填充是八字节分组而 PKCS #7 是十六字节 ,还记得上面我们说过的 DES 分组长度是八字节而 AES 分组长度是十六字节 吗?就是这个分组字节数影响了填充方式。

那么具体是怎么填充的呢,我们以 PKC #5 为例

image-20241114194320377

当最后一组还剩 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 跳转啥啥的,通过这个网页返回的信息,我们就有了可乘之机。

我们先通过一个图感受一下第一组的解密流程

image-20241114200918054

好的好的,感受了这个解密流程后,我们来说说攻击的事。 如果我们得到了 Intermediary Value (中间值),并且可以手动修改 IV,那么我们岂不是可以构造任意 Decrypted Value(明文)了? 所以 padding oracle attack 的核心就是去获得中间值。 那么我们怎么去获取呢?

还记得刚刚提到的 3 种情况吗?我们可以通过修改 IV,通过判断网页返回内容来判断中间值,具体做法如下:

我们先把 IV 全部设置为 0x00,然后修改 IV 的最后一个数,当其与中间值 XOR 后的值为 0x01 则此时解密就会成功,若不是 0x01 解密就会失败,网页会返回不同的内容,以此来判断何时解密成功。然后把解密成功时的 IV 的最后一位数与 0x01 进行异或计算,即可得到中间值的最后一位

image-20241114200941307

然后我们把 IV 最后一位数设置为能和中间值最后一位数异或后值为 0x02 的数,穷举 IV 倒数第二个数看看哪个数能和中间值倒数第二个数异或运算后值为 0x02,然后我们就可得到中间值倒数第二个数,以此类推可以获得第一组的中间值(有点绕) 然后破解到了中间值我们再用最开始的 IV(不是我们后面构造的 IV)去和中间值异或就得到明文了 当然,你也可以再次构造 IV,从而构造解密出来的字符(通过中间值与 IV 异或)

image-20241114201047952

先获取密文:

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 明文

image-20241114201206936

# 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())

image-20241114204159168

获取 flag

参考链接:https://cloud.tencent.com/developer/article/1942522

参考链接:https://blog.csdn.net/weixin_43610673/article/details/105898440

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

odiws WeChat Pay

WeChat Pay

odiws Alipay

Alipay

odiws PayPal

PayPal