漏洞介绍
Apache Shiro 是一个强大且易于使用的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、会话管理(Session Management)和密码学支持等功能。Apache Shiro 550反序列化漏洞(CVE-2016-4437)是一个严重的Java反序列化漏洞,它允许攻击者通过特制的Java序列化对象在目标系统上执行任意代码。该漏洞影响了使用默认“rememberMe”功能的Apache Shiro。
漏洞范围
Apache Shiro <= 1.2.4
漏洞靶场
使用vulhub的靶场
使用版本为Apache Shiro 1.2.4
vulhub-master/shiro/CVE-2016-4437
1 | docker-compose up -d |
漏洞原理
Apache Shiro 提供了一种“rememberMe”功能,用于在用户关闭浏览器后仍然保持会话,当启用该功能时,Shiro 会将用户的会话信息序列化并存储在一个 cookie 中,以便在用户重新访问时反序列化并恢复会话;在这边攻击者可以通过修改“rememberMe” cookie,注入特制的恶意 Java 序列化对象;当 Shiro 反序列化这个恶意对象时,攻击者可以在目标系统上执行任意代码。
Apache Shiro如何记住我?
1 | 序列化用户身份对象.(这里为SimplePrincipalCollection) |
Apache Shiro如何解析我?
1 | 读取cookie中的rememberMe的值. |
如何攻击?
1 | 通过以上,我们可以看出,只要手动构造一个反序列化 payload,进行AES加密(密钥我们知道的)和base64编码,即可在反序列化时执行任何操作. |
漏洞相关源代码片段
漏洞出现在 Shiro 的 RememberMeManager 组件中,特别是处理“rememberMe”cookie 的代码部分,以下是与漏洞相关的关键代码片段的简化示例,帮助我们理解漏洞的原理。
1 | public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) { |
shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java#L291-L297
在Shiro中,当用户成功登录时,可以通过onSuccessfulLogin方法来处理“记住我”功能的逻辑。这个方法会清理旧的身份验证信息,并根据需要生成新的身份验证信息。
forgetIdentity方法会删除存储在Cookie中的身份信息,以确保旧的身份信息不会被重新使用。isRememberMe(token):检查登录请求中是否包含“记住我”选项。如果用户在登录时选择了“记住我”,这个方法会返回true。rememberIdentity(subject, token, info):如果用户选择了“记住我”,这个方法会生成并存储新的身份验证信息;接着我们再继续追踪rememberIdentity:
1 | public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) { |
shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java#L319-L322
getIdentityToRemember(subject, authcInfo)方法用于从authcInfo(身份验证信息)中提取用户的身份信息。接着调用rememberIdentity(subject, principals)方法,使用提取到的身份信息来设置“记住我”功能,这边继续跟进rememberIdentity:
1 | protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) { |
shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java#L345-L348
1.convertPrincipalsToBytes
这个函数中使用了convertPrincipalsToBytes(accountPrincipals)方法用于将PrincipalCollection(包含用户身份信息的对象)转换为字节数组;此时我们进入convertPrincipalsToBytes函数查看具体针对信息数据的操作是什么样的:
1 | protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) { |
shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java#L359-L365
可以看到再这个函数中首先使用了serialize(principals)方法将PrincipalCollection对象序列化为字节数组。接着进行一个获取密钥服务的操作,当CipherService不为空,则对序列化后的字节数组进行加密;接着我们进入encryp函数中查看具体的加密操作是如何进行的:
1 | protected byte[] encrypt(byte[] serialized) { |
shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java#L469-L477
这里调用了getCipherService()方法获取配置的加密服务,并赋值给变量cipherService;进入getCipherService()方法后可以发现此处的加密为AES加密(除了指定加密的方式之外还指定了其他的一些加密细节)

获取到加密的信息后就是对序列化的数据进行加密了,在加密时,程序中使用了getEncryptionCipherKey()方法进行密钥获取:

此时可以看到默认的加密密钥实际上在shiro中是一串固定的base64解码后的字符串;其base64编码的值是“kPH+bIxk5D2deZiIxcaaaA==”
2.rememberSerializedIdentity
在清楚如何针对信息数据转化为字节数组后,接着再看一下rememberSerializedIdentity方法中具体做了哪些操作:
1 | protected void rememberSerializedIdentity(Subject subject, byte[] serialized) { |
shiro-shiro-root-1.2.4\web\src\main\java\org\apache\shiro\web\mgt\CookieRememberMeManager.java#L135-L158
在该方法中使用了Base64.encodeToString(serialized);对序列化数据进行Base64编码;并将Base64编码后的数据整合到Cookie中;至此我们就可以知道RememberMe的具体生成步骤:
1 | 用户信息-->序列化-->AES加密-->Base64编码-->Cookie信息 |
那么对应的解密过程就是:
1 | Cookie信息-->Base64解码-->AES解密-->反序列化-->用户信息 |
漏洞复现
访问http://127.0.0.1:8080/进入靶场页面

默认的账号密码为admin:vulhub;这个时候我们可以先输入任意Username和passwrod进行登录并抓包(Remember me选项必须勾选);抓到数据包后将数据包发送至BP的重放模块Repeater,进行发包,查看相应包内容:

发现相应包中包含有Set-Cookie: rememberMe=deleteMe字段就基本上可以断定这边使用了shiro框架
exp使用
https://github.com/SummerSec/ShiroAttack2
1 | java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar |


命令执行成功