江鸟's Blog

shiro550分析

字数统计: 1.4k阅读时长: 5 min
2021/03/28 Share

简单分析一下shiro550反序列化

shiro反序列化

环境下载

下载大佬写好的war包

https://cloud.tencent.com/developer/article/1472310

放到tomcat的路径webapps下,启动tomcat,就会自动解压

然后用idea打开文件夹

用IDEA内置tomcat部署即可image-20210328172800959

代码分析

前置条件

在漏洞的首页

https://issues.apache.org/jira/browse/SHIRO-550

image-20210328175132072

在这里获取到的信息:

  • CookieRememberMeManager这是一个入口
  • 功能点是remember me 这个功能点
  • 使用了AES 解密和 Base64加密
  • 使用Java序列化(ObjectInputStream)反序列化
  • 源代码存在默认的AES加密密钥,所有能够查看源代码的人都可以知道默认密钥是什么

首先进行测试一下正常功能点

我们进入login.jsp进行查看,登陆的时候有一个rememeber me功能点,勾选上

登陆之后在cookie中发现remember me字段

image-20210328175553810

而shiro每次都会对cookie中的rememberMe来进行解密后反序列化操作来确定访问者权限,所以直接在cookie传输rememberMe参数就可以控制shiro反序列化的值

debug

第二个目的是获得加密解密的方法,以此来自行加密解密恶意payload进行传输

IDEA中按两次shift 搜索咱们前面准备当做入口点的CookieRememberMeManager类,按着函数列表查看后并未发现有关加密的信息,so跟进父类AbstractRememberMeManager去看一下

在这里前几行就有一个特殊的字符串

1
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

根据前面我们在官网漏洞报告中得到的信息 这应该就是默认的AES密钥

查看这个参数在哪进行了使用

image-20210329100605140

跟进setCipherKey(DEFAULT_CIPHER_KEY_BYTES)

1
2
3
4
5
6
7
8
9
10
public void setCipherKey(byte[] cipherKey) {
this.setEncryptionCipherKey(cipherKey);
this.setDecryptionCipherKey(cipherKey);
}
public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
this.encryptionCipherKey = encryptionCipherKey;
}
public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
this.decryptionCipherKey = decryptionCipherKey;
}

继续往下看 能找到一个encrypt函数,

1
2
3
4
5
6
7
8
9
10
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = this.getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
value = byteSource.getBytes();
}

return value;
}

传入的serialized的值是我刚才web端登录的用户名root序列化后的数据

根据运行步骤函数名猜测流程是shiro验证完了登录的账号密码,然后根据用户名生成序列化数据准备进行加密了

image-20210329101722079

在这里的cipherService是一个新的对象,去查看他的源码

1
2
3
4
5
6
7
8
9
10
11
org.apache.shiro.crypto.AesCipherService

package org.apache.shiro.crypto;

public class AesCipherService extends DefaultBlockCipherService {
private static final String ALGORITHM_NAME = "AES";

public AesCipherService() {
super("AES");
}
}

继续debug可以看到AES加密的模式

image-20210329102203725

继续debug,跳转到org.apache.shiro.crypto.JcaCipherService#encrypt(byte[], byte[])

image-20210329103032785

在这里的ivBytes是随机生成的长度为16的字节

1
2
3
4
5
6
return this.encrypt(plaintext, key, ivBytes, generate);

plaintext 为 序列化的用户名
key 为 DEFAULT_CIPHER_KEY_BYTES 就是上面base64解码的那个密钥
ivBytes 为 随机生成的长度为16的字节
generate 为 true

跳转至org.apache.shiro.crypto.JcaCipherService#encrypt(byte[], byte[], byte[], boolean)

传递的参数如下:

1
2
3
4
plaintext没变
key没变
iv=ivBytes
prependIv=generate

image-20210329103829176

继续跟进

新建一个字节类型的参数名为output,长度为iv的长度加上encrypted长度

1
2
System.arraycopy(iv, 0, output, 0, iv.length);
System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);

这两行是进行了一个复制操作,将iv的内容复制到encrypted中,并赋值给output

所以output的值为随机生成的16位iv+AES加密结果

继续debug,发现传递的参数回到了最开始的encrypt、

继续debug,最终到了CookieRememberMeManager类中的rememberSerializedIdentity方法又对存储着加密结果的变量进行了一次base64加密,然后赋值到了cookie的rememberMe参数中,到此加密过程结束。

image-20210329104732816

加密总结

最终总结加密过程为

设定:密钥 = kPH+bIxk5D2deZiIxcaaaA==

1.获得明文 = 正常识序列化用户名后的字节(root)

2.以下步骤:

  • 科普知识:正常的AES加密所需参数 = 想加密的字符串 + iv + key + CBC + padding
  • shiro:AES加密 = 想加密的字符串 (明文) + iv(随机生成的长度为16的字节) + key(base64解码**密钥**的结果) + CBC + PKCS5Padding

3.随机生成的长度为16的字节 + AES加密结果 (就是拼接了一下)

4.base64加密

那么解密过程为:

设定:密钥 = kPH+bIxk5D2deZiIxcaaaA==

1.获得密文 = base64解密rememberMe参数传过来的值

2.以下步骤:

  • 科普知识:正常的AES解密所需参数 = 想解密的字符串 + iv + key + CBC
  • shiro:AES解密 = 想解密的字符串(删除密文前16个字节的剩余字节)+iv(密文的前16个字节) + key(base64解码**密钥**的结果) + CBC + PKCS5Padding

3.对解密结果进行反序列化,触发payload

解密部分

看一下最终触发反序列化的地方在哪里,按照加密方法调试过程,在解密方法org.apache.shiro.mgt.AbstractRememberMeManager#decrypt处添加断点

其中serialized就是我们传入的cookie base64解码的结果

后面的步骤大致相似,直接到了org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals这个函数

image-20210329112521447

在这里进行了一种对象流的序列化

最后在调用readObject()完成了反序列化

image-20210329112655454

image-20210329113050245

解密时:从cookie中取出、base64解密、decrypt解密、反序列化

如何攻击

根据我们的分析来写poc

POC的加密流程就是:

  1. 获取到 反序列化的数据
  2. 设置AES加密模式,使用AES.MODE_CBC的分块模式
  3. 设置硬编码的 key
  4. 使用随机数生成 16 字节的 iv
  5. 使用 iv + AES加密(反序列化数据) 拼接
  6. 最后base64加密全部内容

命令执行还是需要cc链等利用链, shiro自带cc3.2.1,这一条是无法直接利用的

CATALOG
  1. 1. 环境下载
  2. 2. 代码分析
    1. 2.1. 前置条件
    2. 2.2. debug
    3. 2.3. 加密总结
    4. 2.4. 解密部分
  3. 3. 如何攻击