摘要:讀取新的一行作為模塊名,讀取下一行作為對(duì)象名,然后將壓入到堆棧中。讀取字符串進(jìn)行處理之后壓入堆棧。將一個(gè)元組和一個(gè)可調(diào)用對(duì)象彈出堆棧,然后以該元組作為參數(shù)調(diào)用該可調(diào)用的對(duì)象,最后將結(jié)果壓入到堆棧中。調(diào)用結(jié)束反序列化。
python pickle允許類定義__reduce__方法來(lái)聲明如何進(jìn)行序列化。其返回字符串或者tuple,前者可能代表著一個(gè)python的全局變量的名稱,后者則是描述在反序列化過(guò)程中如何進(jìn)行重構(gòu)。安全問(wèn)題也是主要出在后者,本文主要針對(duì)于該情況進(jìn)行pickle模塊源碼分析。一、源碼分析
代碼結(jié)構(gòu)可以分為:基礎(chǔ)變量、自定義異常類、操作變量、序列化以及反序列化類以及普通函數(shù)。
1.1 基礎(chǔ)變量代碼(28-57行)最先定義了部分變量,如最高協(xié)議號(hào)還有代碼中使用了struct.pack()以及marshal.loads()進(jìn)行序列化和反序列化,并且解釋了為何用這兩個(gè)函數(shù)。
1.2 自定義異常類代碼(59-85行)中自定義了4個(gè)異常類,分別為PickleError、PicklingError、UnpicklingError以及_Stop.
1.3 操作變量PickleError:PickingError和UnpicklingError的基類
PicklingError:序列化過(guò)程中異常
UnpicklingError:反序列化過(guò)程中異常
_Stop:在反序列化過(guò)程中結(jié)尾處觸發(fā)該異常
代碼(99-126行)定義了操作變量,我們可以理解為操作指令,每一個(gè)變量都對(duì)應(yīng)著相關(guān)操作,這些指令在序列化的過(guò)程中寫入,然后在反序列化過(guò)程中讀取進(jìn)行對(duì)應(yīng)操作;我們主要理解如下操作指令。
1.4 序列化以及反序列化類c:讀取新的一行作為模塊名module,讀取下一行作為對(duì)象名object,然后將module.object壓入到堆棧中。
p:將堆棧中索引為-1的對(duì)應(yīng)存儲(chǔ)入內(nèi)存。
(:將一個(gè)標(biāo)記對(duì)象插入到堆棧中。
t:構(gòu)建元組壓入堆棧。
S:讀取字符串進(jìn)行處理之后壓入堆棧。
R:將一個(gè)元組和一個(gè)可調(diào)用對(duì)象彈出堆棧,然后以該元組作為參數(shù)調(diào)用該可調(diào)用的對(duì)象,最后將結(jié)果壓入到堆棧中。
.:調(diào)用_Stop結(jié)束反序列化。
代碼定義了Pickler和Unpickler類,這兩個(gè)類是pickle模塊進(jìn)行序列化反序列化的核心,下面看其實(shí)現(xiàn)過(guò)程:
1.4.1 序列化過(guò)程dumps函數(shù)接收參數(shù)后首先進(jìn)行Pickler類的初始化,然后調(diào)用類中的dump函數(shù)進(jìn)行序列化。
dump()函數(shù)首先調(diào)用save函數(shù),save函數(shù)可以看做字典類型調(diào)度器,key為需要進(jìn)行序列化的對(duì)象的type,value為對(duì)應(yīng)type的存儲(chǔ)函數(shù)名。例如如果序列化對(duì)象為[1, 2, 3],也就是list類型,save函數(shù)判斷完類型之后,在調(diào)度器內(nèi)查找對(duì)應(yīng)的方法save_list,然后調(diào)用結(jié)束后將結(jié)果寫入內(nèi)存中,最后dump函數(shù)寫入結(jié)束符號(hào)完成整個(gè)序列化過(guò)程。
如果上一步查詢調(diào)度器并沒有查詢到對(duì)應(yīng)的方法,即對(duì)象的type不在NoneType/bool/builtin/classobj/dict/float/function/instance/int/list/long/str/tuple/type/unicode這些類型中的時(shí)候,首先查看是否存在__reduce_ex__,如果存在則不再查找__reduce__,不存在的話則繼續(xù)查找__reduce__;進(jìn)而判斷該函數(shù)返回值是string還是tuple,前者進(jìn)入save_global;后者進(jìn)入危險(xiǎn)開始的save_reduce函數(shù)。
save_reduce會(huì)將__reduce__返回的tuple結(jié)果,調(diào)用save_tuple方法進(jìn)行序列化存儲(chǔ)
測(cè)試代碼
import os class A(): def __reduce__(self): a = "whoami" return (os.system, (a,)) print type(A) print dumps(A) print type(A()) print dumps(A()) class B(object): def __reduce__(self): a = "whoami" return (os.system, (a,)) print type(B) print dumps(B) print type(B()) print dumps(B())
測(cè)試結(jié)果:
c__main__ A p0 . (i__main__ A p0 (dp1 b. c__main__ B p0 . cnt system p0 (S"whoami" p1 tp2 Rp3 .
上述結(jié)果可以看出如果我們要達(dá)到執(zhí)行任意代碼的目的,需要使用的是第四種即dumps(B())才能進(jìn)入到save_reduce方法,前三種只能調(diào)用save_global方法,只是對(duì)于命名引用進(jìn)行序列化,所以也只能使用于相同環(huán)境中,否則在反序列化的過(guò)程中會(huì)報(bào)錯(cuò)。
1.4.2 反序列化過(guò)程反序列化和序列化的過(guò)程挺相似,按字節(jié)讀取然后在調(diào)度器中查找對(duì)應(yīng)的處理函數(shù);上面我們提到過(guò)一些操作指令,反序列化的調(diào)度器中將上述的操作指令作為key,處理函數(shù)作為value,此處主要分析上述最后一個(gè)實(shí)例的反序列化過(guò)程。
序列化的結(jié)果:
cnt system p0 (S"whoami" p1 tp2 Rp3 .
反序列化過(guò)程:
讀取第一個(gè)字符c,查詢調(diào)度器,對(duì)應(yīng)的方法為load_global;
調(diào)用load_global,讀取該行將該行后面nt作為模塊名,下一行system作為方法名,兩者作為參數(shù)進(jìn)入find_class;
find_class的目的就是返回nt.system方法,然后將返回結(jié)果壓入堆棧中;該方法的代碼如下所示:
def find_class(self, module, name): __import__(module) mod = sys.modules[module] klass = getattr(mod, name) return klass
總結(jié)繼續(xù)讀取字節(jié)p,調(diào)用load_put,load_put從堆棧中獲取最后一個(gè)對(duì)象,放入內(nèi)存中,p后面的數(shù)字,為key;
繼續(xù)讀取字節(jié)(,調(diào)用load_mark,將object()壓入堆棧;
繼續(xù)讀取字節(jié)S,調(diào)用load_string,將"whoami"去除" "壓入堆棧;
到目前堆棧中有3個(gè)對(duì)象,分別為nt.system、object()、whoami;繼續(xù)讀取p,將whoami存儲(chǔ)到內(nèi)存中,key為1;
讀取字節(jié)t,調(diào)用load_tuple,其首先調(diào)用marker獲取object()的索引號(hào),此處為1,然后將stack[1:]變?yōu)?"whoami",),也就是說(shuō)執(zhí)行完這一步操作之后,堆棧中只有nt.system和("whoami",);
讀取字節(jié)p,調(diào)用load_put,將("whoami",)存儲(chǔ)如內(nèi)存,key為2,;
讀取字節(jié)R,調(diào)用load_reduce,運(yùn)行nt.system("whoami"),得出結(jié)果之后賦值給堆棧索引為-1;
讀取p,調(diào)用load_put,將結(jié)果存儲(chǔ)到內(nèi)存;
讀取.,即結(jié)束符號(hào),清空堆棧,結(jié)束反序列化。
序列化以及反序列化其實(shí)是給每種能夠識(shí)別出來(lái)的類型的對(duì)象一個(gè)既定的方式去進(jìn)行序列化或者反序列化,如果碰到不認(rèn)識(shí)的,那就去查找__reduce__,將其序列化,然后在根據(jù)它去進(jìn)行反序列化過(guò)程中的重構(gòu);
從上面的分析過(guò)程中可以看出,如果我們要在反序列化的過(guò)程中去執(zhí)行命令,就要滿足在序列化的時(shí)候能執(zhí)行save_reduce,然后在反序列化的過(guò)程中才能執(zhí)行l(wèi)oad_reduce,進(jìn)而執(zhí)行命令;
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41376.html
摘要:讀取新的一行作為模塊名,讀取下一行作為對(duì)象名,然后將壓入到堆棧中。讀取字符串進(jìn)行處理之后壓入堆棧。將一個(gè)元組和一個(gè)可調(diào)用對(duì)象彈出堆棧,然后以該元組作為參數(shù)調(diào)用該可調(diào)用的對(duì)象,最后將結(jié)果壓入到堆棧中。調(diào)用結(jié)束反序列化。 python pickle允許類定義__reduce__方法來(lái)聲明如何進(jìn)行序列化。其返回字符串或者tuple,前者可能代表著一個(gè)python的全局變量的名稱,后者則是描...
摘要:反序列化安全問(wèn)題一這一段時(shí)間使用做開發(fā),使用了存儲(chǔ),閱讀了源碼,發(fā)現(xiàn)在存儲(chǔ)到過(guò)程中,利用了模塊進(jìn)行序列化以及反序列化正好根據(jù)該樣例學(xué)習(xí)一波反序列化相關(guān)的安全問(wèn)題,不足之處請(qǐng)各位表哥指出。 Python 反序列化安全問(wèn)題(一) 這一段時(shí)間使用flask做web開發(fā),使用了redis存儲(chǔ)session,閱讀了flask_session源碼,發(fā)現(xiàn)在存儲(chǔ)session到redis過(guò)程中,利用了...
摘要:據(jù)公告稱,和的包裝庫(kù)使用了不安全的函數(shù)來(lái)反序列化編碼的機(jī)器學(xué)習(xí)模型。簡(jiǎn)單來(lái)看,序列化將對(duì)象轉(zhuǎn)換為字節(jié)流。據(jù)悉,本次漏洞影響與版本,的到版本均受影響。作為解決方案,在宣布棄用之后,團(tuán)隊(duì)建議開發(fā)者以替代序列化,或使用序列化作為替代。 ...
閱讀 2789·2021-11-02 14:42
閱讀 3172·2021-10-08 10:04
閱讀 1193·2019-08-30 15:55
閱讀 1035·2019-08-30 15:54
閱讀 2327·2019-08-30 15:43
閱讀 1688·2019-08-29 15:18
閱讀 871·2019-08-29 11:11
閱讀 2370·2019-08-26 13:52