0x00 前言
之前学过有关Pickle的反序列化问题,但是好记心不如烂笔头,何况我还没有好记心…所以,过去这么久了,我都忘得差不多了,悲剧ε(┬┬﹏┬┬)3, 今天就及时的补上把。
本篇包含以下元素:
- Python中Pickle的介绍
- Pickle反序列化的简单了解
这里就不写实践了,我现在打算每次写了一个东西,下一篇专门写一个实践;过去的懒狗总是不太重视题目的实践,导致虽然学过,但遇到了还是无从下手,久而久之连理论也忘记了。学如逆水行舟,不进则退。大家做什么事情,还是要始终如一的,😋共勉
0x01 Pickle
简单介绍
Pickle是Python中用来序列化的一个模块,但是除此之外还有其他的模块,只是Pickle使用的比较普遍,且有些序列化模块在无法正常序列化或反序列化时会尝试去调用Pickle。
简单的使用
如果是保存为文件,我们可以使用dump()
和load
分别来序列化和反序列化;如果是转换为字节流,我们可以使用dumps
和loads
:
import pickle
import os
class Chenlvtang:
name = ""
id = 123
def __init__(self, name, id):
self.name = name
self.id = id
def printf(self):
print("Hello")
if __name__ == "__main__":
chen = Chenlvtang("chenlvtang", 8003119052)
#file
with open("seri_test", "wb") as file:
pickle.dump(chen, file) #serialize
with open("seri_test", "rb") as file:
c = pickle.load(file) #unserialize
c.printf()
#bytes
seri_test = pickle.dumps(chen)
print("String Serialize: \n")
print(seri_test)
c = pickle.loads(seri_test)
c.printf()
简单的原理
上面的程序输出一个经Pickle处理过后的字符串,如下:
b'\x80\x03c__main__\nChenlvtang\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\n\x00\x00\x00chenlvtangq\x04X\x02\x00\x00\x00idq\x05\x8a\x05\xcc\xe7\x05\xdd\x01ub.'
“我看不懂,但是我大受震惊”,隐约还是能看到”name”和”chenlvtang”这种的,有点类似于PHP的序列化,但是还有一些奇奇怪怪的东西。其实这些看不懂的东西,是Pickle的OPCode(Operation Code),即操作码,顾名思义,就是会完成一些操作。我们可以在pickle.py (位于Python的lib文件夹中) 中看到关于这些操作码的详解:
可以看到,它还是有多版本的,新版本大概是添加了新的功能,所以他是向下兼容的,并且它的第一个版本(protocol 0)采用的都是可见字符,在之后的版本中才加入了\x8c
这种不可见字符。在源码中我们还能发现Pickle的load(s)和dump(s)分别由_Unpickler
和_pickler
类中的对应方法实现:
def _dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None):
_Pickler(file, protocol, fix_imports=fix_imports,
buffer_callback=buffer_callback).dump(obj)
def _load(file, *, fix_imports=True, encoding="ASCII", errors="strict",
buffers=None):
return _Unpickler(file, fix_imports=fix_imports, buffers=buffers,
encoding=encoding, errors=errors).load()
如果继续跟进上面两个类,还可以发现他们采用了两个重要的数据结构:栈和存储区(标签区),Pickle正是通过这个两个重要的结构与OPCode,从而实现了序列化和反序列化,我们将在后文更详细的介绍这两个结构。
现在为了更加深入的了解Python反序列化的原理,我们下面就先来了解并熟悉一下这些OPCode吧。
0x02 OPCode
啰嗦一下
上面说到了,OPCode是有多个协议版本的,但是它向下兼容且第一个版本采用的都是可见字符,所以为了便于我们分析,我们这里就基于第一个版本(V0)来学习。但是不同Python版本,所采用的默认OPCode版本不同,那么我们要怎么指定版本呢?其实很简单,只需要在序列化函数中指定就好了,如:dumps(chen, 0)
即使指定了初始版本。
pickletools
改变版本后,我们得到了如下的字符串:
b'ccopy_reg\n_reconstructor\np0\n(c__main__\nChenlvtang\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nVname\np6\nVchenlvtang\np7\nsVid\np8\nL8003119052L\nsb.'
可能大家还是会觉得,“虽然比之前更加的易懂了,但如果不去源码里一个个对照操作码,还是看不懂,源码又不想看,看又看不懂,只能不看这样子才能维持的下去~”。确实,还好Python贴心的为我们这种懒狗准备了一个工具——“pickletools”。
seri_test = pickle.dumps(chen, 0)
print("String Serialize: \n")
print(seri_test)
pickletools.dis(seri_test) #使用dis即可反汇编
得到的结果如下图所示:
上文提到了,pickle是通过栈和存储区两个重要的结构与OPCode的配合实现了序列化,在开始详细介绍每个操作码的具体作用前,我们有必要先了解这两种结构。
栈和存储区
栈(Stack): 栈分为当前栈和前序栈(metastack),当前栈专门用来处理栈顶数据,前序栈则是处理除栈顶外的数据。
存储区(memo):用来存储经过处理完后的变量,采用类似于数组的形式,用索引来取得值。
操作码
在大概了解了这两种结构后,我们便来细细的探究V0版本之下一些常见的操作码具体含义:
.
:用来表示反序列化的结束I S V
: 一般大写的字母都是表示某个数据类型,这里我只指出常见的三种,其实还有其他一些。 I 表示整数,S表示字符串,V表示Unicode字符,后接详细数据,换行符\n
表示数据的结束, 然后数据会被压入栈中,例子如下(注意最后有个结束符):(
:向栈中压入一个特殊的Mark Object,作为后面创建元组、字典、列表的标记t、d、l
:分别为创建元组、字典、列表。创建时,先弹栈,直到遇到上面(
压入栈中的Mark,此时创建结束,弹出Mark后,把创建好的元组(字典、列表)压入栈中,一张经典动图如下:实例如下:
注意上面创建字典的时候,必须是key,value这样的属性依次出现。
]、}、)
:分别往栈中压入空的列表、字典、元组。s、u、a、e
:这几个都是对创建的列表等(除元组,因为元组一旦创建,不允许修改)的操作。s为往空字典中添加由栈顶端两个元素构成的一对键值(如果键相同,会更新值),相当于dict[key] = value
;u
为更新多个键值,要配合之前的Mark来使用,相当于pydict[keyi] = valuei for i in 1, 2, ..., n
;a
为往列表中更新一个元素,相当于list.append(value)
;e
为向列表更新多个元素,和u一样也要搭配Mark来使用。实例如下:c
:通过调用find_class方法将一个全局object压入栈中,需要两个参数,第一个参数为模块名,第二参数为类名,同样的,是以换行符来界定参数是否结束,实例如下:p
:把栈顶元素放入memo,索引的类型是stringg
: 把存入memo的变量取出,索引类型是stringR
:弹出栈顶的元组(arg1,arg2…)为参数,再弹出新栈顶的object,组成object(arg1,arg2..)执行,然后把执行结果压回栈中,当使用了__reduce__
方法(这个方法会告诉pickle反序列化时做什么,相当于PHP中的__wakeup, 要在类中使用)会生成R指令,实例如下:b
: 用栈顶的字典给栈顶下的Object(即第二个元素)的某个属性更新值,实例如下:i、o
: 创建实例,但是传参方式有点区别。i 是以其后的一个元素为模块名,第二个为类或方法名,然后将从mark开始的元素作为参数,实例如下:o则是以mark后的一个元素为Object(常用c引入),之后的所有为参数,实例如下:
懒狗的结尾
就先学到这里吧,一开始得到的那一坨,你现在应该看得懂了,我懒得重新分析了。😴 下一篇文章里我们将来学习反序列化漏洞的利用
0x03 参考文章
Python pickle 反序列化安全简述 - X5tar’s Blog
Python Pickle反序列化漏洞 - FreeBuf网络安全行业门户
从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势 - 知乎 (zhihu.com)