从开发到安全:我是如何不小心在Flask里写出了一个SSTI漏洞(及修复方案)

张开发
2026/4/4 22:09:10 15 分钟阅读
从开发到安全:我是如何不小心在Flask里写出了一个SSTI漏洞(及修复方案)
从开发到安全我是如何不小心在Flask里写出了一个SSTI漏洞及修复方案那天下午我正为一个内部管理系统开发用户欢迎功能。需求很简单根据URL参数动态显示用户名称比如访问/welcome?nameJohn会返回Hello John。作为Python开发者我自然选择了熟悉的Flask框架。不到5分钟我就写出了一个看似完美的解决方案from flask import Flask, request from jinja2 import Template app Flask(__name__) app.route(/welcome) def welcome(): username request.args.get(name, Guest) template Template(fHello {username}) return template.render()代码上线后一切运行正常——直到安全团队的同事发来一条消息你的欢迎接口存在严重漏洞能执行任意系统命令。那一刻我才意识到自己无意中打开了一个潘多拉魔盒服务器端模板注入SSTI。1. SSTI漏洞的诞生一个开发者的常见误区1.1 问题出在哪里表面看这段代码只是简单拼接字符串Hello加上用户输入。但关键在于Template(fHello {username})这行直接拼接用户输入将未处理的username直接嵌入模板字符串Jinja2的模板解析机制Flask默认使用Jinja2引擎会解析{{}}中的表达式沙箱逃逸可能Python的魔术方法如__class__可被用来突破安全限制攻击者只需构造特殊输入如/welcome?name{{7*7}}服务端返回Hello 49时就证实了漏洞存在——Jinja2执行了乘法运算。1.2 漏洞升级从表达式计算到命令执行真正的危险在于攻击链的延伸。通过Python对象原型链攻击者可以获取基类对象{{ .__class__.__base__ }}遍历所有子类{{ .__class__.__base__.__subclasses__() }}定位到包含危险函数的类如catch_warnings{{ .__class__.__base__.__subclasses__()[166] }}最终执行任意命令{{ config.__class__.__init__.__globals__[os].popen(rm -rf /).read() }}2. 漏洞原理深度剖析2.1 模板引擎的工作机制现代模板引擎如Jinja2设计时考虑了安全沙箱但开发者不当使用会破坏防护安全机制可能绕过方式自动HTML转义通过非HTML上下文如JS受限方法调用利用对象原型链访问__globals__沙箱环境查找未正确过滤的内置函数2.2 Flask中的危险模式以下代码模式极易引发SSTI# 危险示例1直接字符串拼接 Template(Hello user_input) # 危险示例2未转义的动态模板 render_template_string(user_input) # 危险示例3非常规模板文件加载 render_template(user_controlled_path)3. 修复方案从临时补丁到架构优化3.1 立即补救措施对于已上线系统可采用以下紧急修复from markupsafe import escape app.route(/welcome) def welcome(): username escape(request.args.get(name, Guest)) return fHello {username} # 直接返回字符串不使用Template3.2 中长期解决方案方案实施方式优点缺点静态模板预定义所有模板绝对安全灵活性差严格白名单只允许特定字符平衡安全与灵活需维护规则无逻辑模板使用Mustache等避免代码执行功能受限沙箱环境隔离模板渲染多层防护性能开销推荐的安全编码实践# 安全示例使用Jinja2自动转义 from flask import render_template app.route(/welcome) def welcome(): return render_template(welcome.html, namerequest.args.get(name))对应的welcome.html模板!-- 安全模板示例 -- divHello {{ name }}/div4. 安全开发 checklist每个Python开发者都应定期检查输入验证是否所有用户输入都经过验证是否使用强类型转换如int()过滤数字模板使用是否避免直接拼接模板字符串是否禁用render_template_string依赖管理Jinja2是否保持最新版本是否启用扩展的安全功能监控措施是否记录异常模板渲染是否部署WAF规则过滤__class__等关键词那次事件后我在团队内部建立了代码审查的三不原则不信任任何输入、不直接拼接模板、不留未验证的动态渲染。现在每次写{{}}时都会下意识问自己这个变量真的安全吗

更多文章