
在之前的一篇博文中,我们看了一下Java的自定义序列化平台以及其安全影响。而最近,我写了关于Java 17的改进如何帮助你防止不安全的反序列化。然而,现在人们已经不那么依赖Java的自定义序列化了,而是选择使用JSON。JSON是最广泛的数据序列化格式,它是人类可读的,而且不是Java特有的。
最常用的库之一是jackson-databind ,它为你提供了一个ObjectMapper ,将你的对象转化为JSON,反之亦然。
由于它是一个流行的库,所以非常有必要知道,在过去的几年里,jackson-databind 库已经有许多报告的漏洞。尽管如此,这并不意味着使用Jackson ObjectMapper默认会有安全风险。这篇文章将解释Jackson反序列化漏洞是如何工作的,以及如何确保你不被它们影响。
但在我们走得太远之前,让我们先回答你可能有的几个问题...
什么是反序列化?
反序列化是将数据从文件或流中转化回一个对象,以便在你的应用程序中使用。这可以是二进制数据或结构化数据,如JSON和XML。反序列化是与序列化相反的,序列化将对象转化为字节流或结构化文本。
Jackson ObjectMapper是用来做什么的?
Jackson Objectmapper是Jacksondatabind 库的一部分,用于将JSON转换成Java对象,反之亦然。它是Java生态系统中最常用和最知名的库,用于将JSON转换为Java对象,并自动与Spring Boot一起提供。
Jackson Objectmapper安全吗?
是的,Jackson Objectmapper是安全的,并且使用安全默认值。你必须更新到最新的版本,以避免已知的安全问题。
创建一个 ObjectMapper 是否很昂贵?
创建一个ObjectMapper是相当昂贵的。因此,建议你重复使用你的ObjectMapper实例。Jackon ObjectMapper是线程安全的,所以它可以安全地被重复使用。
使用Jackson ObjectMapper从JSON创建Java对象
通过下面的代码,我们可以创建一个ObjectMapper ,并使用它从一个来自文件的JSON字符串中重新创建一个Person 。
ObjectMapper om = new ObjectMapper();
Person myvalue = om.readValue(Files.readAllBytes(Paths.get("person.json")), Person.class);
System.out.println("name:"+myvalue.name+" \n"+"Age:"+myvalue.age);
这一切都很直接,没有什么真正花哨的事情会发生。没有任何注释,Jackson ObjectMapper使用反射来完成POJO映射。因为反射,它对所有字段都有效,而不考虑访问修改器。如果有可用的getters和setters,Jackson ObjectMapper将使用它来做映射。
杰克逊中的默认类型
用于JSON序列化的Jackson库的许多漏洞都取决于默认类型,默认情况下不启用该类型。你需要明确地启用它。这意味着在大多数情况下,这个列表中的漏洞不会直接影响你的系统。
但让我们解释一下什么是默认类型,以及它的用途。
默认类型是Jackson ObjectMapper中处理多态类型和继承的一种机制。如果你想把JSON反序列化为一个Java POJO,但不确定该对象或字段是什么子类型,你可以简单地反序列化为超类。
比如你有Coffee 和Tea 。这两个类都有相同的超类HotDrink 。因此,如果你的Breakfast 包含一个HotDrink ,但你不知道它是Coffee 还是Tea ,你可以使用默认类型来解决这个问题。
public class Breakfast {
public String food;
public HotDrink drink;
}
public abstract class HotDrink {
public String name;
}
public class Coffee extends HotDrink {
@Override
public String toString() {
return String.format("Coffee{name='%s'}", name);
}
}
public class Tea extends HotDrink {
@Override
public String toString() {
return String.format("Tea{name='%s'}", name);
}
}
String breakfastJson = """
{
"food":"sandwich",
"drink":["nl.brianvermeer.example.jackson.serialization.Tea",{"name":"oolong"}]
}
""";
var om = new ObjectMapper();
om.enableDefaultTyping();
var myBreakfast = om.readValue(breakfastJson, Breakfast.class);
System.out.println("breakfast hotdrink:"+myBreakfast.drink);
在这个例子中,我在ObjectMapper上启用了默认类型,这样我就可以在使用这个ObjectMapper 的地方处理多态性。你也可以使用@JsonTypeInfo 注释在一个特定的字段上这样做。
Jackson ObjectMapper上默认类型的安全问题
因此,如果全局启用默认类型,就有可能将继承性发挥到极致。如果你的Breakfast 不包含一个HotDrink ,而是一个类型为Object 的字段,那么任何对象都可以在classpath上获得。这也意味着我们可以反序列化任何在classpath上可用的对象。有可能,这可以是一个小工具对象,它建立了一个小工具链,最终以远程执行结束。
这些小工具链与我的《Java中的序列化和反序列化》博文中描述的小工具链非常相似。让我们用一个单一的小工具来简化,这个小工具在初始化时立即执行一个命令。我的SecondBreakfast 类吹,包含一个类型为Object 的饮料。
public class Gadget {
private Runnable command;
public Gadget(String value) {
this.command = new Command(value);
this.command.run();
}
}
public class SecondBreakfast {
public String food;
public Object drink;
}
如果我在启用默认类型的情况下反序列化我的SecondBreakfast ,我可以反序列化一个包含任意代码执行的Object 。
String secondBreakfastJson = """
{
"food":"sandwich",
"drink":["nl.brianvermeer.example.jackson.serialization.Gadget", "rm -rf *"]
}
""";
var om = new ObjectMapper();
om.enableDefaultTyping();
Var mySecondBreakfast = om.readValue(secondBreakfastJson, SecondBreakfast.class);
System.out.println("Second breakfast hotdrink:"+mySecondBreakfast.drink);
现在这只是一个简化的例子。但是,虽然寻找和创建一个小工具链并不容易,但它绝对是可能的。因为我们使用的所有库和框架,有可能你的classpath中的所有类的组合可以用来创建这样一个小工具链。
而且,已经有一大批著名的 "讨厌的类 "被识别出来。这样的类的反序列化被认为是危险的,基本上遵循上述的模式。作为参考,请查看Snyk漏洞数据库上的jackson-databind 库的反序列化漏洞列表
这对我的应用程序有什么影响?
好吧,不要惊慌,因为它没有那么糟糕。首先,jackson-databind 库的维护者积极阻止SubTypeValidator 中的一组 "讨厌的类"。其次,你需要明确启用默认类型。这意味着在默认情况下,这个设置是关闭的,多态反序列化甚至是不可能的。
用SCA工具(如Snyk)进行扫描,确实在扫描结果中显示了这个漏洞。快速搜索显示,过去有很多这样的问题。更新到最新的版本总是明智的,因为这个库维护得很好,新的 "讨厌的类 "一旦被发现就会被主动阻止。尽管如此,最好的办法是防止启用你的杰克逊ObjectMapper 的默认键入。
Snyk分流助手来拯救
如果你的代码不符合前提条件,Snyk目前正在努力过滤这些漏洞。对于Jackson ObjectMapper来说,这意味着,如果你的代码没有启用多态类型,我们将向你表明,这个特定的漏洞不太可能利用你。在写这篇文章的时候,这个功能只针对Jackson反序列化漏洞发布,但团队继续努力改进和扩展这个功能。要使用这个功能,我们需要批准扫描你的代码,以检查你是否没有启用默认键入。

这篇博文中使用的代码例子发布在我的GitHub账户上。欢迎以任何你喜欢的方式分叉或重复使用这些例子。
安全地进行反序列化!
避免Jackson ObjectMapper的反序列化问题的最好方法是防止多态类型化。请不要为你的ObjectMapper启用默认类型。另外,将你的项目连接到Snyk,以了解你是否在使用一个有已知漏洞的jackson-databind 库。在大多数情况下,你可以很容易地用一个较新的版本来替换它。当你启用代码扫描时,Triage Assistant可以帮助你确定它是可能还是不可能被利用。