扫出你实际装的 Apache Shiro 模块与版本,逐条判定官方 26 条 CVE 里哪些真的落在你身上。
零依赖单 jar,不联网,不读你的 pom —— 直接扫构建产物(jar / war / fat jar / 目录)。
java -jar shiro-check.jar --utf8 your-app.jar
Shiro 的 CVE 不是挂在「Shiro」上的,是挂在具体模块上的。同一个编号在不同模块上是不同的规则:
| CVE | 受影响模块 | 只用 shiro-core 的人 |
|---|---|---|
| CVE-2020-17510 | shiro-spring | 不中 |
| CVE-2020-17523 | shiro-web / shiro-spring / shiro-spring-boot-starter | 不中 |
| CVE-2023-34478 | shiro-web | 不中 |
| CVE-2026-56091 | shiro-guice | 不中 |
官方 26 条里,13 条完全不碰 shiro-core。把它们压成一句「Shiro < 1.7.1 有洞」,
就是替用户做了他该自己做的判断 —— 而且做错了。
本工具的判定粒度是 CVE × 模块:26 条 CVE 展开成 37 条规则,覆盖 7 个模块。
Dependabot 按依赖坐标匹配 GitHub advisory。26 条里有 5 条对不上:
| 条目 | 为什么对不上 |
|---|---|
| CVE-2026-56091(shiro-guice 认证绕过,high) | advisory 仍是 unreviewed,受影响包为空 |
| CVE-2026-56130(RememberMe cookie 永不过期) | 同上 |
| CVE-2014-0074(LDAP 空口令绕过) | 同上 |
| CVE-2023-22602(Spring Boot 2.6+ 认证绕过,high) | advisory 只挂在 org.apache.shiro:shiro-root 上 |
| CVE-2010-3863 | 同上 |
而 shiro-root 是 packaging=pom 的父 POM —— Maven Central 上
没有对应的 jar(HTTP 404),
没有任何项目会依赖它,所以按坐标匹配永远对不上。
ℹ️
unreviewed是 GitHub 的正常流程状态(NVD 自动导入、尚未人工标注受影响包), 按坐标匹配也是合理设计。这里只陈述「进不了告警」这个事实,不是说谁报错了。
shiro-all 是把多个模块打包进去的 uber jar。实测 shiro-all-1.3.2.jar 内部有 6 个
META-INF/maven/org.apache.shiro/<模块>/pom.properties:
shiro-all shiro-core shiro-web shiro-spring shiro-ehcache shiro-quartz
也就是说,pom 里只写了 shiro-all 的项目,坐标层面只有一个名字
(26 条里只有 CVE-2016-6802 挂在这个坐标上),而 jar 里实际躺着 core / web / spring 三个模块的代码。
扫实物能把这层看穿。实测一个 shiro-all-1.3.2.jar,本工具报出 跨 3 个模块 20 条。
3 条 advisory 的 first_patched_version 是 3.0.0-alpha-2,而这个版本从未发布到 Maven Central
(官方直接发了 3.0.0 正式版)。照着它去升是升不动的 —— 本工具会标注出来,并把升级建议
换成实际拿得到的版本。
「命中」= 这个模块在场 且 版本落在官方受影响区间内。不等于「已被利用」或「必然可利用」。
26 条里绝大多数需要特定配置才成立,所以工具把它们分开列:
条件示例(逐字对照官方描述原文,未作外推):
CVE-2026-49268 仅当使用 DefaultLdapRealm(用户名未转义即拼进 LDAP DN)
CVE-2023-22602 仅当与 Spring Boot 2.6+ 一起使用,且未把
spring.mvc.pathmatch.matching-strategy 设回 ant_path_matcher
CVE-2026-23903 仅当静态文件放在大小写不敏感的文件系统上(如 macOS 默认设置),
且 Shiro 里只配了小写的 filter 路径;只影响静态文件
🔴 工具不解析你的配置。 shiro.ini / application.yml / Java 代码里都能配 Shiro, 解析出来的结论比不解析更危险。条件明文列出,由你自己判断 —— 这是有意的设计,不是偷懒。
# 扫一个 jar / war
java -jar shiro-check.jar your-app.jar
# 扫整个目录(递归)
java -jar shiro-check.jar /path/to/libs
# 连「不适用」的条目也列出来
java -jar shiro-check.jar --all your-app.jar
# Windows 控制台中文乱码时
java -jar shiro-check.jar --utf8 your-app.jar
识别能力:普通 jar · Spring Boot fat jar(BOOT-INF/lib/)· 传统 WAR(WEB-INF/lib/)·
uber jar(shiro-all,展开内含模块)· 被改名的 jar(以 pom.properties 为准)·
用反斜杠写路径的畸形归档。
需要 JDK 17+。运行时零依赖。
一行都不手抄。 tools/gen_rules.py 从两个一手源生成:
| 源 | 提供什么 |
|---|---|
| shiro.apache.org/security-reports.html | 条目全集、描述原文、触发条件原文 |
| GitHub Advisory API | 受影响模块坐标、结构化版本区间、评级 |
生成过程带 12 条断言,任一不满足就中止不写文件(防「解析失败生成空壳表而测试照样全绿」):
发文前另有 tools/recheck_before_publish.py,用第二套独立口径重新核一遍承重论据
(不 import 生成脚本,也不读它的输出 —— 复用同一套解析逻辑就会连 bug 一起复现)。
unreviewed 转成 reviewedshiro-check scans your build artifacts (jar / war / fat jar / directory) to find which Apache Shiro modules you actually ship, then evaluates all 26 official CVEs against your exact module + version combination.
Why it exists: Shiro CVEs attach to modules, not to "Shiro". 13 of the 26 never touch
shiro-core at all. The tool works at CVE × module granularity (37 rules, 7 modules).
Three things coordinate-based matching cannot see:
unreviewed with an
empty affected-package list; 2 are attached only to org.apache.shiro:shiro-root, which is
a packaging=pom parent POM with no jar on Maven Central (HTTP 404), so no project ever
depends on it.shiro-all is an uber jar — shiro-all-1.3.2.jar contains 6 pom.properties entries
(core, web, spring, ehcache, quartz + itself). Your pom shows one coordinate; the jar carries
three modules' worth of code.3.0.0-alpha-2 as the fix, a version never published to Maven
Central. The tool flags these and suggests a version you can actually upgrade to.A "hit" means module present AND version within the official affected range. It does not mean exploitable — most entries require specific configuration, which the tool lists verbatim from the official advisory. It deliberately does not parse your configuration.
java -jar shiro-check.jar [--all] [--utf8] <jar | war | directory> ...
Requires JDK 17+. No runtime dependencies. The rule table is generated from two primary sources
with 12 assertions that abort on failure; see tools/gen_rules.py.
Apache License 2.0
这个工具回答的是「我中没中」。下面这些它答不了,可以找我做:
📮 [email protected] —— 说清情况,我 24 小时内给你一页书面答复: 能不能做、难在哪、大概多久。这一步免费,也不用你先承诺什么。
| 码 | 含义 |
|---|---|
0 | 扫完了,并且每个文件都真的读进去了,没有需要你动作的发现 |
4 | 有文件读不动 —— 不是 zip、内容截断、或读取失败 |
🔴 4 是 2026-09-09 加的,加它的理由值得说清楚。
在此之前,读不动的文件只在输出里留一行提示,而退出码照旧是 0 ——
留痕是给人看的,CI 和脚本看的是退出码,于是「我没能读它」在自动化里等于「通过」。
一个损坏、加密或下载不全的 jar,会安静地变成一句「没发现问题」。
「我读不动它」和「你是安全的」必须是两句话。
⚠️ 合法的空 jar 不算读不动(一条 22 字节的 EOCD 记录是一个真的空 zip),不会触发 4 ——
假告警多了,真告警就没人看。
其余退出码的含义见上文各判定档位与用法说明。