漏洞背景
OnlineSessionFactory.createSession(SysUserOnline) 方法(第36-38行)使用原生ObjectInputStream.readObject()从数据库sys_user_online.session_data字段反序列化会话数据:
ByteArrayInputStream bis = new ByteArrayInputStream(userOnline.getSessionData());
ObjectInputStream ois = new ObjectInputStream(bis);
OnlineSession onlineSession = (OnlineSession) ois.readObject();
该反序列化过程存在以下安全缺陷:
- 未配置
ObjectInputFilter(JEP 290)进行类白名单过滤
- 未重写
resolveClass()进行类校验
- 未对
session_data进行HMAC完整性校验
- 类型转换发生在
readObject()之后,gadget chain已经执行
漏洞表现
如果攻击者能够向sys_user_online.session_data字段写入恶意序列化数据(通过SQL注入或数据库直接访问),下一次使用该会话的HTTP请求将触发反序列化,执行攻击者构造的gadget chain载荷,实现远程代码执行(RCE)。
该反序列化通过Shiro过滤器链在每次认证请求中触发:
OnlineSessionFilter → OnlineSessionDAO.doReadSession() → SysShiroService.getSession() → OnlineSessionFactory.createSession() → readObject()
复现步骤
环境: RuoYi v4.8.3,默认配置,MySQL 8.0
- 使用admin登录系统,等待1分钟让会话同步到数据库
- 确认会话数据已写入:
SELECT sessionId, login_name, LENGTH(session_data) as data_len FROM sys_user_online;
-- 输出示例:
-- sessionId: 70a2a937-15ee-41e7-807e-b76af74d5a78
-- data_len: 3109
- 将
session_data替换为畸形的Java序列化流(ACED0005为Java序列化魔数):
UPDATE sys_user_online
SET session_data = 0xACED00057372002E6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B787200
WHERE sessionId = '70a2a937-15ee-41e7-807e-b76af74d5a78';
- 重启应用以清除Shiro的EhCache内存缓存
- 在浏览器中刷新页面(使用相同session)
- 查看应用日志:
WARN c.r.f.s.s.OnlineSessionFactory - deserialize OnlineSession failed,
sessionId=70a2a937-15ee-41e7-807e-b76af74d5a78
java.io.UTFDataFormatException
结果: 应用确实通过ObjectInputStream.readObject()反序列化了注入的数据,且无任何类过滤。使用ysoserial等工具生成的有效gadget chain载荷(如Spring1、Spring2)将在此处执行任意代码。
关于攻击前置条件
此漏洞需要攻击者能够写入sys_user_online.session_data字段,通常需要:
- 应用中其他位置存在SQL注入漏洞
- 数据库凭据泄露(如
application-druid.yml暴露)
- 共享主机环境下的数据库访问
虽然需要链式利用,但这是一个严重的纵深防御缺陷——一旦存在任何SQL注入,此反序列化触发点会将数据层面的入侵升级为服务器完全沦陷。
涉及文件
ruoyi-framework/.../shiro/session/OnlineSessionFactory.java(第36-38行)—— 反序列化触发点
ruoyi-framework/.../manager/factory/AsyncFactory.java(第62-66行)—— 序列化写入
ruoyi-framework/.../shiro/session/OnlineSessionDAO.java(第54-57行)—— 会话读取
ruoyi-framework/.../shiro/service/SysShiroService.java(第45-46行)—— 数据库查询+工厂调用
修复建议
方案一:添加ObjectInputFilter(最小改动)
ObjectInputStream ois = new ObjectInputStream(bis);
ois.setObjectInputFilter(filterInfo -> {
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
String name = clazz.getName();
if (name.startsWith("com.ruoyi.") ||
name.startsWith("java.lang.") ||
name.startsWith("java.util.")) {
return ObjectInputFilter.Status.ALLOWED;
}
return ObjectInputFilter.Status.REJECTED;
}
return ObjectInputFilter.Status.UNDECIDED;
});
方案二:替换序列化方式(推荐)
使用JSON(Jackson)替代Java原生序列化,从根本上消除反序列化攻击面。
方案三:添加HMAC完整性校验(最佳)
在序列化数据存储前添加HMAC签名,反序列化前验证签名,防止数据被篡改。
漏洞背景
OnlineSessionFactory.createSession(SysUserOnline)方法(第36-38行)使用原生ObjectInputStream.readObject()从数据库sys_user_online.session_data字段反序列化会话数据:该反序列化过程存在以下安全缺陷:
ObjectInputFilter(JEP 290)进行类白名单过滤resolveClass()进行类校验session_data进行HMAC完整性校验readObject()之后,gadget chain已经执行漏洞表现
如果攻击者能够向
sys_user_online.session_data字段写入恶意序列化数据(通过SQL注入或数据库直接访问),下一次使用该会话的HTTP请求将触发反序列化,执行攻击者构造的gadget chain载荷,实现远程代码执行(RCE)。该反序列化通过Shiro过滤器链在每次认证请求中触发:
OnlineSessionFilter→OnlineSessionDAO.doReadSession()→SysShiroService.getSession()→OnlineSessionFactory.createSession()→readObject()复现步骤
环境: RuoYi v4.8.3,默认配置,MySQL 8.0
session_data替换为畸形的Java序列化流(ACED0005为Java序列化魔数):结果: 应用确实通过
ObjectInputStream.readObject()反序列化了注入的数据,且无任何类过滤。使用ysoserial等工具生成的有效gadget chain载荷(如Spring1、Spring2)将在此处执行任意代码。关于攻击前置条件
此漏洞需要攻击者能够写入
sys_user_online.session_data字段,通常需要:application-druid.yml暴露)虽然需要链式利用,但这是一个严重的纵深防御缺陷——一旦存在任何SQL注入,此反序列化触发点会将数据层面的入侵升级为服务器完全沦陷。
涉及文件
ruoyi-framework/.../shiro/session/OnlineSessionFactory.java(第36-38行)—— 反序列化触发点ruoyi-framework/.../manager/factory/AsyncFactory.java(第62-66行)—— 序列化写入ruoyi-framework/.../shiro/session/OnlineSessionDAO.java(第54-57行)—— 会话读取ruoyi-framework/.../shiro/service/SysShiroService.java(第45-46行)—— 数据库查询+工厂调用修复建议
方案一:添加ObjectInputFilter(最小改动)
方案二:替换序列化方式(推荐)
使用JSON(Jackson)替代Java原生序列化,从根本上消除反序列化攻击面。
方案三:添加HMAC完整性校验(最佳)
在序列化数据存储前添加HMAC签名,反序列化前验证签名,防止数据被篡改。