
log4j 2025/2026 年 7 条「配置静默失效」CVE 自查:按 CVE×模块 判定 4 个模块,结构化解析 log4j2 配置做 applicability 降噪,并算出该升到哪个版本才一次到位(6 条写 2.25.4,但有一条要 2.25.5,而它 Dependabot 报不出来) CVE-2026-49844 / CVE-2026-34477
log4j 2025/2026 年这批「配置静默失效」公告的自查工具。 零运行时依赖,单个 jar,不联网。
⚠️ 先把口径说清楚:这批全是 medium,一条 RCE 都没有,不是 Log4Shell 那种。 它们的共同点是 配置在没有任何报错的情况下失效了 —— 属性被静默忽略、 属性被静默改名、日志被静默丢掉。所以光看版本号看不出你到底中没中,要读配置。 如果你在找 CVE-2021-44228(Log4Shell),这个工具帮不了你。
java -jar log4j-check.jar app.jar # fat jar 里带配置,一步到位
java -jar log4j-check.jar ./target ./src # 构建产物 + 源码
java -jar log4j-check.jar ~/.m2/repository --no-config
Dependabot 告诉你「log4j 有 N 个漏洞」。这个工具回答三个它不回答的:
log4j2.xml 里,能读,而且能读准。这批 7 条里,6 条的修复版都 ≤ 2.25.4。于是绝大多数人会得出「升到 2.25.4」这个答案。
但 CVE-2026-49844 要 2.25.5(2.26 线上要 2.26.1)。它的 advisory 原文写着自己是
CVE-2026-34481 的不完整修复:
The fix released in version
2.25.4did not cover all affected code paths. CVE-2026-49844 was assigned to the remaining issue …
📌 更正(2026-08-16)
本节原本写着「而这一条恰好是 Dependabot 结构性报不出来的那一条」—— 那句话现在只对一半。
GHSA-qv9r-c865-cp47已于 2026-08-13 被 GitHub 转为reviewed并补齐包信息, 补上的两个区间是>= 2.13.1, < 2.25.5和>= 2.26.0, < 2.26.1:
- ✅ 跑 2.x 的人,现在收得到 Dependabot 告警了。
- 🔴 但
3.x预览线(3.0.0-alpha1~3.0.0-beta2)至今没有对应区间,那条线仍然报不出来。升级结论完全没变 ——
log4j-api仍然要 2.25.5(2.26 线要 2.26.1)。 变的只是「机器看不看得见它」。本工具的判定表已按版本线逐条更新,报告里印的数字由表算出,不再硬编码。🔑 这件事本身比原来那个例子更值得记:advisory 的形态会随时间被上游补齐, 而你上一次扫描的结论不会自己更新,也不会有人通知你。 「当时扫过了」和「现在是安全的」,是两件事。
它的 GitHub advisory(GHSA-qv9r-c865-cp47)在 2026-08-13 之前是 unreviewed,
且 vulnerabilities 数组为空 —— 没有包名、没有版本区间、没有修复版,
那段时间 Dependabot 拿不到任何可以跟你的依赖树比对的数据。
在 OSV.dev 上当时同样如此:另外 6 条都能通过 GHSA alias 拿到 Maven 坐标,只有它拿不到
(它在 OSV 里连 GHSA alias 都没有)。这是第三个源上的独立佐证,
tools/recheck_before_publish.py 每次重跑都会重新核实
(该脚本现在也会把「已经补齐」这件事本身报出来)。
🔴 这一条别读成「Dependabot 不好用」。 它是「这条数据当时不存在」,不是「这个工具有 bug」。
unreviewed是 GitHub 的正常流程状态,而 08-13 的补齐正是那个流程在正常走完。
另一条链是 CVE-2025-68161(2.25.3)→ CVE-2026-34477(2.25.4):
verifyHostName 属性自 2.12.0 引入,却一路到 2.25.3 都被静默忽略,
配了等于没配。照 68161 升到 2.25.3、且用属性(而不是 system property)配主机名校验的人,仍然中招。
| 模块 | 命中条目 | 该升到 |
|---|---|---|
log4j-core | 34477 / 34478 / 34480 / 68161 | 2.25.4 |
log4j-api | 49844 | 2.25.5(2.26 线:2.26.1) |
log4j-1.2-api | 34479 | 2.25.4 |
log4j-layout-template-json | 34481 | 2.25.4 |
目标版本不是同一个数字。 而绝大多数项目的 pom 里只写 log4j-core
—— 按那一个坐标反查只查得到 4 条,另外 3 条对他们来说根本不存在。
这个工具把四个模块分别扫出版本并分别判定。四个模块版本错开时(BOM 覆盖、 手工排除、老 WAR 里塞着两代 jar)它会明确报出来。
| CVE | 触发条件(取自官方原文) |
|---|---|
CVE-2026-34477 | <Ssl> 的 verifyHostName 属性 + Socket / Syslog / SMTP appender |
CVE-2025-68161 | SocketAppender + TLS |
CVE-2026-34478 | 直接配 Rfc5424Layout(用 SyslogAppender 的不受影响) |
CVE-2026-34480 | log4j-core 的 XmlLayout |
CVE-2026-34479 | 桥的 Log4j1XmlLayout,或 log4j 1 兼容层 + org.apache.log4j.xml.XMLLayout |
CVE-2026-34481 | JsonTemplateLayout + MapMessage/ObjectMessage 里的浮点值 |
CVE-2026-49844 | JsonTemplateLayout 的 message resolver,或 MapMessage.asJson() |
实测(真实构件,见下):同一套装了受影响版本的四个模块,
Console + RollingFile + PatternLayout)→ 版本层报 7 条,真中 0 条Socket+<Ssl verifyHostName>+Rfc5424Layout+XmlLayout → 报 7 条,真中 4 条两个负判据是官方原文明说的,单独成档:
用 SyslogAppender 的不中 34478;只用 HTTP appender 的 verifyHostname 的不中 34477。
这一档和「我没找到」的可靠程度完全不同 —— 前者有原文背书,后者只是我们没看见。
🔴
verifyHostName(大写 N,<Ssl>,中招)与verifyHostname(小写 n,HTTP appender, 官方写明不受影响**)只差一个字母的大小写,结论完全相反。** 同理XmlLayout(34480,log4j-core)是Log4j1XmlLayout(34479,桥)的子串。 只 grep 名字会让人额外背上不属于他的 CVE。所以配置层做结构化解析而不是文本匹配。
| 格式 | 怎么读 | 能不能判准属性归属 |
|---|---|---|
log4j2*.xml(含 strict 模式 <Appender type="…">) | DOM 解析,XXE 已关 | ✅ |
log4j2*.properties | 按 xxx.type = Foo 建前缀→插件映射 | ✅ |
log4j2*.yaml / .json | 文本匹配(JDK 无 YAML 解析器,而本工具坚持零依赖) | ❌ 标为「文本依据」 |
.java | 文本匹配(已剥注释,认字符串) | ❌ |
配置在归档内部也扫(BOOT-INF/classes/、WEB-INF/classes/),
所以只丢一个 Spring Boot fat jar 给它,降噪也做得成 —— 不需要源码树。
log4j-spring.xml / log4j2-test.xml / log4j.xml(1 兼容层)都认。
「没找到触发条件」不等于安全,至少四种情况会让它变成假的安心:
ConfigurationBuilder / Configurator.initialize);log4j2.configurationFile 指别处、容器里挂进来);「触发条件全部成立」也不等于确认中招:多数条目还要求「攻击者能控制被记进日志的那个值」, 这一点工具判不了。所以结论只够用来排优先级,不够用来宣布事故。
本工具不覆盖 log4j 1.x(log4j:log4j)。扫到会告警,但不判定 —— 它是另一套代码。
python tools/gen_rules.py # 从两个一手源重建 CveTable.java + Triggers.java
logging.apache.org/cyclonedx/vdr.xml)——
条目全集 + 逐模块精确区间 + 描述原文。
🔴
/repos/apache/logging-log4j2/security-advisories实测返回 0 条 —— Apache 不走 GitHub Security Advisories。只查那个端点会得到「log4j 很太平」,而且不报错。
17 条断言,任一不满足就中止不写文件。其中几条是承重的:
| 断言 | 钉住的事 |
|---|---|
| ASSERT2 | 双源盲区:官方 7 条 vs 单坐标 4 条 vs 四坐标 6 条,三个数字都要量出来 |
| ASSERT13 | 那 1 条结构性盲区必须能给出可复核的理由,不许写死在文案里 |
| ASSERT7 | 每个修复版去 Central 实测 + 负对照(证明探测器真会说「拿不到」) |
| ASSERT8 | 修复版是从区间上界推导的 → 必须逐字出现在官方 recommendation 原文里 |
| ASSERT9 | 两条补丁缺口链的原文措辞 + 后一环修复版必须真的更高 |
| ASSERT10 | 照最常见的修复版升级必须仍有漏 —— 否则「求交集」没价值 |
| ASSERT14 | XmlLayout≠Log4j1XmlLayout、verifyHostName≠verifyHostname,正反样本各测一次 |
| ASSERT17 | 描述原文必须是完整句子,不是被 asciidoc 硬换行折断的残句 |
tools/recheck_before_publish.py):
不读生成脚本的任何输出,换三个源(OSV.dev / 官方安全网页 / Maven Central)重新算一遍真实构件复验抓到 3 个单测测不出的问题,都已修 + 加回归:
log4j-api jar 里自带 Log4j-charsets.properties,被当成了用户的 log4j2 配置 →
「本次没看到配置」被翻译成「看过了,没找到」,而只要 classpath 上有 log4j-api,
这个翻译就一直在发生。| 码 | 含义 |
|---|---|
| 0 | 版本没中,并且每个文件都真的读进去了 |
| 2 | 版本中,但配置里没找到触发条件(或本次没看到配置) |
| 3 | 触发条件也成立 |
| 4 | 有文件读不动 —— 不是 zip、内容截断、或读取失败 |
🔴 4 是 2026-09-09 加的。 在此之前,读不动的文件只在输出里留一行提示,而退出码照旧是 0 ——
留痕是给人看的,CI 和脚本看的是退出码,于是「我没能读它」在自动化里等于「通过」。
「我读不动它」和「你是安全的」必须是两句话。
⚠️ 合法的空 jar 不算读不动(一条 22 字节的 EOCD 记录是一个真的空 zip),不会触发 4。
⚠️ 4 只在版本没中时出现:真判出问题(2 / 3)比读不动更要紧,不会被它盖掉。
JDK 17+,mvn package。运行时零依赖 —— 尤其不依赖 log4j 本身:
一个排查 log4j 配置问题的工具自己带着一份 log4j,会在扫自己的目录时把自己报出来。
CVE-2026-34477 https://github.com/advisories/GHSA-6hg6-v5c8-fphqCVE-2026-34478 https://github.com/advisories/GHSA-445c-vh5m-36rjCVE-2026-34479 https://github.com/advisories/GHSA-h383-gmxw-35v2CVE-2026-34480 https://github.com/advisories/GHSA-3pxv-7cmr-fjr4CVE-2026-34481 https://github.com/advisories/GHSA-w35j-pv5h-q9q9CVE-2026-49844 https://github.com/advisories/GHSA-qv9r-c865-cp47 ← 2026-08-13 已转 reviewed 并补齐 2.x 区间;3.x 线仍无覆盖CVE-2025-68161 https://github.com/advisories/GHSA-vc5p-v9hr-52mjApache License 2.0
这个工具回答的是「我中没中」。下面这些它答不了,可以找我做:
📮 [email protected] —— 说清情况,我 24 小时内给你一页书面答复: 能不能做、难在哪、大概多久。这一步免费,也不用你先承诺什么。