反序列化漏洞通常需要两个条件:
1、用户可控的反序列化入口
例如 PHP
的 unserialize()
、Java
的 readObject()
。
2、运行环境中存在调用了危险函数的 magic function
例如 PHP
的 __wakup()
、 __destruct()
以及 Java
的 readObject()
。
满足这两个条件的前提下,我们构造第二个条件的对象(也就是 Gadget
),并将其序列化后传递给第一个条件的入口,就可以成功触发反序列化漏洞了。
相对而言,第二个条件的利用更难,所以就诞生了 ysoserial
和 marshalsec
这样的 Gadget 生成器。
不过,对于 python
而言,反序列化漏洞的利用就简单多了,因为,python
的反序列化Gadget
不需要存在于原有的运行环境中,而是可以通过序列化数据直接传递。
看个例子。
代码将Test类的对象序列化到payload
文件中,其中在 magic function reduce()
中注入了恶意命令 ls
。接下来是反序列化。
看看结果。
可以看到,序列化到 payload
中的命令 ls
被成功地执行了。
因此,python
的反序列化漏洞利用,只需要满足第一个条件“用户可控的反序列化入口”就好了。
现在来看看 numpy
的这个 CVE。numpy
是非常流行的用于科学计算的python
开源库,包括TensorFlow
在内的许多项目都使用了 numpy
。
numpy
提供了一个接口 numpy.load()
,定义长这样:
函数里首先打开 file
文件,赋值给 fid
随后判断文件头。当文件头既不满足 zip
格式也不满足 numpy
格式时,numpy
直接做了一个操作:反序列化。
也就是说,只要我们将恶意的序列化内容传递给 numpy.load()
函数,就可以触发这个漏洞。
运行结果是:
成功执行了在序列化文件 payload
中注入的 ls
命令。
首先,一个通用的原则:不要对不可信的数据进行反序列化。
其次,就 numpy
的这个 CVE 而言,可以注意到在进行反序列化之前有一个判断:allow_pickle
。
allow_pickle
其实是 numpy.load()
的第三个参数,可选,默认为True
。
只要在调用 numpy.load()
的时候,将 allow_pickle
置为 False
就可以避免反序列化操作了。