没什么好分析的Shiro-550

前言

陆陆续续分析了几个Java的安全漏洞,接下来轮到shiro的了。这里分析的是Shiro-550,硬编码rememberMe密钥造成的反序列化漏洞。分析起来比想象着的简单。

环境搭建

Shiro-550是使用shiro1.2.4及以下的默认密钥导致的反序列化漏洞,环境的代码我是用的github上一个大佬写的,使用的是Maven包管理,不用怎么搭,自动下载依赖直接运行即可。

漏洞调试

根据网上大部分的复现文章,使用ysoserial生成CommonsBeanutils1反序列化链的payload,并使用上面shiro漏洞环境项目的GenPayload类加密一下payload,生成shiro可识别的rememberMe内容。

然后依旧是在Runtimeexec方法下断点,Cookie中设置rememberMe并发送。调用栈挺长的,但跟rememberMe有关的是从AbstractRememberMeManager开始这段。

跟进到AbstractRememberMeManagergetRememberedPrincipals方法。看到bytes变量的内容为加密后的序列化链,跟进getRememberedSerializedIdentity方法看看是如何获取rememberMe内容的。

getRememberedSerializedIdentity方法在AbstractRememberMeManager类中是抽象方法,具体的实现代码要去到AbstractRememberMeManager的子类CookieRememberMeManager中看。整体就是从Cookie中获取rememberMe内容,并返回base64解码的内容。rememberMe的内容是通过this.getCookie()获取当前的cookie对象,而cookie初始化时是用rememberMe这个名称的,获取到的内容相应的也该名称的值。

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
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
...
return null;
} else {
WebSubjectContext wsc = (WebSubjectContext)subjectContext;
if (this.isIdentityRemoved(wsc)) {
return null;
} else {
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
HttpServletResponse response = WebUtils.getHttpResponse(wsc);
String base64 = this.getCookie().readValue(request, response);
if ("deleteMe".equals(base64)) {
return null;
} else if (base64 != null) {
base64 = this.ensurePadding(base64);
...
byte[] decoded = Base64.decode(base64);
...
return decoded;
} else {
return null;
}
}
}
}

获取到字节数组为非空的话会继续调用convertBytesToPrincipals方法,在convertBytesToPrincipals方法中先调用decrypt方法对rememberMe进行解密。shiro使用的是AES加密,按理说还具有初始化向量iv,但解密时候的iv是从rememberMe的开头获取的,所以不影响payload的构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
byte[] encrypted = ciphertext;
byte[] iv = null;
if (this.isGenerateInitializationVectors(false)) {
try {
int ivSize = this.getInitializationVectorSize();
int ivByteSize = ivSize / 8;
iv = new byte[ivByteSize];
System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
int encryptedSize = ciphertext.length - ivByteSize;
encrypted = new byte[encryptedSize];
System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
} catch (Exception var8) {
String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
throw new CryptoException(msg, var8);
}
}

return this.decrypt(encrypted, key, iv);
}

接着是对解密后的内容进行反序列化,最终调用方的是DefaultSerializer类的deserialize方法。这里实例化ObjectInputStream类是用他的子类ClassResolvingObjectInputStream是实现的。

ClassResolvingObjectInputStreamresolveClass方法是通过forName方法返回Class实例的,但forName不支持数组类型的Class,所以反序列化的利用链没使用CommonsCollections的,而是用了CommonsBeanutils1。因为事先看了别的师傅复现的文章,所以没有踩这个坑。具体的forName原理在@zsx师傅的文章里有详尽的分析。除此之外,后面的反序列化就与普通的反序列化无异。

后记

CommonsBeanutils1里的TemplatesImpl在上回Java反序列化的文章里没有学到,好像fastjson不出网的payload是有用到这个的,后续还还要补充学习。冲冲冲

参考

https://www.cnblogs.com/loong-hon/p/10619616.html

https://p2hm1n.com/2020/12/03/Shiro550-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

https://www.mi1k7ea.com/2020/10/03/%E6%B5%85%E6%9E%90Shiro-rememberMe%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88Shiro550%EF%BC%89/#%E9%83%A8%E5%88%86Gadget%E6%89%93%E5%A4%B1%E8%B4%A5%E7%9A%84%E5%9D%91

http://www.lmxspace.com/2019/10/17/Shiro-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%AE%B0%E5%BD%95/#4-%E4%BF%AE%E5%A4%8D%E6%96%B9%E5%BC%8F

https://blog.zsxsoft.com/post/35

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×