反序列化备忘单¶
简介¶
本文旨在为在应用程序中安全反序列化不可信数据提供清晰、可操作的指导。
什么是反序列化¶
序列化是将某个对象转换为以后可以恢复的数据格式的过程。人们通常序列化对象以便存储它们,或者作为通信的一部分发送。
反序列化是该过程的逆过程,它获取以某种格式构造的数据,并将其重建为一个对象。如今,最流行的数据序列化格式是 JSON。在此之前是 XML。
然而,许多编程语言都有原生的对象序列化方式。这些原生格式通常比 JSON 或 XML 提供更多功能,包括序列化过程的定制。
不幸的是,当操作不可信数据时,这些原生反序列化机制的功能有时可能被重新利用以达到恶意目的。针对反序列化器的攻击已被发现可导致拒绝服务、访问控制或远程代码执行(RCE)攻击。
安全反序列化对象的指南¶
以下针对特定语言的指南旨在列举用于反序列化不可信数据的安全方法。
PHP¶
白盒审查¶
检查 unserialize()
函数的使用情况,并审查外部参数的接收方式。如果需要将序列化数据传递给用户,请使用安全的标准数据交换格式,例如 JSON(通过 json_decode()
和 json_encode()
)。
Python¶
黑盒审查¶
如果流量数据末尾包含点符号 .
,则数据很可能以序列化形式发送。只有当数据未使用 Base64 或十六进制编码时,这种情况才成立。如果数据正在被编码,那么最好通过查看参数值的起始字符来检查序列化是否可能发生。例如,如果数据是 Base64 编码的,那么它很可能以 gASV
开头。
白盒审查¶
Python 中的以下 API 将容易受到序列化攻击。请搜索以下代码模式。
pickle/c_pickle/_pickle
与load/loads
的使用
import pickle
data = """ cos.system(S'dir')tR. """
pickle.loads(data)
PyYAML
与load
的使用
import yaml
document = "!!python/object/apply:os.system ['ipconfig']"
print(yaml.load(document))
jsonpickle
与encode
或store
方法的使用。
Java¶
以下技术都适用于防止针对 Java Serializable 格式的反序列化攻击。
实施建议
- 在您的代码中,重写
ObjectInputStream#resolveClass()
方法以防止任意类被反序列化。这种安全行为可以封装在一个库中,例如 SerialKiller。 - 使用此处所示的通用
readObject()
方法的安全替代方案。请注意,这通过检查输入长度和反序列化对象的数量来解决“十亿笑声”类攻击。
白盒审查¶
请注意以下 Java API 使用情况,它们可能存在序列化漏洞。
1. 使用外部用户定义参数的 XMLdecoder
2. 使用 fromXML
方法的 XStream
(xstream 版本 <= v1.4.6 易受 序列化问题攻击)
3. 使用 readObject
的 ObjectInputStream
4. readObject
, readObjectNoData
, readResolve
或 readExternal
的使用
5. ObjectInputStream.readUnshared
6. Serializable
黑盒审查¶
如果捕获的流量数据包含以下模式,则可能表明数据是以 Java 序列化流的形式发送的
- 十六进制表示的
AC ED 00 05
- Base64 表示的
rO0
- HTTP 响应的
Content-type
头设置为application/x-java-serialized-object
防止数据泄露和可信字段覆盖¶
如果对象的某些数据成员在反序列化期间不应由最终用户控制,或在序列化期间不应暴露给用户,则应将它们声明为 transient
关键字(保护敏感信息 部分)。
对于定义为 Serializable 的类,敏感信息变量应声明为 private transient
。
例如,在类 myAccount
中,变量 'profit' 和 'margin' 被声明为 transient,以防止它们被序列化。
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
....
防止领域对象反序列化¶
由于其继承关系,您的一些应用程序对象可能被迫实现 Serializable
。为了确保您的应用程序对象不能被反序列化,应该声明一个(带 final
修饰符的)readObject()
方法,该方法始终抛出异常
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
加固您自己的 java.io.ObjectInputStream¶
java.io.ObjectInputStream
类用于反序列化对象。可以通过子类化来加固其行为。如果满足以下条件,这是最佳解决方案:
- 您可以更改执行反序列化的代码;
- 您知道期望反序列化哪些类。
一般思路是重写 ObjectInputStream.html#resolveClass()
以限制允许反序列化的类。
因为此调用发生在 readObject()
被调用之前,所以您可以确保除非类型是您允许的类型,否则不会发生反序列化活动。
这里展示了一个简单示例,其中 LookAheadObjectInputStream
类保证除了 Bicycle
类之外,不反序列化任何其他类型。
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
这种方法的更完整实现已由各个社区成员提出
- NibbleSec - 一个允许创建允许反序列化类列表的库
- IBM - 奠基性的保护措施,在最具破坏性的利用场景被设想出来之前多年就已编写。
- Apache Commons IO 类
使用代理加固所有 java.io.ObjectInputStream 用法¶
如上所述,java.io.ObjectInputStream
类用于反序列化对象。可以通过子类化来加固其行为。但是,如果您不拥有代码或无法等待补丁,那么使用代理将加固措施织入 java.io.ObjectInputStream
是最佳解决方案。
全局修改 ObjectInputStream
仅对黑名单中已知的恶意类型是安全的,因为不可能知道所有应用程序预期要反序列化的类。幸运的是,目前黑名单中只需要极少数类就能免受所有已知攻击向量的威胁。
不可避免的是,未来将发现更多可被滥用的“小工具”类。然而,目前暴露了大量需要修复的易受攻击软件。在某些情况下,“修复”漏洞可能涉及重新架构消息系统并破坏向后兼容性,因为开发人员正在转向不接受序列化对象。
要启用这些代理,只需添加一个新的 JVM 参数
-javaagent:name-of-agent.jar
采用此方法的代理已由各个社区成员发布
一种类似但可伸缩性较差的方法是手动修补并引导您的 JVM 的 ObjectInputStream。有关此方法的指南可在此处获取。
其他反序列化库和格式¶
虽然上述建议主要集中在 Java 的 Serializable 格式上,但还有许多其他库使用不同的格式进行反序列化。如果配置不当,其中许多库可能存在类似的安全问题。本节列出了一些此类库以及建议的配置选项,以避免在反序列化不可信数据时出现安全问题。
在默认配置下可以安全使用
以下库可以在默认配置下安全使用
- fastjson2 (JSON) - 只要不开启autotype 选项,就可以安全使用
- jackson-databind (JSON) - 只要不使用多态性,就可以安全使用(参见博客文章)
- Kryo v5.0.0+ (自定义格式) - 只要类注册不被关闭(参见文档 和 此问题),就可以安全使用
- YamlBeans v1.16+ (YAML) - 只要不使用 UnsafeYamlConfig 类,就可以安全使用(参见 此提交)
- 注意:由于这些版本在 Maven Central 中不可用,因此存在一个可替代使用的分支。
- XStream v1.4.17+ (JSON 和 XML) - 只要允许列表和其他安全控制未被放松(参见文档),就可以安全使用。
需要配置后才能安全使用
以下库需要设置配置选项才能安全使用
- fastjson v1.2.68+ (JSON) - 除非开启 safemode 选项(这将禁用任何类的反序列化),否则无法安全使用(参见文档)。早期版本不安全。
- json-io (JSON) - 无法安全使用,因为 JSON 中 @type 属性的使用允许反序列化任何类。仅在以下情况下可以安全使用:
- 在非类型化模式下,使用 JsonReader.USE_MAPS 设置,该设置会关闭通用对象反序列化。
- 使用自定义反序列化器控制哪些类被反序列化
- Kryo < v5.0.0 (自定义格式) - 除非开启类注册(这将禁用任何类的反序列化),否则无法安全使用(参见文档 和 此问题)
- 注意:Kryo 还存在其他包装器,例如 Chill,无论底层 Kryo 版本如何,它们也可能默认不需要类注册。
- SnakeYAML (YAML) - 除非使用 org.yaml.snakeyaml.constructor.SafeConstructor 类(这将禁用任何类的反序列化),否则无法安全使用(参见文档)
无法安全使用
以下库要么不再维护,要么无法与不可信输入安全使用
- Castor (XML) - 自 2016 年以来没有提交,似乎已被放弃
- fastjson < v1.2.68 (JSON) - 这些版本允许反序列化任何类(参见文档)
- JDK 中的 XMLDecoder (XML) - “几乎不可能从不可信输入中安全地反序列化这种格式的 Java 对象”(“红帽防御性编码指南”,第 2.6.5 节末尾)
- XStream < v1.4.17 (JSON 和 XML) - 这些版本允许反序列化任何类(参见文档)
- YamlBeans < v1.16 (YAML) - 这些版本允许反序列化任何类(参见此文档)
.Net CSharp¶
白盒审查¶
在源代码中搜索以下术语
TypeNameHandling
JavaScriptTypeResolver
查找任何类型由用户控制变量设置的序列化器。
黑盒审查¶
搜索以下以 Base64 编码开头的内容
AAEAAAD/////
搜索包含以下文本的内容
TypeObject
$type
一般注意事项¶
Microsoft 已声明 BinaryFormatter
类型是危险的,无法保证安全。因此,不应使用它。完整详情请参见 BinaryFormatter 安全指南。
不要允许数据流定义该流将反序列化为的对象的类型。例如,如果可能的话,您可以通过使用 DataContractSerializer
或 XmlSerializer
来防止这种情况发生。
在使用 JSON.Net
的地方,确保 TypeNameHandling
仅设置为 None
。
TypeNameHandling = TypeNameHandling.None
如果要使用 JavaScriptSerializer
,则不要将其与 JavaScriptTypeResolver
一起使用。
如果您必须反序列化定义其自身类型的数据流,则限制允许反序列化的类型。应该注意的是,这仍然具有风险,因为许多原生 .Net 类型本身就可能存在危险。例如:
System.IO.FileInfo
引用服务器上实际文件的 FileInfo
对象在反序列化时,可以更改这些文件的属性,例如设置为只读,从而造成潜在的拒绝服务攻击。
即使您限制了可以反序列化的类型,也要记住某些类型具有危险的属性。例如,System.ComponentModel.DataAnnotations.ValidationException
有一个 Object
类型的 Value
属性。如果此类型是允许反序列化的类型,则攻击者可以将 Value
属性设置为他们选择的任何对象类型。
必须阻止攻击者操纵将被实例化的类型。如果这成为可能,那么即使 DataContractSerializer
或 XmlSerializer
也可以被颠覆,例如:
// Action below is dangerous if the attacker can change the data in the database
var typename = GetTransactionTypeFromDatabase();
var serializer = new DataContractJsonSerializer(Type.GetType(typename));
var obj = serializer.ReadObject(ms);
反序列化期间,某些 .Net 类型中可能发生执行。创建如下所示的控件是无效的。
var suspectObject = myBinaryFormatter.Deserialize(untrustedData);
//Check below is too late! Execution may have already occurred.
if (suspectObject is SomeDangerousObjectType)
{
//generate warnings and dispose of suspectObject
}
对于 JSON.Net
,可以使用自定义的 SerializationBinder
创建一种更安全的允许列表控制形式。
尝试及时了解已知的 .Net 不安全反序列化小工具,并特别注意您的反序列化过程可以创建此类类型的情况。反序列化器只能实例化它所知道的类型。
尝试将任何可能创建潜在小工具的代码与具有互联网连接的代码分开。例如,WPF 应用程序中使用的 System.Windows.Data.ObjectDataProvider
是一个已知的小工具,允许任意方法调用。在反序列化不可信数据的 REST 服务项目中引用此程序集将是危险的。
已知 .NET RCE 小工具¶
System.Configuration.Install.AssemblyInstaller
System.Activities.Presentation.WorkflowDesigner
System.Windows.ResourceDictionary
System.Windows.Data.ObjectDataProvider
System.Windows.Forms.BindingSource
Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider
System.Data.DataViewManager, System.Xml.XmlDocument/XmlDataDocument
System.Management.Automation.PSObject
与语言无关的安全反序列化方法¶
使用替代数据格式¶
通过避免使用原生(反)序列化格式,可以大大降低风险。通过切换到像 JSON 或 XML 这样的纯数据格式,您可以减少自定义反序列化逻辑被重新用于恶意目的的可能性。
许多应用程序依赖于数据传输对象模式,该模式涉及创建一个单独的对象领域,专门用于数据传输。当然,即使解析了纯数据对象,应用程序仍然可能犯安全错误。
仅反序列化签名数据¶
如果应用程序在反序列化之前知道哪些消息需要处理,它们可以在序列化过程中对这些消息进行签名。然后,应用程序可以选择不反序列化任何没有经过身份验证签名的消息。
缓解工具/库¶
检测工具¶
- 面向渗透测试人员的 Java 反序列化备忘单
- 一个用于生成利用不安全 Java 对象反序列化漏洞的有效载荷的概念验证工具。
- Java 反序列化工具包
- Java 反序列化工具
- .Net 有效载荷生成器
- Burp Suite 扩展
- Java 安全反序列化库
- Serianalyzer 是一个用于反序列化的静态字节码分析器
- 有效载荷生成器
- Android Java 反序列化漏洞测试器
- Burp Suite 扩展
参考资料¶
- Java-Deserialization-Cheat-Sheet
- 不可信数据的反序列化
- Java 反序列化攻击 - 2016 年德国 OWASP 日
- AppSecCali 2015 - Marshalling Pickles
- FoxGlove Security - 漏洞公告
- 面向渗透测试人员的 Java 反序列化备忘单
- 一个用于生成利用不安全 Java 对象反序列化漏洞的有效载荷的概念验证工具。
- Java 反序列化工具包
- Java 反序列化工具
- Burp Suite 扩展
- Java 安全反序列化库
- Serianalyzer 是一个用于反序列化的静态字节码分析器
- 有效载荷生成器
- Android Java 反序列化漏洞测试器
- Burp Suite 扩展
- .Net
- Python