跳转至

CVE-2026-43515 — Apache Tomcat 多集合安全约束授权绕过

发布于 2026-06-06

CVSS 9.1 CRITICAL

未经身份验证的攻击者可以通过发送利用约束评估逻辑缺陷的特定 HTTP 方法请求,在不提供任何凭据的情况下获取 Apache Tomcat 服务器上受保护的资源。

Project Apache Tomcat
受影响组件 java/org/apache/catalina/realm/RealmBase.java 中的 RealmBase.findSecurityConstraints()
Severity CRITICAL
CVSS v3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (9.1)
CWE CWE-285
受影响版本 11.0.0-M1 – 11.0.21, 10.1.0-M1 – 10.1.54, 9.0.0.M1 – 9.0.117, 8.5.0 – 8.5.100(EOL,无官方修复),7.0.0 – 7.0.109(EOL,无官方修复)
修复版本 11.0.22, 10.1.55, 9.0.118
安全公告 GHSA-5m62-pw8w-7w9f

1. 漏洞概述

Apache Tomcat 是 Apache 软件基金会维护的 Java Servlet 容器。该漏洞允许未经身份验证的网络攻击者,通过发送约束评估逻辑会静默忽略的 HTTP 方法请求,读取受 web.xml<security-constraint> 保护的资源。

受影响版本为 9.0.0.M1 至 9.0.117、10.1.0-M1 至 10.1.54 以及 11.0.0-M1 至 11.0.21。漏洞于 2026 年 5 月 12 日公开披露,修复版本为 9.0.118、10.1.55 和 11.0.22。

根本原因

该缺陷位于 java/org/apache/catalina/realm/RealmBase.java 中的 RealmBase.findSecurityConstraints() 方法。当 web.xml 中的一个 <security-constraint> 包含多个 URL 模式匹配请求 URI 的 <web-resource-collection> 时,Tomcat 本应依次检查每个集合声明的 HTTP 方法并返回所有适用的约束。但修复前的代码在集合循环外部声明了 boolean matched 标志和 int pos 索引变量。一旦第一个匹配的集合将 matched 设为 true 并在 pos 中记录其索引,后续迭代便无法再更新 pos,因为条件判断 !matched 已为假。循环结束后,方法只查询 collection[pos].findMethod(method),所有后续同样匹配 URL 模式的集合都不再被检查其方法列表。

存在缺陷的代码(节选):

// BEFORE (buggy) — boolean matched / int pos declared OUTSIDE the j-loop
boolean matched = false;
int pos = -1;
for (int j = 0; j < collection.length; j++) {
    String[] patterns = collection[j].findPatterns();
    // ... skips non-extension patterns ...
    for (int k = 0; k < patterns.length && !matched; k++) {
        // extension-pattern matching logic ...
        if (pattern.regionMatches(1, uri, dot, uri.length() - dot)) {
            matched = true;
            pos = j;   // records only the FIRST matching collection
        }
    }
}
// Evaluated only collection[pos]; all later matching collections ignored
if (matched) {
    found = true;
    if (collection[pos].findMethod(method)) {
        if (results == null) { results = new ArrayList<>(); }
        results.add(constraints[i]);
    }
}

补丁差异(在 9.x、10.x 和 11.x 分支的提交中完全相同):

-            boolean matched = false;
-            int pos = -1;
             for (int j = 0; j < collection.length; j++) {
                 String[] patterns = collection[j].findPatterns();

                 // ... skips non-extension patterns ...

+                boolean matched = false;
                 for (int k = 0; k < patterns.length && !matched; k++) {
                     String pattern = patterns[k];
                     if (pattern.startsWith("*.")) {
                         // extension-pattern matching logic
                         if (pattern.regionMatches(1, uri, dot, uri.length() - dot)) {
                             matched = true;
-                            pos = j;
                         }
                     }
                 }
-            }
-            if (matched) {
-                found = true;
-                if (collection[pos].findMethod(method)) {
-                    if (results == null) { results = new ArrayList<>(); }
-                    results.add(constraints[i]);
+                if (matched) {
+                    found = true;
+                    if (collection[j].findMethod(method)) {
+                        if (results == null) { results = new ArrayList<>(); }
+                        results.add(constraints[i]);
+                    }
                 }
             }

修复方案将 boolean matched 移至 j(集合)循环内部,使其在每次迭代开始时重置为 false,同时删除了 pos 变量,改为直接使用循环索引 j,并将方法检查逻辑移入同一循环,使每个匹配的集合都能与请求的 HTTP 方法独立对比。


2. 漏洞环境

复现环境使用两个官方 Apache Tomcat Docker 镜像,以同级容器形式运行在私有桥接网络上:tomcat:10.1.54-jre17(10.1.x 最后一个受影响版本)作为攻击目标,tomcat:10.1.55-jre17(10.1.x 第一个修复版本)作为对比服务器。Tomcat 二进制文件直接使用官方镜像,未做修改,仅叠加了一个受保护的 Web 应用、一个 Realm 用户文件和一个生成动态密钥的入口脚本。

服务 角色 镜像 端口
vulnerable 攻击目标——Tomcat 10.1.54 tomcat:10.1.54-jre17 127.0.0.1:8080
patched 已修复对比服务器——Tomcat 10.1.55 tomcat:10.1.55-jre17 127.0.0.1:8081

下载环境包(env.zip)或单独浏览文件: env/Dockerfileenv/docker-compose.yml

启动环境:

docker compose -f env/docker-compose.yml up -d --build

确认漏洞版本正在运行:

# 预期输出: Server number: 10.1.54.0
docker exec cve-2026-43515-tomcat-vulnerable sh -c 'catalina.sh version 2>/dev/null | grep "Server number"'

# 预期输出: Server number: 10.1.55.0
docker exec cve-2026-43515-tomcat-patched   sh -c 'catalina.sh version 2>/dev/null | grep "Server number"'

确认受保护资源真正受到认证保护(需要认证的 GET 请求被拒绝):

# 预期: 401
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/protected/secret.html

漏洞 Web 应用中的 web.xml 包含以下触发该漏洞的安全约束配置:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>Protected GET</web-resource-name>
    <url-pattern>*.html</url-pattern>
    <http-method>GET</http-method>
  </web-resource-collection>
  <web-resource-collection>
    <web-resource-name>Protected POST</web-resource-name>
    <url-pattern>*.html</url-pattern>
    <http-method>POST</http-method>
  </web-resource-collection>
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>

两个集合共享 *.html URL 扩展名模式。GET 列在第一个集合中,POST 列在第二个集合中,整个约束均要求 <auth-constraint> 中的 admin 角色。在存在缺陷的代码中,Tomcat 评估 POST 请求时仅查询第一个集合,发现其中只列出了 GET 而非 POST,于是将 POST 视为不受约束,放行未经身份验证的请求。

受保护资源 /protected/secret.html 中的 CVE-2026-43515-FLAG-<uuid> 密钥由入口脚本(env/config/entrypoint.sh)在每次容器启动时生成。该值不在构建时写入,因此事先无法预知,可证明资源确实被读取,而非被猜测。


3. 利用方法

前提条件

  • 按第 2 节所述启动环境。
  • 确认两个容器均报告 healthy,且未经身份验证的 GET 请求对受保护资源返回 401(确认认证门控是真实的)。

步骤

1. 运行漏洞利用脚本。

脚本(exploit/run.sh)发送三个请求:控制用的 GET 请求(确认资源受认证保护)、绕过授权的 POST 请求(漏洞利用)、以及对已修复服务器发送相同 POST 请求(确认其拒绝该请求):

bash exploit/run.sh http://127.0.0.1:8080 /protected/secret.html http://127.0.0.1:8081

下载完整的漏洞利用包:exploit.zip

2. 观察输出结果。

在存在漏洞的服务器上,输出如下:

  • [CONTROL] GET:8080HTTP/1.1 401(资源确实受到认证保护)。
  • [EXPLOIT] POST:8080HTTP/1.1 200,包含完整受保护页面内容,其中有一行 SECRET: CVE-2026-43515-FLAG-<uuid>
  • [EXPLOIT] Disclosed secret 部分打印该 SECRET: 行——即绕过请求返回的实际内容,不含任何硬编码值。
  • [NEGATIVE] POST 对已修复的 :8081HTTP/1.1 401(密钥被拒绝)。

3. 通过独立的特权通道验证泄露的密钥。

漏洞利用脚本打印的是服务器返回的任意内容。唯一可信的证明方式,是将其泄露的值与真实的当前密钥对比,而该密钥需通过完全独立于漏洞利用 HTTP 路径的通道读取:

docker exec cve-2026-43515-tomcat-vulnerable cat /seed/secret_value

入口脚本在启动 Tomcat 之前将本次启动的密钥写入 /seed/secret_value,该文件归 root 所有,权限为 0600。漏洞利用脚本无法访问该文件,只能读取 HTTP 响应内容。两个值一致,即证明绕过攻击确实读取了受保护的内容。

已验证运行的观测证据

复现证据如下:

  • 漏洞服务器版本:Server number: 10.1.54.0
  • 已修复服务器版本:Server number: 10.1.55.0
  • 控制用 GET 对 :8080401(响应体中无密钥)
  • 绕过 POST 对 :8080200,响应体包含: SECRET: CVE-2026-43515-FLAG-4356b3e6-d0f3-4c45-9c6d-730333e5dc8a
  • 相同 POST 对已修复的 :8081401
  • 特权通道(真实当前密钥):CVE-2026-43515-FLAG-4356b3e6-d0f3-4c45-9c6d-730333e5dc8a
  • 漏洞利用泄露值与特权通道值完全一致。

为确认密钥确实是每次启动生成的动态值而非构建时固定写入,本次运行记录了连续两次启动,验证密钥在两次启动之间发生了变化:

  • 第 1 次启动密钥:CVE-2026-43515-FLAG-a2def6ba-7c53-4839-90e0-04c2e441a3b5
  • 第 2 次启动密钥:CVE-2026-43515-FLAG-4356b3e6-d0f3-4c45-9c6d-730333e5dc8a

对已修复 10.1.55 服务器运行相同的漏洞利用脚本返回 401,且其独立生成的密钥(CVE-2026-43515-FLAG-4d92cd84-f79d-4bd7-8fd5-cc686dcc4649)与漏洞容器的值不同,确认修复后的阴性对照是真实结果,而非巧合。

该绕过攻击 100% 可确定性触发。只要存在上述 web.xml 配置,漏洞每次均会触发,无需竞争条件、时序依赖或堆布局操作。

清理环境

测试完成后,停止容器并删除其卷:

docker compose -f env/docker-compose.yml down -v

4. 安全建议

修复方案

升级至以下修复版本之一:

分支 修复版本
11.x 11.0.22
10.1.x 10.1.55
9.x 9.0.118

Tomcat 8.5.x 和 7.x 已停止维护,Apache 未为这些分支发布补丁。仍在运行这些版本的用户应尽快升级。

缓解措施与临时解决方案

如果无法立即升级,请逐一检查所有已部署 web.xml 中的 <security-constraint>。触发漏洞的具体模式是:单个 <security-constraint> 包含两个或多个 <web-resource-collection> 块,这些块共享相同的 URL 扩展名模式(如 *.html*.jsp),但各自列出不同的 HTTP 方法。依赖此模式进行访问控制的应用程序,除第一个集合中列出的方法外,其余方法均未受到保护。

若可以修改 web.xml,有两种临时缓解方案:

  1. 将需要相同认证约束的所有方法合并到单个 <web-resource-collection> 块中,使用共享的 URL 模式,并为每个受保护的方法添加一个 <http-method> 元素。
  2. 或者——如果目的是保护所有方法——完全删除 <http-method> 元素;不含方法列表的集合将覆盖所有 HTTP 方法。

如果受保护资源位于其他访问控制层之后(WAF、反向代理、应用层认证检查),这些层可以降低实际暴露面,但不能修复容器层面的授权绕过。

注:NVD/GHSA CVSS 评分为 9.1 CRITICAL,但 Apache 官方安全页面将此漏洞评级为"Moderate"。实际影响取决于约束背后的资源性质;受保护资源若包含敏感数据,危害程度则较高。

参考资料