Sa-Token 安全审计报告
审计对象:Sa-Token 开源 Java 权限认证框架(dromara 组织),版本 v1.45.0 / dev 分支
审计方式:核心鉴权链路 + 签名插件 + JWT + 序列化 + 路由/防火墙 + SpEL 插件逐行核查
审计日期:2026-08-13
一、结论总览
四类高危漏洞 verdict
| 漏洞类别 | 结论 | 关键位置 |
|---|---|---|
| 反序列化 → RCE | ⚠️ 有条件可利用:JDK 原生 readObject 与 fastjson1 两处落点,需攻击者能污染存储或应用把用户输入喂给反序列化器 |
SaSerializerTemplateForJdk.java:72、SaSessionForFastjsonCustomized.java:70 |
| 命令执行 | ❌ 框架内无直接命令执行点;唯一 RCE 通道是反序列化(条件)和 SpEL 注入(条件) | 全仓无 Runtime.exec/ProcessBuilder |
| 权限绕过 | ⚠️ 框架默认防火墙挡了大部分路径绕过;真正绕过来自「应用自写鉴权 pattern」与 Spring 路由的路径归一化不一致 | SaPathPatternParserUtil.java:37、SaInterceptor |
| 权限伪造 | ✅ 当前版本框架层不可伪造(JWT 强制验签 + 无默认秘钥);剩余风险是集成方用弱秘钥 | SaJwtTemplate.java:191、StpLogicJwtForSimple.java:54 |
核心结论:框架当前版本没有免条件直接拿权限的漏洞(JWT 伪造、命令执行点、可全局利用的路径绕过均不存在,防火墙默认全开)。
真正能打穿的是:签名插件的重放攻击 + debug 日志泄秘钥,以及 「Redis 裸奔 + JDK 序列化 / fastjson1」这条需部署配合的反序列化 RCE 链路。
二、可直接利用(已从代码证实)
🔴 1. 签名插件重放防护「静默失效」——布尔校验路径 nonce 不落地
最可复现、业务影响最直接的问题。
sa-token-plugin/sa-token-sign/.../SaSignTemplate.java 有两套校验路径:
checkParamMap/checkRequest(异常版)→ 走checkNonce,校验通过后 nonce 写入缓存(:267-281),防重放生效isValidParamMap/isValidRequest(布尔版,应用最常用写法)→ 走isValidNonce,只查不存(:251-260)
源码注释自己都写了:
/** 注意:同一 nonce 可以被多次判断有效,不会被缓存 */
public boolean isValidNonce(String nonce) { ... return dao.get(key) == null; }
攻击链:攻击者抓包拿一个合法签名的请求 → 在 timestamp 允许偏差窗口内(默认 5 分钟)无限重放。对下单、转账、SSO 数据拉取等接口,可重复触发业务动作。
性质:不是配置错误,是框架 API 设计缺陷。
🔴 2. debug 日志把签名秘钥打进日志
SaSignTemplate.java:178-180:
log.debug("fullStr:{}", fullStr); // fullStr = 参数字符串 + "&key=" + secretKey
fullStr 包含完整秘钥。生产误开 debug → 秘钥进日志文件 → 攻击者拿到秘钥即可为任意参数伪造签名 → 签名体系整体作废。这是"能直接拿权限"的最短路径,且极易误触发。
🟠 3. 签名比对用普通 equals → 时序侧信道
SaSignTemplate.java:291:
return theSign.equals(sign);
非恒定时间比较,远程可逐字节探测有效签名。比 1、2 难利用,但属同一模块连锁短板,修复成本极低(换 MessageDigest.isEqual)。
三、需特定条件才可利用("能拿权限/数据"但前提苛刻)
🟠 4. JDK 原生反序列化 → RCE(条件:能控制存储的字节)
落点:SaSerializerTemplateForJdk.java:72-74、jfinal/jboot 插件同款、sa-token-redis-template-jdk-serializer 插件。
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
攻击前提是攻击者能控制被反序列化的字节,两条路:
- Redis 未授权/弱口令/端口暴露 → 直接往 key 写恶意序列化对象 → 触发 gadget chain → RCE
- 应用把攻击者可控的对象
set进SaSession(如用户上传对象存会话)
第 1 条是经典 Java 生态事故(Redis 裸奔 + JDK 序列化),一旦成立就是 RCE。修复优先级高。
🟠 5. fastjson1 反序列化 → RCE(条件:会话值被污染 + 应用调 getModel)
落点:sa-token-plugin/sa-token-fastjson/.../SaSessionForFastjsonCustomized.java:70:
return JSON.parseObject(value.toString(), cs);
cs 是应用调用 session.getModel(key, SomeClass) 传入的目标类,但 fastjson1 解析嵌套对象时会遵循 JSON 里的 @type 字段——若会话里存的是攻击者可控 JSON 且 autoType 未开 safeMode,可实例化任意 gadget 类。
fastjson1 已停止维护(2020 年后无安全更新,历史 CVE 一大串),引入它本身就是供应链风险。攻击成立还需「应用把用户输入存进会话 + 后续 getModel 反序列化」。
🟠 6. SpEL 注入 → RCE(条件:开发者把用户输入拼进 @SaCheckEL)
sa-token-plugin/sa-token-spring-el/.../SaCheckELAspect.java:117,123:
parser.parseExpression(ofClass.value()).getValue(context);
表达式来自注解(开发者静态写),框架不会把用户输入直接喂进解析器——但解析上下文暴露了所有方法参数(#paramName 可直接引用)、所有 Spring Bean、this。一旦开发者写出 @SaCheckEL("... '" + 用户输入 + "' ..."),就是经典 SpEL RCE。该特性强烈放大开发者犯错风险,文档应显著警告。
四、偏弱 / 放大攻击面(建议改,不构成直接利用)
| # | 问题 | 证据 | 说明 |
|---|---|---|---|
| 7 | token 随机源非加密级 | SaFoxUtil.java:73 ThreadLocalRandom.current().nextInt(62) |
默认 uuid 风格和 temp-token 都是 UUID.randomUUID()(底层 SecureRandom,安全);切到 random-32/64/128 风格才退化。需观测大量 token 才可能预测,利用难度高 |
| 8 | 默认从 URL query 读 token | StpLogic.java:361-362 isReadBody=true 时 request.getParam() |
?satoken=xxx 直接可鉴权。token 放 URL → 进 Referrer、浏览器历史、访问/代理日志,泄露面大 |
| 9 | 默认 timeout=30天、isLastingCookie=true、isConcurrent=true |
SaTokenConfig.java:45,111,61 |
token 泄露后可用窗口长、多端并发放大泄露面 |
| 10 | Same-Token 比对也是普通 equals |
SaSameTemplate.java:52 |
同 #3,影响小 |
五、正向发现(框架做得好的地方)
- ✅ JWT 已加固:
SaJwtTemplate.java:191每次解析强制验签,getPayloadsNotCheck只是跳过 timeout、签名仍校验;StpLogicJwtForSimple.java:54未配置秘钥直接抛异常,无硬编码默认秘钥。固定 HS256、无alg=none混淆 → 当前版本不存在 JWT 权限伪造。 - ✅ 防火墙默认全开:目录穿越、危险字符(
//\%2e;%00换行等)、host、方法、header、参数检查全部默认注册(SaFirewallStrategy.java:46-54),缓解了基于 URL 编码/路径的原始绕过。 - ✅ temp-token 用
UUID.randomUUID()(安全随机)。 - ✅ 全仓无直接命令执行点。
六、修复优先级建议
| 优先级 | 动作 |
|---|---|
| P0 | ① 签名插件布尔校验路径补上 nonce 落地(或文档显著标注"防重放必须用 checkRequest");② 生产禁止 debug 日志 |
| P1 | ③ Redis 设密码 + 网络隔离;④ 弃用 JDK 序列化和 fastjson1,统一用 JSON 序列化插件 |
| P2 | ⑤ 签名比对改恒定时间;⑥ 默认关闭 URL query 读 token;⑦ Cookie 主动配 HttpOnly/SameSite/Secure |
| P3 | ⑧ 收紧默认有效期;⑨ 文档警告 @SaCheckEL 拼用户输入 |