Apache Shiro 是一个强大且易于使用的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、会话管理(Session Management)和密码学支持等功能。Apache Shiro 550反序列化漏洞(CVE-2016-4437)是一个严重的Java反序列化漏洞,它允许攻击者通过特制的Java序列化对象在目标系统上执行任意代码。该漏洞影响了使用默认“rememberMe”功能的Apache Shiro。
shiro550(1.2.4版本)反序列化漏洞原理
Apache Shiro 提供了一种“rememberMe”功能,用于在用户关闭浏览器后仍然保持会话,当启用该功能时,Shiro 会将用户的会话信息序列化并存储在一个 cookie 中,以便在用户重新访问时反序列化并恢复会话;在这边攻击者可以通过修改“rememberMe” cookie,注入特制的恶意 Java 序列化对象;当 Shiro 反序列化这个恶意对象时,攻击者可以在目标系统上执行任意代码。
漏洞相关源代码片段
漏洞出现在 Shiro 的 RememberMeManager
组件中,特别是处理“rememberMe”cookie 的代码部分,以下是与漏洞相关的关键代码片段的简化示例,帮助我们理解漏洞的原理。
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//清理旧的身份验证信息c
forgetIdentity(subject);
//生成新的身份验证信息
if (isRememberMe(token)) { //如果有勾选remember me
rememberIdentity(subject, token, info);//生成新的cookie中的RememberMe字段
在Shiro中,当用户成功登录时,可以通过onSuccessfulLogin
方法来处理“记住我”功能的逻辑。这个方法会清理旧的身份验证信息,并根据需要生成新的身份验证信息。
forgetIdentity
方法会删除存储在Cookie中的身份信息,以确保旧的身份信息不会被重新使用。isRememberMe(token)
:检查登录请求中是否包含“记住我”选项。如果用户在登录时选择了“记住我”,这个方法会返回true
。rememberIdentity(subject, token, info)
:如果用户选择了“记住我”,这个方法会生成并存储新的身份验证信息;接着我们再继续追踪rememberIdentity
:
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
rememberIdentity(subject, principals);
}
getIdentityToRemember(subject, authcInfo)
方法用于从authcInfo
(身份验证信息)中提取用户的身份信息。接着调用rememberIdentity(subject, principals)
方法,使用提取到的身份信息来设置“记住我”功能,这边继续跟进rememberIdentity:
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
rememberSerializedIdentity(subject, bytes);
}
1.convertPrincipalsToBytes
这个函数中使用了convertPrincipalsToBytes(accountPrincipals)
方法用于将PrincipalCollection
(包含用户身份信息的对象)转换为字节数组;此时我们进入convertPrincipalsToBytes
函数查看具体针对信息数据的操作是什么样的:
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
bytes = encrypt(bytes);
}
return bytes;
}
可以看到再这个函数中首先使用了serialize(principals)
方法将PrincipalCollection
对象序列化为字节数组。接着进行一个获取密钥服务的操作,当CipherService
不为空,则对序列化后的字节数组进行加密;接着我们进入encryp函数中查看具体的加密操作是如何进行的:
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = getCipherService();
if (cipherService != null) { //如果cipherService不为空
ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
value = byteSource.getBytes();
}
return value;
}
这里调用了getCipherService()
方法获取配置的加密服务,并赋值给变量cipherService
;进入getCipherService()
方法后可以发现此处的加密为AES加密(除了指定加密的方式之外还指定了其他的一些加密细节)
获取到加密的信息后就是对序列化的数据进行加密了,在加密时,程序中使用了getEncryptionCipherKey()
方法进行密钥获取:
此时可以看到默认的加密密钥实际上在shiro中是一串固定的base64解码后的字符串;其base64编码的值是“kPH+bIxk5D2deZiIxcaaaA==”
2.rememberSerializedIdentity
在清楚如何针对信息数据转化为字节数组后,接着再看一下rememberSerializedIdentity
方法中具体做了哪些操作:
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized); //进行base64编码
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64); //将base64编码的信息整合到cookie当中
cookie.saveTo(request, response);
}
在该方法中使用了Base64.encodeToString(serialized);
对序列化数据进行Base64编码;并将Base64编码后的数据整合到Cookie中;至此我们就可以知道RememberMe
的具体生成步骤:
用户信息-->序列化-->AES加密-->Base64编码-->Cookie信息
那么对应的解密过程就是:
Cookie信息-->Base64解码-->AES解密-->反序列化-->用户信息
漏洞利用
靶场则是使用vulhub进行搭建,进入对应的目录.../vulhub/shiro/CVE-2016-4437;使用docker命令进行启动
docker-compose up -d
查看具体的端口映射
docker ps -a
这个时候直接访问环境所在靶机IP地址的8080端口:
默认的账号密码为admin:vulhub
;这个时候我们可以先输入任意Username和passwrod进行登录并抓包(Remember me选项必须勾选);抓到数据包后将数据包发送至BP的重放模块Repeater,进行发包,查看相应包内容:
发现相应包中包含有Set-Cookie: rememberMe=deleteMe
字段就基本上可以断定这边使用了shiro框架;既然是shiro框架,我们就可以尝试进行安全测试:
安全工具(文章末尾附下载地址)
①Burpsuite ShiroPassiveScan插件扫描
这是一款基于BurpSuite的被动式shiro检测插件;该插件会对BurpSuite传进来的每个不同的域名+端口的流量进行一次shiro检测且key可在config.yml自定义,解决有些用户觉得key太少的问题。
安装方法:
添加进入插件即可;
这当我们正常去访问网站, 如果站点的某个请求出现了,那么该插件就会去尝试检测(需要挂BP);当检测到漏洞后则会在BP中进行显示:
这款插件也允许我们自行添加Key:打开./BurpShiroPassiveScan/resources/config.yml并找到application.shiroCipherKeyExtension.config.payloads,在后面添加新的Key即可。
②shiro_attack
使用此工具需要事先准备java环境,java10以上版本缺少javafx-sdk-18.0.2泛型,需要自己手动安装,最好使用jdk1.8版本(环境变量的设置这边就不说了),环境准备好后直接在工具目录打开cmd命令行,运行如下命令:
java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar
能够爆破出Key则表示Shiro框架存在反序列化漏洞,在爆破出Key后可以直接选择检测当前利用链条,若利用链不能够利用则可以进行利用链爆破;
发现利用链可以使用后则尝试进行命令执行、内存马操作,成功GetShell。
【注意】若懒得去找jdk1.8以及这两个工具,关注风铃Sec回复[Shiro550]进行领取即可(文章底下有二维码)