惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Y
Y Combinator Blog
月光博客
月光博客
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
博客园 - 司徒正美
V
Visual Studio Blog
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
The Cloudflare Blog

FreeBuf网络安全行业门户

Anthropic 强化 Claude 安全防护:AI 模型在评估中未经授权访问真实系统 黑名单只防了 AWS?Directus 默认配置下 SSRF 直通国产云 metadata 告警堆到 100 万条那天,我决定自己写一个“会用 AI“的安全运营中心 - FreeBuf网络安全行业门户 新手勇闯网络安全 | 网络通信基础(三) - FreeBuf网络安全行业门户 FreeBuf早报 | 宇树G1 EDU人形机器人漏洞可致root级远程代码执行;Claude平台遭攻击 - FreeBuf网络安全行业门户 保障 Claude Code 安全:全新 Compliance API、本地可见性与身份治理 Apache Shiro rememberMe 反序列化漏洞:从 Cookie 到 RCE 免费路由器 DNS 调整可拦截家庭网络中的恶意软件和钓鱼攻击 - FreeBuf网络安全行业门户 黑客利用信息窃取恶意软件窃取 Claude 登录会话,劫持账户 - FreeBuf网络安全行业门户 奇安信2026半年报:营收跌了14%,亏损却砍半,这笔账怎么算? - FreeBuf网络安全行业门户 出厂即后门:一台深圳路由器里藏着的两个钉子户——SPEAKINGSTONE 与 DARKLANTERN 拆解 - FreeBuf网络安全行业门户 一个 STOR 文件名,让 PostgreSQL 替你执行命令——ProFTPD mod_sql 认证后 RCE 拆解(CVE-2026-42167) 让 root 替你写文件:cPanel 域停放附加域提权拆解(CVE-2026-65643) - FreeBuf网络安全行业门户 量化公司策略代码全生命周期安全平台建设方案 - FreeBuf网络安全行业门户 AI Agent 工具调用的信任边界:从 MCP 攻击面到"Agent 信任链"模型 新手勇闯网络安全 | 网络通信基础(二) - FreeBuf网络安全行业门户 HW 蓝队·异常外联流量溯源实战:那些告警背后真正在发生什么 - FreeBuf网络安全行业门户 安全度量:拿什么向管理层证明安全的价值 - FreeBuf网络安全行业门户 曼彻斯特机场集团确认客户数据遭窃取 - FreeBuf网络安全行业门户 黑客利用 Claude 和 ChatGPT 入侵多个政府机构 报告:思科或以超2.5亿美元收购AI Agent安全初创公司Astrix Security 【安全圈】国家网络安全通报中心:近期集中爆发多起供应链投毒攻击事件 员工自费买Token引爆内网?揭秘AI中转站的四种“投毒”手段与供应链危机 AI 路由漏洞可被利用注入恶意代码并窃取敏感数据 黑客滥用 GitHub 和 GitLab 托管恶意软件并实施凭证钓鱼攻击 Claude 在数分钟内发现存在 13 年的 ActiveMQ 远程代码执行漏洞 2026攻防演练红队高频面试题30个(含答案)! AI Agent沙箱之ANOLISA内置沙箱 XXE 漏洞原理剖析:DTD、实体解析机制与攻击链构建 谷歌预警引发连锁反应:Cloudflare正积极调整抗量子加密战略优先级
Apache Tomcat CVE-2026-65182 分析:一次 SecurityConstrain...
关 注 0 文章数 0 关注者 · 2026-08-29 · via FreeBuf网络安全行业门户

CVE-2026-65182 是 Apache Tomcat 中一个重要的安全约束绕过漏洞,由声明式安全约束(security-constraint)的处理缺陷导致。当为较长路径定义的约束,出现在为较短子路径定义的更严格约束之前时,可能导致更严格的规则被绕过。

0. 结论速览

  • 是什么:web.xml 里用多条“访问规则”控制谁能访问哪个路径。当一条“长路径(拒绝)”和一条“短路径(放行)”重叠时,Tomcat 会挑错规则去执行,把本应拒绝的路径放行。

  • 根因:Tomcat 挑选“最长匹配”规则时,同一个规则里后写的短路径会把先写长路径的匹配长度调小,于是本该生效的“拒绝”被当成“不够长”而淘汰,改由“放行”生效。

  • 怎么验证:构造满足L2 < L3 < L1的配置,对深层路径发未认证请求。9.0.120 返回200(越权),而修复版应返回401/403

  • 受影响范围(谨慎表述):并非“有公开子路径就受影响”,而是同时满足以下三点才可能——同一规则(collection)内多个 URL Pattern 且深层在前长短路径之间夹有一条介入的放行规则该拒绝规则先于放行规则声明。具体边界见第 2.1 节。

1. 技术根因与源码分析

每次请求进来,Tomcat 会先找出一条“管这个 URL 的访问规则”去执行。找规则这一步有个小 bug:本应生效的“拒绝访问”会被错误筛掉。先给一个不依赖源码的模型,再逐步进入源码。

1.1 漏洞模型

关键的一句话是:同一个规则里既写了长路径、又写了短路径,且短路径写在后面时,短路径会把该规则“代表的最长匹配值”调小。

拒绝规则(声明在前):
   /level1/level2/level3/*   (深层)
   /level1/*                 (浅层兜底,写在后面)

Tomcat 实际认为它代表的最长匹配:
   /level1/*                 ← 被短路径调小了,本应是深层那一条

此时若目标 uri 落在两者之间、且由另一条规则放行(如 /level1/level2/* 为放行):
   放行规则会被误当成“最长”而生效 → 越权

整篇内容的机制都围绕这一句展开,后面只是用源码把它坐实。

1.2 请求 → 调用链

先交代“谁在管这件事”:URL→Servlet 的映射由Mapper负责;而访问规则是否放行,是在请求进入应用后的鉴权环节里检查的,两者相互独立。

安全约束的匹配不在Mapper中,而是在 Context 管道的认证 Valve 里:

Coyote Adapter (Adapter.service)
 └─ StandardEngineValve → StandardHostValve → StandardContextValve
     └─ 各类 ContextValve(含 AuthenticatorBase.invoke())
         ├─ AuthenticatorBase.invoke
         │    ├─ L523  realm.findSecurityConstraints(request, context)  ← 漏洞入口
         │    ├─ L556  realm.hasUserDataPermission(...)   (transport-guarantee)
         │    ├─ L605  doAuthenticate / authenticateJaspic (认证,可选)
         │    └─ L635  realm.hasResourcePermission(...)   ← 最终授权判定

源码位置:

  • 认证入口:AuthenticatorBase.java:/tomcat/java/org/apache/catalina/authenticator/AuthenticatorBase.java#L519-L639

  • 约束求值:RealmBase.findSecurityConstraints:tomcat/java/org/apache/catalina/realm/RealmBase.java#L553-L693

  • 授权判定:RealmBase.hasResourcePermission:tomcat/java/org/apache/catalina/realm/RealmBase.java#L795-L859

AuthenticatorBase.java#L519-L536(含 L523 调用点 + “无约束直接放行”分支
AuthenticatorBase.java#L519-L536(含 L523 调用点 + “无约束直接放行”分支)

1.3 约束的存储与“最长匹配优先”

规则按写在配置里的先后顺序存在 Context 里,Tomcat 不做排序。多个规则重叠时,它靠“谁的路径最长谁说了算”来判断用哪条。

  • 约束在 Context 中按声明顺序保存(context.findConstraints()),无排序。

  • “最长匹配优先”由变量longest实现:一旦发现更长匹配,就用results.clear()丢弃之前的较短结果,最后只保留最长匹配的那条约束进授权判定。

  • 正是这个“只留最长”的逻辑缺陷构成了绕过基础。

SecurityConstraint 拒绝/放行语义 #L154-L166(findAuthRoles #L329,SecurityCollection #L296/#L232)

1.4 根因(源码)

漏洞函数findSecurityConstraints中,对每个SecurityCollection的模式循环(9.0.120):

// RealmBase.java:644-659 (9.0.120)
boolean matched = false;
int length = -1;
for (String pattern : patterns) {
    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest) {
        if (pattern.length() == 2) {                 // "/*"
            matched = true;
            length = pattern.length();
        } else if (pattern.regionMatches(0, uri, 0, pattern.length() - 1)
                || (pattern.length() - 2 == uri.length()
                        && pattern.regionMatches(0, uri, 0, pattern.length() - 2))) {
            matched = true;
            length = pattern.length();               // ← 后出现的短模式覆盖 length
        }
    }
}
if (matched) {
    if (length > longest) {                          // 用被低估的 length 更新全局 longest
        found = false;
        if (results != null) results.clear();
        longest = length;
    }
    ...
}

问题出在length:同一个集合内,后遍历到的短模式会覆盖前面长模式得到的值。以拒绝规则[ /level1/level2/level3/* , /level1/* ]为例,Tomcat 先算出 25,又被后写的/level1/*覆盖成 9;随后长度 16 的放行规则/level1/level2/*因大于 9 而“反超”生效,拒绝规则被挤掉,受保护目录被匿名放行。
根因循环 RealmBase.java#L619-L681(L645/L647/L651/L655/L661
根因循环 RealmBase.java#L619-L681(L645/L647/L651/L655/L661)

1.5 修复 diff(9.0.120 vs 9.0.121)

修复文件清单:

文件改动
java/org/apache/catalina/realm/RealmBase.java核心修复:通配模式匹配门控
test/.../TestRealmBase.java新增回归测试testOverlappingConstraints
test/.../TesterRequest.java支撑测试(支持自定义 URI)
webapps/docs/changelog.xml变更说明

核心 diff:

// RealmBase.java:647
 for (String pattern : patterns) {
-    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest) {
+    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest &&
+            pattern.length() >= length) {
         ...

git diff 9.0.120 vs 9.0.121
git diff 9.0.120 vs 9.0.121

git diff 门控改动

维度修改前(9.0.120)修改后(9.0.121)
门控条件pattern.length() >= longest增加pattern.length() >= length
同集合内length更新后者覆盖前者只能被更长的模式更新
全局longest可能被低估(如 25→9)保持真实最长匹配(25)
结果选择放行约束可能反超、拒绝被挤出拒绝被正确保留

2. 触发条件与边界

一个“拒绝”规则里常常会同时写长路径和短路径(例如“整站要登录”+“深层目录更要控”)。只要中间恰好夹了一条更短的“放行”路径,Tomcat 就可能把放行当“最长”去执行。

2.1 触发条件

用长度关系概括:把拒绝规则的最长路径长度叫 L1较短兜底路径长度叫 L2,把夹在中间的放行路径长度叫 L3。当三者满足L2 < L3 < L1时,绕过才有可能成立。

L2 < L3 < L1
  • L2 < L3:拒绝的length被覆盖为L2后,放行L3才可能反超胜出。

  • L3 < L1:修复后拒绝保留真实的L1,仍能盖过放行。

最小必要配置(与官方回归测试同构):

  • 拒绝约束的单个web-resource-collection内、/*两模式「深层在前、浅层在后」、带空<auth-constraint>

  • 放行约束模式为子路径、无<auth-constraint>

  • 声明顺序:拒绝在前、放行在后;

  • 请求 URI 同时命中深层与浅层。

2.2 不触发的边界情形

情形结果
拒绝集合只有一个命中模式L1胜出 → 拒绝(无覆盖)
L3 >= L1(放行比拒绝深层还长)放行本就是最长匹配,属合理放行
L3 <= L2(放行 ≤ 浅层)放行无法反超被低估的longest=L2
声明顺序颠倒(拒绝在放行之后)拒绝先以真实L1占位,放行无法胜出

绕过成立需同时具备三点:同一拒绝集合内有序多模式长度介于其间的放行子路径拒绝先于放行声明,缺一不可。

2.3 机制示意

URI = /level1/level2/level3/index.jsp

【9.0.120 · 漏洞版】                          【9.0.121 · 修复版】
遍历 outer 集合(拒绝):                        遍历 outer 集合(拒绝):
  /level1/level2/level3/* → len=25 匹配          /level1/level2/level3/* → len=25 匹配 → length=25
  /level1/*               → len=9 覆盖 length    /level1/* → 9 < length(25) → 被门控跳过
  └ length=9 → longest=9, 结果=[outerDeny]      └ length=25 → longest=25, 结果=[outerDeny]

遍历 inner 集合(放行):                         遍历 inner 集合(放行):
  /level1/level2/* → len=16 >= longest(9)        /level1/level2/* → len=16 < longest(25) → 跳过
  └ longest=16, results.clear()                 └ 结果仍=[outerDeny]
    结果=[innerAllow]
  └ 拒绝约束被挤出,匹配到放行约束

hasResourcePermission(innerAllow)                hasResourcePermission(outerDeny)
  → 无 auth-constraint → 放行 200 越权           → 空角色且 auth-constraint → 拒绝 403

根因循环源码 RealmBase.java#L619-L681(突出 L647/L651/L655
根因循环源码 RealmBase.java#L619-L681(突出 L647/L651/L655)

3. 场景与验证

3.1 场景示例

某系统同一 Context 同时部署公开内容与后台数据目录,访问控制依赖声明式约束:

<!-- 拒绝:深层路径先声明,浅层兜底后声明 -->
<security-constraint>
    <web-resource-collection>
        <url-pattern>/app/private/data/*</url-pattern>   <!-- 深层, 先声明 -->
        <url-pattern>/app/*</url-pattern>                  <!-- 浅层兜底, 后声明 -->
    </web-resource-collection>
    <auth-constraint/>
</security-constraint>

<!-- 放行:/app/private/* 下的部分开放资源(长度介于上面两者之间) -->
<security-constraint>
    <web-resource-collection>
        <url-pattern>/app/private/*</url-pattern>
    </web-resource-collection>
    <!-- 无 auth-constraint -->
</security-constraint>

未认证请求:

curl -i http://victim/app/private/data/index.jsp

漏洞版(9.0.120):

HTTP/1.1 200 OK
...(受限业务数据)...

修复版(9.0.121):/app/private/data/*保持 len 20,放行len 14无法反超 →401/403

同集合内/app/private/data/*(len20)被/app/*(len6)覆盖 →longest=6;放行/app/private/*(len14,满足6<14<20)被误判为最长,拒绝被丢弃,匿名访问成功。

3.2 影响评估方法

白盒审计(能拿到配置/源码时)

按声明顺序枚举约束,抓“同一 collection 多/*模式 + 嵌套放行子路径”的组合:

# 提取约束及其 url-pattern,用 -B/-A 保留声明顺序
grep -nE '<security-constraint>|<url-pattern>|<auth-constraint>' WEB-INF/web.xml

人工或脚本核对是否存在满足L2 < L3 < L1的三元组,且拒绝先于放行声明。这是触发前提。

黑盒验证(无法读配置时)

  • 先定位“看似应受保护”的深层路径(如/app/private/data//admin/…/manage/)。

  • 对深层路径与“疑似公开子路径”分别发未认证请求,比较响应码与响应体差异。

  • 若某深层路径在匿名且无跳转下返回200 + 业务内容,而相邻同级别路径返回401/403,即可作为待复核的命中点。

需要注意的是:黑盒场景下“该不该公开”需结合应用特点判断,命中点需以两版行为差或边界公式印证,避免误报。

3.3 影响验证要点

  1. 优先选择满足L2 < L3 < L1且拒绝先声明的组合,更容易触发。

  2. 请求 URI 必须同时命中深层与浅层模式(落在深层子树的任意资源)。

  3. 保持匿名:不带Authorization/Cookie,排除“本身允许匿名”的干扰;同时试GET/POST双方法(约束可按 HTTP 方法过滤,方法不同结论可能不同)。

  4. 判定标准(避免误报):

    • 绝对:同一份配置下,修复版返回401/403,而漏洞版返回200

    • 相对:深层路径匿名可达,而相邻同级受保护路径被拦。

  5. 确认修复语义:仅当放行模式长度“介于”拒绝集合深浅两层之间才构成绕过,否则属合理放行。

3.4 变体与进一步验证方向(以下为建议,本文未逐一实测)

  • 方法级差异:约束可按 HTTP 方法限定,同一路径下GETPOST的绕过结论可能不同。

  • /*兜底模式:模式恰为/*(len 2)走独立分支,与多层/*混排时可构造不同覆盖组合。

  • 多集合 / 多放行:一个拒绝集合对多个放行约束、或多个拒绝集合并存时,results.clear()的相互影响可能产生更多变化。

  • 跨分支交叉验证:9.0 / 10.1 / 11.0 共用同一段逻辑,若某分支行为存在差异,也可作为交叉验证靶点。

  • 与路径归一化的交互:uri取自解码后的请求路径;若上游存在路径归一化/重写(如 RewriteValve),可能与约束匹配产生交互,值得单独研究。

4. 复现实验

环境:OpenJDK 17.0.20 + Apache Ant 1.10.14,源码仓库 tomcat tag 9.0.120。

4.1 实测结果

  • 9.0.120:ant compile+ant test-compile编译主源码(1840)与测试类(865);将官方testOverlappingConstraints回填后运行 → FAILAssertionError位于TestRealmBase.java:986,level3 的assertFalse返回 true ⇒ 放行)。

  • 9.0.121:ant clean compile test-compile后运行官方自带同一测试 → PASS(Failures: 0)。

9.0.120  FAIL  TestRealmBase.testOverlappingConstraints  →  level3 越权放行(应 403 反 200)
9.0.121  PASS  Failures: 0                                →  level1/2/3 行为全部正确

官方回归测试 testOverlappingConstraints(TestRealmBase.java#L945-L987,9.0.121 版本
官方回归测试 testOverlappingConstraints(TestRealmBase.java#L945-L987,9.0.121 版本)

4.2 可重复复现步骤

# 1) 准备
apt-get install -y ant

# 2) 9.0.120(漏洞版)
git -C /root/tomcat checkout 9.0.120
cd /root/tomcat && ant compile test-compile
#   将官方 testOverlappingConstraints 回填到 TestRealmBase,重跑:
#   java -cp output/classes:output/testclasses:<junit4.13.2>:<hamcrest> \
#        org.junit.runner.JUnitCore ... → 断言失败 ⇒ 漏洞成立

# 3) 9.0.121(修复版)
git checkout 9.0.121 && ant clean compile test-compile
#   官方自带 TestRealmBase.testOverlappingConstraints → 通过 ⇒ 已修复

5. 延伸与后续方向

  • 这类缺陷不依赖具体数据,只要满足长度关系与声明顺序即可触发,属于授权选择逻辑的普遍性问题,可对其它“最长前缀选择”的授权/匹配器做同类审计。

  • 7.x / 8.5.x / 9.x / 10.1.x / 11.x 共用该段逻辑,修复需要逐分支打补丁(这里给出 9.0.x 的b2c56ec)。

  • 可对“约束集合(模式组合/顺序/方法)× 请求路径”做组合化的测试,系统排查同类排序/选择缺陷。

附录 · 处置

A. 修复版本与升级目标

分支升级目标
9.0.x9.0.121
10.1.x10.1.58
11.0.x11.0.25

影响版本:9.0.0.M1~9.0.120、10.1.0-M1~10.1.57、11.0.0-M1~11.0.24,以及已 EOL 的 8.5.0~8.5.100、7.0.0~7.0.109。修复 commit(9.0.x):b2c56ec8f20c66773a1a813034ffcdb60841f1cf

B. 升级前的临时缓解

将「长路径宽松约束在前、短路径严格约束在后」的组合调整为更严格约束前置声明,并审查同一web-resource-collection内多个/*模式的顺序。

C. 取证自查

  1. 枚举约束并保留声明顺序。

  2. 定位“同一 collection 含 ≥2 个/*模式”且“深层在前、浅层在后”的约束。

  3. 核对是否存在L2 < L3 < L1的嵌套放行子路径。

  4. 确认拒绝先于放行声明。

  5. 探针:对深层路径未认证访问,预期401/403;若返回200并含业务内容,则确认受影响。

grep -nE '<security-constraint>|<url-pattern>|<auth-constraint>' WEB-INF/web.xml
curl -i http://<host>:<port>/app/private/data/index.jsp   # 不带凭证

局限与说明

  • 本报告结论基于官方公告、源码(tag 9.0.120 / 9.0.121)及本地一次真实编译的端到端测试;未使用公开 PoC。

  • 9.0.120 的失败结果通过「回填官方回归测试」触发原生未修复字节码得出;9.0.121 直接使用官方自带测试。

URI 来源细节:TesterRequest.java#L84-L96image

报告生成环境:Apache Tomcat 源码仓库(9.0.120 / 9.0.121),OpenJDK 17,Apache Ant 1.10.14。