Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-22732-poc — Proof-of-concept demonstrating CVE-2026-22732, a Spring Security flaw where setIntHeader("Content-Length") drops all security headers, with vulnerable and patched builds. | Kitploit
Tools/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Defensive ToolsVulnerability AnalysisExploitationSecurity VirtualizationWeb SecurityLearning & Education
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

Proof-of-concept demonstrating CVE-2026-22732, a Spring Security flaw where setIntHeader("Content-Length") drops all security headers, with vulnerable and patched builds.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
View Repository
11821 days agoNot yet reviewed
Share

CVE-2026-22732 — Proof of Concept

Spring Security silently drops HTTP response security headers. Demo / educational use only; run it against nothing but this local app.

CVECVE-2026-22732 (CWE-425), published 2026-03-19
CVSS 3.19.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Direct dependencyspring-boot-starter-web + spring-boot-starter-security 2.7.18
Vulnerable componentspring-security-web / -config / -core 5.7.11 — transitive only, never named in pom.xml
Fixed componentspring-security-web 5.7.14-0.cgr.2, reached by one <version> change — see the transition
Verified onTomcat 9.0.118, JDK 17.0.18, macOS arm64

Affected ranges: 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 pins Spring Security 5.7.11, squarely inside the first range:

$ 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 it

./run.sh        # terminal 1 — builds and starts on :8080 (pins JDK 17)
./exploit.sh    # terminal 2 — drives every endpoint, diffs the headers

run.sh pins JAVA_HOME because Spring Boot 2.7.x cannot run on the JDK 25 that mvn resolves by default on this machine. Override with JAVA_HOME_17=/path/to/jdk17.

It also builds with -s settings-chainguard.xml by default, because the patched parent is not on Maven Central. Set MAVEN_SETTINGS=/path/to/your/settings.xml to point elsewhere, or MAVEN_SETTINGS= to build purely from Central — which works for stock 2.7.18 only.

exploit.sh reads the actual spring-security-web and spring-boot versions out of target/*.jar, so its banner always reports what is really running rather than a hardcoded string.

The setup

SecurityConfig applies no header customisation at all — Spring Security's defaults are in force, which is exactly what a security-conscious app relies on. Every endpoint returns the same sensitive body:

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

The only thing that varies is how the controller writes the response.

Measured results

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

Only /vuln/content-length/account changes state when the CVE is patched, so it is the only endpoint exploit.sh derives its verdict from. The rest are controls.

The CVE — setIntHeader("Content-Length", n) → total bypass

Three lines of ordinary-looking controller code strip every header Spring Security promised:

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

No X-Frame-Options, no X-Content-Type-Options, no Cache-Control, no Pragma, no Expires, no X-XSS-Protection. Compare /safe/account, which carries all six. The response is framable by any origin, MIME-sniffable, and cacheable — while serving a card number.

The by-design case — application-set cache header → cache suppression

Corrected: an earlier version of this README called this "exploit 2" and claimed it was the condition the advisory documents. It is not part of CVE-2026-22732 and no upgrade fixes it. CacheControlHeadersWriter is byte-identical in 5.7.11, 5.7.14-0.cgr.2, 6.5.8 (last vulnerable) and 6.5.9 (first fixed) — verified by diffing the sources jars. Its Javadoc states the behaviour outright: "Inserts headers to prevent caching if no cache control headers have been specified."

It is still worth demonstrating, because the leak is real and the residual risk survives patching. CacheControlHeadersWriter bails out if Cache-Control, Expires or Pragma is already present, so setting any one of the three suppresses all of Spring Security's no-store directives. One well-intentioned line does it:

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 counts as "present" in the table above, but with an attacker-friendly value the app chose — Spring Security's Expires: 0 was replaced, not merely dropped. Cardholder data is now storable by every browser and shared proxy on the path until 2099.

On the patched build /vuln/content-length/account flips to NOT cacheable, while /vuln/cache/account stays exactly as above. Only application code or a reverse proxy fixes it — which is the useful thing to say out loud in a demo: upgrading the library closes the CVE and leaves this untouched.

Two negative results, kept on purpose

Several widely-circulated write-ups — including a public reproduction repo — list response.getOutputStream() and response.flushBuffer() as triggers, explaining that "the response is committed before Spring Security can inject its headers". On Spring Security 5.7.11 that is wrong. Both endpoints deliver all six headers.

/diag/committed shows why the explanation doesn't hold. After a 12 KB write the response really is committed inside the controller, yet the headers still arrive:

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

OnCommittedResponseWrapper overrides flushBuffer() and the output-stream writes, so it gets its headers out ahead of those commits. Commit-ordering alone is not the bug; the declared-Content-Length path is. The Spring advisory itself never endorses the commit-ordering story.

Keeping these two endpoints in makes the PoC falsifiable: it shows what does not reproduce as clearly as what does, and both stay green across the patch, which is what makes the one endpoint that does flip meaningful.

The vulnerable → patched transition

One line in pom.xml, nothing else. No source change, no property change, no Spring Boot major bump:

<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>

That re-pins spring-security.version from 5.7.11 to 5.7.14-0.cgr.2 (and spring-framework.version from 5.3.31 to 5.3.39-0.cgr.4) through the parent's spring-boot-dependencies. Measured:

Download Tool