Java代码审计之Jackson序列化和反序列化

Java代码审计之Jackson序列化和反序列化

注:本文为复现文和学习文,原创极少,参考了大量参考链接里的内容~感兴趣者自行阅读原作

0x01 序列化和反序列化

Jackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。

Jackson提供了ObjectMapper.writeValueAsString()ObjectMapper.readValue()两个方法来实现序列化和反序列化的功能。

Person.java

public class Person {
    public int age;
    public String name;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s", age, name);
    }
}

JSTest.java

package com.DemoJackson.Study.Demo;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        ObjectMapper mapper = new ObjectMapper();

        // 序列化
        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        // 反序列化
        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

结果

{"age":6,"name":"mi1k7ea"}
Person.age=6, Person.name=mi1k7ea

image-20211029172644720

0x02 多态 JacksonPolymorphicDeserialization

DefaultTyping和@JsonTypeInfo注解

1. DefaultTyping

public static enum DefaultTyping {
  JAVA_LANG_OBJECT,
  OBJECT_AND_NON_CONCRETE,
  NON_CONCRETE_AND_ARRAYS,
  NON_FINAL;

  private DefaultTyping() {
  }
}

DefaultTyping的几个设置选项是逐渐扩大适用范围的,如下表:

DefaultTyping类型 描述说明
JAVA_LANG_OBJECT 属性的类型为Object
OBJECT_AND_NON_CONCRETE 属性的类型为Object、Interface、AbstractClass
NON_CONCRETE_AND_ARRAYS 属性的类型为Object、Interface、AbstractClass、Array
NON_FINAL 所有除了声明为final之外的属性

1-1. JAVA_LANG_OBJECT

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

Hacker.java

package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;

public class Hacker {
    public String skill = "Jackson";
}

Person.java

package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;

public class Person {
    public int age;
    public String name;
    public Object object;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
    }
}

JSTest.java

// 通过enableDefaultTyping()设置设置JAVA_LANG_OBJECT后,会多输出Hacker类名
// 且在输出的Object属性时直接输出的是Hacker类对象,也就是说同时对Object属性对象进行了序列化和反序列化操作

package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        p.object = new Hacker();


        // 设置JAVA_LANG_OBJECT
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);

        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

结果输出了Hacker对象,说明对Person.Object的对象Hacker序列化和反序列化了。

image-20211029173244915

1-2. OBJECT_AND_NON_CONCRETE

OBJECT_AND_NON_CONCRETE:不仅仅对Object类,对Interface、AbstractClass类也会进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。此外,enableDefaultTyping()默认的无参数的设置就是此选项。

Sex.java

package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;

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

MySex.java

package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;

public class MySex implements Sex {
    int sex;

    @Override
    public int getSex() {
        return sex;
    }

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

JSTest.java

// OBJECT_AND_NON_CONCRETE:除了前面提到的特征,当类里有Interface、AbstractClass类时,对其进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。
// enableDefaultTyping()默认的无参数的设置就是此选项。

package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        p.object = new Hacker();
        p.sex = new MySex();


        // 设置OBJECT_AND_NON_CONCRETE
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        //mapper.enableDefaultTyping();      // 或直接无参调用,输出一样

        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

可以看到接口Interface也能够序列化和反序列化

image-20211029174011694

1.3. NON_CONCRETE_AND_ARRAYS

NON_CONCRETE_AND_ARRAYS:除了前面提到的特征外,还支持Array类型。

JSTest.java

// 对Array类型成功进行了序列化和反序列化
// 类名变成了 [L类名
package com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        p.sex = new MySex();

        // object属性是Hacker类的数组
        Hacker[] hackers = new Hacker[2];
        hackers[0] = new Hacker();
        hackers[1] = new Hacker();
        p.object = hackers;

        // 设置NON_CONCRETE_AND_ARRAYS
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);

        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

结果: 数组的类名变成了 [L类名

[Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker

{"age":6,"name":"mi1k7ea","object":["[Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker;",[{"skill":"Jackson"},{"skill":"Jackson"}]],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.MySex",{"sex":0}]}
Person.age=6, Person.name=mi1k7ea, [Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker;@6093dd95, com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.MySex@5622fdf

image-20211029174333003

1.4. NON_FINAL

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

Person.java

package com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal;

import com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete.Sex;

public class Person {
    public int age;
    public String name;
    public Object object;
    public Sex sex;
    public Hacker hacker;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s, %s, %s, %s", age, name, object == null ? "null" : object, sex == null ? "null" : sex, hacker == null ? "null" : hacker);
    }
}

JSTest.java

// 对非final的hacker属性进行序列化和反序列化
package com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        p.object = new Hacker();
        p.sex = new MySex();
        p.hacker = new Hacker();

        // 设置NON_FINAL
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

结果:可以看到序列化和反序列化了hacker属性,是因为设置了NON_FINAL属性,所以Person类的Hacker属性是Hacker类也能序列化和反序列化。

["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Person",{"age":6,"name":"mi1k7ea","object":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex",{"sex":0}],"hacker":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}]}]
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@3ffc5af1, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex@5e5792a0, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@26653222

image-20211029174829906

如果不设置NON_FINAL属性

结果:可以看到Person类的Hacker属性是不被序列化的

{"age":6,"name":"mi1k7ea","object":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex",{"sex":0}],"hacker":{"skill":"Jackson"}}
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@3ffc5af1, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex@5e5792a0, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@26653222

image-20211029175310571

2.@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.CUSTOM)

1.NONE

不对Person类的object属性序列化和反序列化

Person.java

package com.DemoJackson.Study.DemoJsonTypeInfo.Demo1None;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class Person {
    public int age;
    public String name;

    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    public Object object;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
    }
}

JSTest.java

// 和没有设置值为JsonTypeInfo.Id.NONE的@JsonTypeInfo注解是一样的
package com.DemoJackson.Study.DemoJsonTypeInfo.Demo1None;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JSTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.age = 6;
        p.name = "mi1k7ea";
        p.object = new Hacker();
        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(p);
        System.out.println(json);

        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }
}

Person类的object属性并没有序列化和反序列化

image-20211029190727452

2.CLASS

Person.java

package com.DemoJackson.Study.DemoJsonTypeInfo.Demo2CLASS;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class Person {
    public int age;
    public String name;
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    public Object object;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
    }
}

object属性中多了”@class”:”xxxxxxx.Hacker”,说明对object属性的值序列化和反序列化了。

那么也就是说,在Jackson反序列化的时候如果使用了JsonTypeInfo.Id.CLASS修饰的话,可以通过@class的方式指定相关类,并进行相关调用。

image-20211029190839098

3.MINIMAL_CLASS

Person.java

package com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class Person {
    public int age;
    public String name;

    @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
    public Object object;

    @Override
    public String toString() {
        return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
    }
}

结果:object属性中多了”@c”:”xxxxxxxxxxx.Hacker”,即使用@c替代料@class,官方描述中的意思是缩短了相关类名,实际效果和JsonTypeInfo.Id.CLASS类似

{"age":6,"name":"mi1k7ea","object":{"@c":"com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass.Hacker","skill":"Jackson"}}
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass.Hacker@204f30ec

image-20211029191055855

0x03 总结

满足下面三个条件之一即存在Jackson反序列化漏洞:

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

0x04 参考链接

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


   转载规则


《Java代码审计之Jackson序列化和反序列化》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。