
CVE-2026-22732 का प्रमाण-अवधारणा प्रदर्शन, जो Spring Security की एक खामी है जहाँ setIntHeader("Content-Length") सभी सुरक्षा हेडर हटा देता है, जिसमें असुरक्षित और पैच किए गए बिल्ड शामिल हैं।
Spring Security चुपचाप HTTP रिस्पॉन्स सुरक्षा हेडर छोड़ देता है। केवल डेमो / शैक्षिक उपयोग के लिए; इसे इस लोकल ऐप के अलावा किसी और चीज़ के विरुद्ध न चलाएँ।
| CVE | CVE-2026-22732 (CWE-425), प्रकाशित 2026-03-19 |
| CVSS 3.1 | 9.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 # terminal 1 — builds and starts on :8080 (pins JDK 17)
./exploit.sh # terminal 2 — drives every endpoint, diffs the headers
run.sh JAVA_HOME को पिन करता है क्योंकि Spring Boot 2.7.x उस JDK 25 पर नहीं चल सकता जिसे इस मशीन पर
mvn डिफ़ॉल्ट रूप से रिज़ॉल्व करता है। JAVA_HOME_17=/path/to/jdk17 से ओवरराइड करें।
यह डिफ़ॉल्ट रूप से -s settings-chainguard.xml के साथ भी बिल्ड करता है, क्योंकि पैच किया गया पैरेंट
Maven Central पर नहीं है। कहीं और इंगित करने के लिए MAVEN_SETTINGS=/path/to/your/settings.xml सेट करें, या
पूरी तरह Central से बिल्ड करने के लिए MAVEN_SETTINGS= — जो केवल स्टॉक 2.7.18 के लिए काम करता है।
exploit.sh वास्तविक spring-security-web और spring-boot संस्करणों को
target/*.jar से पढ़ता है, इसलिए इसका बैनर हमेशा वही रिपोर्ट करता है जो वास्तव में चल रहा है, न कि कोई हार्डकोडेड स्ट्रिंग।
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
CVE पैच होने पर केवल /vuln/content-length/account ही स्थिति बदलता है, इसलिए यही एकमात्र
एंडपॉइंट है जिससे exploit.sh अपना निर्णय निकालता है। बाकी कंट्रोल हैं।
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 (पहला ठीक किया गया) में बाइट-समान है — सोर्स जार को डिफ करके सत्यापित। इसका Javadoc व्यवहार को
स्पष्ट रूप से बताता है: "Inserts headers to prevent caching if no cache control headers have been
specified."
यह अभी भी प्रदर्शित करने योग्य है, क्योंकि लीक वास्तविक है और अवशिष्ट जोखिम पैचिंग के बाद भी बना रहता है।
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>
यह पैरेंट के 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 पर) फिर से पिन करता है। मापा गया:
spring-security-web सोर्स जार को डिफ करने पर, केवल एक फ़ाइल मायने रखती है। 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 बिल्ड
दो null-guards जोड़ता है जो अपस्ट्रीम 6.5.9 में नहीं हैं (String ओवरलोड पर value != null, और
append में (csq != null) ? csq.length() : 4)।
Spring Security 5.7.x अपस्ट्रीम में end-of-life है; OSS फिक्स केवल 6.4.15 / 6.5.9 / 7.0.4+ में आते हैं। यदि पुनर्निर्मित 5.7.x विकल्प नहीं है:
ObjectPostProcessor के माध्यम से HeaderWriterFilter.shouldWriteHeadersEagerly = true सेट करें।
एडवाइज़री के अनुसार यह व्यवहार बदलता है: एप्लिकेशन-लिखित हेडर तब Spring Security के हेडर को दबाने के बजाय
केवल विशिष्ट हेडर को ओवरराइड करते हैं। यह भी /vuln/cache/account को ठीक करता है, जिसे
संस्करण बंप नहीं करता।इनमें से कोई भी इस प्रोजेक्ट में वायर्ड नहीं है, इसलिए कमजोर व्यवहार डिफ़ॉल्ट है और
पैच किया गया राज्य ऊपर दिए गए एकल <version> परिवर्तन से पहुँचा जा सकता है।
Boot 2.7.18, Tomcat 9.0.83 को पिन करता है, जिसे grype . 34 CVEs (4 Critical) के साथ फ्लैग करता है। इन सभी को
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 अपग्रेड नहीं:
ये ओवरराइड पैच किए गए पैरेंट के साथ इंटरैक्ट करते हैं, इसलिए डेमो करने से पहले जानें कि वे क्या करते हैं।
2.7.18-0.cgr.3 पर पैरेंट पहले से ही tomcat.version 9.0.118, logback.version 1.2.13 और
snakeyaml.version 1.33 प्रदान करता है — वे तीन पंक्तियाँ सटीक डुप्लिकेट बन जाती हैं और कुछ भी बदले बिना हटाई जा सकती हैं।
jackson-bom.version और log4j2.version पंक्तियाँ अभी भी वास्तविक काम करती हैं: पैच किया गया पैरेंट Boot के
स्टॉक 2.13.5 / 2.17.2 रखता है, इसलिए ओवरराइड जीत जाते हैं और वे दो निर्भरताएँ Chainguard-निर्मित के बजाय
सादे अपस्ट्रीम बिल्ड पर रिज़ॉल्व होती हैं। spring-framework.version जानबूझकर कमेंट आउट है, जो पैरेंट के
5.3.39-0.cgr.4 को आने देता है।
grype . प्रगति| State | Findings | Breakdown |
|---|---|---|
| Stock Boot 2.7.18 | 99 | 7C / 39H / 38M / 15L |
| + Tomcat bump | 65 | 3C / 23H / 29M / 10L |
पूरी तरह साफ़: Tomcat (34), Jackson (7), log4j (1)। कुल मिलाकर 99 → 43, Highs 39 → 12।
प्रत्येक बंप के बाद सत्यापित: ऐप Tomcat/9.0.118 पर बूट होता है, सभी छह एंडपॉइंट 200 लौटाते हैं, और
CVE बाइट-दर-बाइट पुनरुत्पादित होता है। Spring Boot अभी भी 2.7.18 है और Spring Security अभी भी 5.7.11, इसलिए
CVE-2026-22732 अछूता है — जो इस अनुभाग का उद्देश्य है, और साथ ही इसकी ईमानदार सीमा:
इसके आसपास सब कुछ पैच करना एप्लिकेशन-फ्रेमवर्क CVE के लिए कुछ नहीं करता। उसे ठीक करने के लिए
पैरेंट बंप चाहिए, प्रॉपर्टी नहीं।
नवीनतम 1.x 1.6.3 है — वही मेजर, इसलिए नाममात्र रूप से दायरे में। यह काम नहीं करता। Logback 1.3+ ने
SLF4J 1.7 StaticLoggerBinder को SLF4J 2.x ServiceLoader प्रोवाइडर से बदल दिया, और Boot 2.7 का
LogbackLoggingSystem सीधे StaticLoggerBinder को कॉल करता है। 1.5.38 के साथ मापा गया:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"
Caused by: java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(...:304)
इससे बचने के लिए SLF4J 2.x (एक मेजर बंप) और Boot 3.x लॉगिंग सिस्टम चाहिए। 1.2.13 वास्तविक सीमा है, जो इस लाइन पर 6 Logback फाइंडिंग्स (2 Medium, 4 Low) को अपरिवर्तनीय छोड़ देता है।
शेष तीन Criticals में से दो को स्कोर के बजाय ठीक से पढ़ने योग्य हैं:
HttpInvokerServiceExporter के माध्यम से
डीसेरियलाइज़ेशन। यह ऐप HTTP Invoker का उपयोग नहीं करता, इसलिए यह यहाँ पहुँच योग्य नहीं है।5.7.14-0.cgr.2 फिक्स संस्करण से आगे है।अवशेष संरचनात्मक है: Spring Framework 5.3.x और Spring Security 5.7.x दोनों EOL हैं। यही, न कि Tomcat या Jackson, Boot 3.x माइग्रेशन का वास्तविक तर्क है।
pom.xml parent + 2 starters, nothing else. Flip the <version> to switch state.
run.sh build + run on JDK 17, via settings-chainguard.xml
exploit.sh header diff, cache impact, clickjacking check, CVE-scoped verdict
settings-chainguard.xml Chainguard Libraries repo -- required for the patched parent
src/main/java/com/example/poc/
PocApplication.java @SpringBootApplication
SecurityConfig.java permitAll, zero header customisation
AccountController.java baseline, 2 exploits, 2 controls, 1 diagnostic
प्रमाणीकरण permitAll है और CSRF बंद है ताकि curl बिना प्रमाणीकरण के काम करे — दोनों में से कोई भी इस CVE का हिस्सा नहीं है।
| 2.7.18 | 2.7.18-0.cgr.3 |
|---|
/safe/account | OK 6/6 | OK 6/6 |
/vuln/stream/account | OK 6/6 | OK 6/6 |
/vuln/flush/account | OK 6/6 | OK 6/6 |
/vuln/content-length/account | BYPASSED 0/6 | OK 6/6 |
/vuln/cache/account | PARTIAL 4/6 | PARTIAL 4/6 (by design) |
exploit.sh verdict | VULNERABLE | PATCHED |
| Property | Boot 2.7.18 default | Pinned here | Ceiling reason |
|---|
tomcat.version | 9.0.83 | 9.0.118 | latest 9.0.x; 10+ is jakarta.* |
spring-framework.version | 5.3.31 | 5.3.39 | last OSS 5.3.x on Central |
jackson-bom.version | 2.13.5 | 2.22.2 | latest 2.x |
log4j2.version | 2.17.2 | 2.26.1 | latest 2.x |
snakeyaml.version | 1.30 | 1.33 | last 1.x; fix for the remaining CVE is 2.0 |
logback.version | 1.2.12 | 1.2.13 | last 1.2.x — see below |
spring-security.version | 5.7.11 | left alone | it is the subject of the demo |
| + all in-major bumps |
| 43 |
| 3C / 12H / 18M / 10L |
| Component | Why it can't be fixed in-major |
|---|
| spring-webmvc / -expression / -core / -context (25) | 5.3.39 is the last OSS 5.3.x; 14 of 15 webmvc findings have no fix at all, and the 5.3.42 grype cites is commercial-only |
| logback-core (6) | needs SLF4J 2.x, see above |
| spring-security-* (8) | EOL line; CVE-2026-22732 is deliberate |
| spring-boot / -autoconfigure (3) | no fix published for 2.7.x |
| snakeyaml (1) | CVE-2022-1471 is fixed only in 2.0 |