Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
cve-2026-22732-poc — 演示 CVE-2026-22732 的概念验证,这是 Spring Security 的一个缺陷,其中 setIntHeader("Content-Length") 会丢弃所有安全头,包含易受攻击和已修补的构建版本。 | Kitploit
工具/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
防御工具漏洞分析漏洞利用安全虚拟化Web安全学习与教育
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

演示 CVE-2026-22732 的概念验证,这是 Spring Security 的一个缺陷,其中 setIntHeader("Content-Length") 会丢弃所有安全头,包含易受攻击和已修补的构建版本。

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
查看仓库
11720天前尚未审核

CVE-2026-22732 — 概念验证

Spring Security 会静默丢弃 HTTP 响应安全头。仅供演示 / 教育用途;请仅针对此本地应用运行。

CVECVE-2026-22732 (CWE-425),发布于 2026-03-19
CVSS 3.19.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
直接依赖spring-boot-starter-web + spring-boot-starter-security 2.7.18
受影响组件spring-security-web / -config / -core 5.7.11 — 仅为传递依赖,从未在 pom.xml 中显式声明
修复组件spring-security-web 5.7.14-0.cgr.2,只需修改一处 <version> 即可达到 — 参见过渡过程
验证环境Tomcat 9.0.118、JDK 17.0.18、macOS arm64

受影响范围:5.7.0–5.7.21、5.8.0–5.8.23、6.3.0–6.3.14、6.4.0–6.4.14、6.5.0–6.5.8、7.0.0–7.0.3。 Spring Boot 2.7.18 固定使用 Spring Security 5.7.11,正好落在第一个范围内:

$ mvn dependency:tree -Dincludes=org.springframework.security
+- org.springframework.security:spring-security-config:jar:5.7.11:compile
|  \- org.springframework.security:spring-security-core:jar:5.7.11:compile
\- org.springframework.security:spring-security-web:jar:5.7.11:compile

运行方式

./run.sh        # 终端 1 — 构建并在 :8080 上启动(固定 JDK 17)
./exploit.sh    # 终端 2 — 驱动每个端点,对比响应头差异

run.sh 固定了 JAVA_HOME,因为 Spring Boot 2.7.x 无法在此机器上 mvn 默认解析出的 JDK 25 上运行。可通过 JAVA_HOME_17=/path/to/jdk17 覆盖。

它默认还会使用 -s settings-chainguard.xml 进行构建,因为修复后的父 POM 不在 Maven Central 上。设置 MAVEN_SETTINGS=/path/to/your/settings.xml 可指向其他位置,或设置 MAVEN_SETTINGS= 完全从 Central 构建 — 这仅对原版 2.7.18 有效。

exploit.sh 会从 target/*.jar 中读取实际的 spring-security-web 和 spring-boot 版本,因此其横幅始终报告真正运行的版本,而非硬编码字符串。

环境配置

SecurityConfig 完全没有进行任何响应头定制 — Spring Security 的默认设置生效,这正是注重安全的应用所依赖的。每个端点都返回相同的敏感响应体:

{"account":"4111-1111-1111-1111","holder":"D. Havelock","balance":"82914.55"}

唯一不同的是控制器写入响应的方式。

实测结果

BASELINE  standard Spring MVC return value
  /safe/account                    OK        all 6 headers delivered

CONTROL   getOutputStream(), body > 8 KB buffer
  /vuln/stream/account             OK        all 6 headers delivered

CONTROL   explicit response.flushBuffer()
  /vuln/flush/account              OK        all 6 headers delivered

EXPLOIT   setIntHeader("Content-Length", n)  <-- CVE-2026-22732
  /vuln/content-length/account     BYPASSED  ALL 6 security headers dropped

BY DESIGN application sets its own Expires header (NOT this CVE)
  /vuln/cache/account              PARTIAL   Cache-Control + Pragma dropped

只有 /vuln/content-length/account 在 CVE 被修复后状态发生变化,因此它是 exploit.sh 得出判定结论的唯一端点。其余均为对照组。

该 CVE — setIntHeader("Content-Length", n) → 完全绕过

三行看似普通的控制器代码就剥离了 Spring Security 承诺的每一个响应头:

response.setContentType("application/json");
response.setIntHeader("Content-Length", body.length);
response.getOutputStream().write(body);
$ curl -sD - -o /dev/null http://localhost:8080/vuln/content-length/account
HTTP/1.1 200
Content-Type: application/json
Content-Length: 77
Date: Tue, 01 Sep 2026 00:53:18 GMT

没有 X-Frame-Options,没有 X-Content-Type-Options,没有 Cache-Control,没有 Pragma,没有 Expires,没有 X-XSS-Protection。对比 /safe/account,它携带了全部六个响应头。该响应可被任意源嵌入框架、可被 MIME 嗅探、可被缓存 — 而它提供的却是一个卡号。

设计如此的情况 — 应用自行设置缓存头 → 缓存抑制

更正: 本 README 的早期版本将此称为“exploit 2”,并声称这是公告所记录的条件。它不属于 CVE-2026-22732,且没有任何升级能修复它。CacheControlHeadersWriter 在 5.7.11、5.7.14-0.cgr.2、6.5.8(最后一个受影响版本)和 6.5.9(第一个修复版本)中逐字节相同 — 已通过对比源码 jar 验证。其 Javadoc 明确说明了该行为:“在未指定任何缓存控制头的情况下,插入响应头以防止缓存。”

它仍然值得演示,因为泄漏是真实存在的,且残余风险在打补丁后依然存在。CacheControlHeadersWriter 在 Cache-Control、Expires 或 Pragma 已存在时会直接退出,因此设置三者中任意一个都会抑制 Spring Security 的所有 no-store 指令。一行善意的代码就能做到:

response.setHeader("Expires", "Thu, 01 Jan 2099 00:00:00 GMT");
/safe/account                  NOT cacheable  (Cache-Control: no-cache, no-store, max-age=0, must-revalidate)
/vuln/cache/account            CACHEABLE      (Cache-Control: absent / Expires: Thu, 01 Jan 2099 00:00:00 GMT)
/vuln/content-length/account   CACHEABLE      (Cache-Control: absent / Expires absent)

在上表中 Expires 算作“存在”,但应用选择了一个对攻击者友好的值 — Spring Security 的 Expires: 0 被替换了,而不仅仅是被丢弃。持卡人数据现在可被路径上的每个浏览器和共享代理存储,直到 2099 年。

在修复后的构建中,/vuln/content-length/account 翻转为 NOT cacheable,而 /vuln/cache/account 保持完全如上。只有应用代码或反向代理能修复它 — 这正是演示中值得大声说出的有用信息:升级库关闭了 CVE,却对此毫无影响。

两个刻意保留的负面结果

若干广泛流传的分析文章 — 包括一个公开的复现仓库 — 将 response.getOutputStream() 和 response.flushBuffer() 列为触发条件,解释说“响应在 Spring Security 能注入其响应头之前就已提交”。在 Spring Security 5.7.11 上这是错误的。 两个端点都交付了全部六个响应头。

/diag/committed 展示了为什么该解释不成立。在写入 12 KB 后,响应确实已在控制器内部提交,但响应头仍然到达:

>>> DIAG response.isCommitted() after 12048 byte write = true
    | wrapper class = org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse

OnCommittedResponseWrapper 重写了 flushBuffer() 和输出流写入,因此它能在这些提交之前将响应头发送出去。仅凭提交顺序本身并不是缺陷;声明 Content-Length 的路径才是。Spring 的公告本身从未认可提交顺序这一说法。

保留这两个端点使 PoC 可被证伪:它展示了什么不会复现,与什么会复现同样清晰,且两者在补丁前后都保持绿色,这正是使那个确实翻转的端点具有意义的原因。

从受影响版本到修复版本的过渡

pom.xml 中改一行,仅此而已。 无需修改源码、无需修改属性、无需升级 Spring Boot 大版本:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.18</version>            <!-- vulnerable -->
  <version>2.7.18-0.cgr.3</version>    <!-- patched   -->
</parent>

这会通过父 POM 的 spring-boot-dependencies 将 spring-security.version 从 5.7.11 重新固定为 5.7.14-0.cgr.2(并将 spring-framework.version 从 5.3.31 重新固定为 5.3.39-0.cgr.4)。实测结果:

2.7.182.7.18-0.cgr.3
/safe/accountOK 6/6OK 6/6
/vuln/stream/accountOK 6/6OK 6/6
/vuln/flush/accountOK 6/6OK 6/6
/vuln/content-length/accountBYPASSED 0/6OK 6/6
/vuln/cache/accountPARTIAL 4/6PARTIAL 4/6 (设计如此)
exploit.sh 判定VULNERABLEPATCHED

该向后移植就是上游修复

对比 spring-security-web 源码 jar,只有一个文件重要。5.7.11 → 5.7.14-0.cgr.2 为 OnCommittedResponseWrapper 添加了 setHeader / setIntHeader / addIntHeader 重写,每个都将 Content-Length 路由到 setContentLength():

@Override
public void setIntHeader(String name, int value) {
    checkContentLengthHeader(name, value);   // <-- added
    super.setIntHeader(name, value);
}

修复前只有 addHeader 这样做,因此 setIntHeader("Content-Length", n) 使包装器跟踪的长度保持为 0,onResponseCommitted() 从未触发,HeaderWriterFilter 也从未在 Tomcat 提交响应之前写入其响应头。

对比上游 6.5.8(最后一个受影响版本)与 6.5.9(第一个修复版本)时,同一代码块逐字出现,因此这是官方修复的向后移植,而非重新实现。Chainguard 构建添加了两个上游 6.5.9 没有的空值保护(String 重载上的 value != null,以及 append 中的 (csq != null) ? csq.length() : 4)。

其他缓解措施

Spring Security 5.7.x 在上游已停止维护;OSS 修复仅出现在 6.4.15 / 6.5.9 / 7.0.4+ 中。如果无法选择重新构建的 5.7.x:

  1. 升级脱离 5.7.x — 即迁移到 Spring Boot 3.x。
  2. 变通方案 — 通过 ObjectPostProcessor 设置 HeaderWriterFilter.shouldWriteHeadersEagerly = true。根据公告,这会改变行为:应用写入的响应头随后仅覆盖特定响应头,而不会抑制 Spring Security 的响应头。这一方案还能修复 /vuln/cache/account,而版本升级则不能。
  3. 商业支持 — Tanzu Spring Enterprise 为 5.7.x/5.8.x 提供向后移植。
  4. 纵深防御 — 在反向代理 / 入口处设置响应头,这样被丢弃的应用响应头就不是唯一的控制措施。这是所列选项中唯一同时覆盖两个端点的方案。

这些都没有接入本项目,因此受影响行为是默认状态,而修复状态可通过上述单处 <version> 修改达到。

在不升级 Spring Boot 的情况下修补内嵌 Tomcat

Boot 2.7.18 固定使用 Tomcat 9.0.83,grype . 标记其存在 34 个 CVE(4 个严重)。所有这些都在 9.0.118 或更低版本中修复,而 9.0.118 是最新的 9.0.x 版本 — 因此一个属性即可清除全部:

<properties>
  <tomcat.version>9.0.118</tomcat.version>
</properties>

spring-boot-dependencies 通过这一个属性声明所有 tomcat-embed-* 构件,因此覆盖它会同时重新固定 core、el 和 websocket。已验证:

$ mvn dependency:tree | grep tomcat-embed
tomcat-embed-core:jar:9.0.118:compile
tomcat-embed-el:jar:9.0.118:compile
tomcat-embed-websocket:jar:9.0.118:compile

约束: 保持在 9.0.x 线上。Tomcat 10+ 将 Servlet API 迁移到 jakarta.*,而 Spring Framework 5.3 是针对 javax.servlet 编译的,因此升级到 10.x/11.x 会在运行时因 servlet 类型上的 NoClassDefFoundError 而失败。

修补其他所有内容,仍保持在各自大版本线内

同样的机制应用于 Boot 2.7.18 管理的其余依赖。没有大版本升级,也没有 Spring Boot 升级:

下载工具