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