Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
cve-2026-22732-poc — Proof-of-concept che dimostra CVE-2026-22732, una vulnerabilità di Spring Security in cui setIntHeader("Content-Length") elimina tutti gli header di sicurezza, con build vulnerabili e corrette. | Kitploit
Strumenti/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Strumenti DifensiviAnalisi delle VulnerabilitàExploitVirtualizzazione per la SicurezzaSicurezza WebApprendimento e Formazione
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

Proof-of-concept che dimostra CVE-2026-22732, una vulnerabilità di Spring Security in cui setIntHeader("Content-Length") elimina tutti gli header di sicurezza, con build vulnerabili e corrette.

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Vedi Repository
2h 52m faNon ancora revisionato
Condividi

CVE-2026-22732 — Proof of Concept

Spring Security elimina silenziosamente gli header di sicurezza delle risposte HTTP. Solo per uso dimostrativo/educativo; eseguilo solo contro questa app locale.

CVECVE-2026-22732 (CWE-425), pubblicata il 2026-03-19
CVSS 3.19.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Dipendenza direttaspring-boot-starter-web + spring-boot-starter-security 2.7.18
Componente vulnerabilespring-security-web / -config / -core 5.7.11 — solo transitiva, mai dichiarata nel pom.xml
Componente correttaspring-security-web 5.7.14-0.cgr.2, raggiunta con un solo cambio di <version> — vedi la transizione
Verificato suTomcat 9.0.118, JDK 17.0.18, macOS arm64

Intervalli interessati: 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 fissa Spring Security 5.7.11, pienamente all'interno del primo intervallo:

root@kitploit:~
$ 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

Esecuzione

root@kitploit:~
./run.sh        # terminale 1 — compila e avvia su :8080 (fissa JDK 17)
./exploit.sh    # terminale 2 — interroga ogni endpoint, confronta gli header

run.sh fissa JAVA_HOME perché Spring Boot 2.7.x non può girare sul JDK 25 che mvn risolve per default su questa macchina. Sovrascrivi con JAVA_HOME_17=/path/to/jdk17.

Compila anche con -s settings-chainguard.xml per default, perché il parent corretto non è su Maven Central. Imposta MAVEN_SETTINGS=/path/to/your/settings.xml per puntare altrove, oppure MAVEN_SETTINGS= per compilare esclusivamente da Central — cosa che funziona solo con il 2.7.18 originale.

exploit.sh legge le versioni effettive di spring-security-web e spring-boot da target/*.jar, quindi il suo banner riporta sempre ciò che è realmente in esecuzione anziché una stringa hardcoded.

Il setup

SecurityConfig non applica nessuna personalizzazione degli header — sono in vigore i default di Spring Security, che è esattamente ciò su cui si basa un'app attenta alla sicurezza. Ogni endpoint restituisce lo stesso body sensibile:

root@kitploit:~
{"account":"4111-1111-1111-1111","holder":"D. Havelock","balance":"82914.55"}

L'unica cosa che varia è come il controller scrive la risposta.

Risultati misurati

root@kitploit:~
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

Solo /vuln/content-length/account cambia stato quando la CVE viene corretta, quindi è l'unico endpoint da cui exploit.sh deriva il suo verdetto. Gli altri sono controlli.

La CVE — setIntHeader("Content-Length", n) → bypass totale

Tre righe di codice del controller dall'aspetto ordinario eliminano ogni header che Spring Security aveva promesso:

root@kitploit:~
response.setContentType("application/json");
response.setIntHeader("Content-Length", body.length);
response.getOutputStream().write(body);
root@kitploit:~
$ 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

Nessun X-Frame-Options, nessun X-Content-Type-Options, nessun Cache-Control, nessun Pragma, nessun Expires, nessun X-XSS-Protection. Confronta con /safe/account, che li porta tutti e sei. La risposta è incorniciabile da qualsiasi origine, MIME-sniffable e cacheable — mentre serve un numero di carta.

Il caso by-design — header di cache impostato dall'applicazione → soppressione della cache

Corretto: una versione precedente di questo README lo chiamava "exploit 2" e sosteneva fosse la condizione documentata nell'advisory. Non fa parte di CVE-2026-22732 e nessun aggiornamento lo corregge. CacheControlHeadersWriter è identico byte per byte in 5.7.11, 5.7.14-0.cgr.2, 6.5.8 (ultima vulnerabile) e 6.5.9 (prima corretta) — verificato confrontando i sources jar. Il suo Javadoc afferma esplicitamente il comportamento: "Inserts headers to prevent caching if no cache control headers have been specified."

Vale comunque la pena dimostrarlo, perché la fuga è reale e il rischio residuo sopravvive alla patch. CacheControlHeadersWriter si ferma se Cache-Control, Expires o Pragma è già presente, quindi impostare uno qualsiasi dei tre sopprime tutte le direttive no-store di Spring Security. Una sola riga ben intenzionata lo fa:

root@kitploit:~
response.setHeader("Expires", "Thu, 01 Jan 2099 00:00:00 GMT");
root@kitploit:~
/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 conta come "presente" nella tabella sopra, ma con un valore favorevole all'attaccante scelto dall'app — l'Expires: 0 di Spring Security è stato sostituito, non semplicemente eliminato. I dati del titolare della carta sono ora memorizzabili da ogni browser e proxy condiviso sul percorso fino al 2099.

Sulla build corretta /vuln/content-length/account passa a NOT cacheable, mentre /vuln/cache/account resta esattamente come sopra. Solo il codice dell'applicazione o un reverse proxy lo corregge — che è la cosa utile da dire ad alta voce in una demo: aggiornare la libreria chiude la CVE e lascia questo intatto.

Due risultati negativi, mantenuti di proposito

Diversi write-up ampiamente diffusi — incluso un repo pubblico di riproduzione — elencano response.getOutputStream() e response.flushBuffer() come trigger, spiegando che "la risposta è committed prima che Spring Security possa iniettare i suoi header". Su Spring Security 5.7.11 questo è sbagliato. Entrambi gli endpoint consegnano tutti e sei gli header.

/diag/committed mostra perché la spiegazione non regge. Dopo una scrittura di 12 KB la risposta è davvero committed all'interno del controller, eppure gli header arrivano comunque:

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

OnCommittedResponseWrapper sovrascrive flushBuffer() e le scritture dell'output-stream, quindi emette i suoi header prima di quei commit. Il solo ordinamento dei commit non è il bug; il percorso con Content-Length dichiarato lo è. L'advisory di Spring stesso non avalla mai la storia dell'ordinamento dei commit.

Mantenere questi due endpoint rende il PoC falsificabile: mostra ciò che non si riproduce con la stessa chiarezza di ciò che si riproduce, ed entrambi restano verdi attraverso la patch, che è ciò che rende significativo l'unico endpoint che effettivamente cambia.

La transizione vulnerabile → corretta

Una riga nel pom.xml, nient'altro. Nessuna modifica al sorgente, nessuna modifica alle proprietà, nessun bump di major di Spring Boot:

root@kitploit:~
<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>

Questo ri-fissa spring-security.version da 5.7.11 a 5.7.14-0.cgr.2 (e spring-framework.version da 5.3.31 a 5.3.39-0.cgr.4) tramite le spring-boot-dependencies del parent. Misurato:

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 (by design)
verdetto di exploit.shVULNERABLEPATCHED

Il backport è la correzione upstream

Confrontando i sources jar di spring-security-web, conta un solo file. 5.7.11 → 5.7.14-0.cgr.2 aggiunge gli override setHeader / setIntHeader / addIntHeader a OnCommittedResponseWrapper, ciascuno instrada Content-Length attraverso setContentLength():

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

Prima della correzione solo addHeader faceva questo, quindi setIntHeader("Content-Length", n) lasciava la lunghezza tracciata dal wrapper a 0, onResponseCommitted() non scattava mai, e HeaderWriterFilter non scriveva mai i suoi header prima che Tomcat facesse il commit della risposta.

Lo stesso hunk appare identico confrontando l'upstream 6.5.8 (ultima vulnerabile) con 6.5.9 (prima corretta), quindi questa è la correzione ufficiale backportata, non una reimplementazione. La build Chainguard aggiunge due null-guard che l'upstream 6.5.9 non ha (value != null sull'overload String, e (csq != null) ? csq.length() : 4 in append).

Altre mitigazioni

Spring Security 5.7.x è end-of-life upstream; le correzioni OSS arrivano solo in 6.4.15 / 6.5.9 / 7.0.4+. Se una 5.7.x ricompilata non è un'opzione:

  1. Aggiornare fuori da 5.7.x — una migrazione a Spring Boot 3.x.
  2. Workaround — impostare HeaderWriterFilter.shouldWriteHeadersEagerly = true tramite un ObjectPostProcessor. Secondo l'advisory questo cambia il comportamento: gli header scritti dall'applicazione sovrascrivono poi solo header specifici anziché sopprimere quelli di Spring Security. Questo corregge anche /vuln/cache/account, cosa che il bump di versione non fa.
  3. Supporto commerciale — backport Tanzu Spring Enterprise per 5.7.x/5.8.x.
  4. Difesa in profondità — impostare gli header a livello di reverse proxy / ingress così che un header applicativo eliminato non sia l'unico controllo. Questa è l'unica opzione elencata che copre entrambi gli endpoint.

Nessuna di queste è cablata in questo progetto, quindi il comportamento vulnerabile è il default e lo stato corretto è raggiungibile con il singolo cambio di <version> sopra.

Correggere il Tomcat embedded senza aggiornare Spring Boot

Boot 2.7.18 fissa Tomcat 9.0.83, che grype . segnala con 34 CVE (4 Critical). Tutte sono corrette alla 9.0.118 o inferiore, e la 9.0.118 è la release 9.0.x più recente — quindi una sola proprietà le elimina tutte:

root@kitploit:~
<properties>
  <tomcat.version>9.0.118</tomcat.version>
</properties>

spring-boot-dependencies dichiara ogni artefatto tomcat-embed-* attraverso quella singola proprietà, quindi sovrascriverla ri-fissa core, el e websocket insieme. Verificato:

root@kitploit:~
$ 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

Vincolo: resta sulla linea 9.0.x. Tomcat 10+ ha spostato la Servlet API su jakarta.* mentre Spring Framework 5.3 compila contro javax.servlet, quindi un bump a 10.x/11.x fallisce a runtime con NoClassDefFoundError sui tipi servlet.

Correggere tutto il resto, restando all'interno di ciascuna linea major

Stesso meccanismo applicato al resto delle dipendenze gestite di Boot 2.7.18. Nessun bump di major-version, e nessun aggiornamento di Spring Boot:

ProprietàDefault Boot 2.7.18Fissata quiMotivo del tetto
tomcat.version9.0.839.0.118ultima 9.0.x; 10+ è jakarta.*
spring-framework.version5.3.315.3.39ultima 5.3.x OSS su Central
jackson-bom.version2.13.52.22.2ultima 2.x
log4j2.version2.17.22.26.1ultima 2.x
snakeyaml.version1.301.33ultima 1.x; la correzione per la CVE rimanente è 2.0
logback.version1.2.121.2.13ultima 1.2.x — vedi sotto
spring-security.version5.7.11lasciata invariataè l'oggetto della demo

Questi override interagiscono con il parent corretto, quindi sappi cosa fanno prima di fare la demo. Su 2.7.18-0.cgr.3 il parent fornisce già tomcat.version 9.0.118, logback.version 1.2.13 e snakeyaml.version 1.33 — quelle tre righe diventano duplicati esatti e possono essere eliminate senza cambiare nulla. Le righe jackson-bom.version e log4j2.version fanno ancora lavoro reale: il parent corretto mantiene le 2.13.5 / 2.17.2 stock di Boot, quindi gli override vincono e quelle due dipendenze si risolvono su build upstream normali anziché su quelle costruite da Chainguard. spring-framework.version è commentata di proposito, ed è ciò che lascia passare la 5.3.39-0.cgr.4 del parent.

Progressione di grype .

StatoRisultatiRipartizione
Boot 2.7.18 stock997C / 39H / 38M / 15L
+ bump Tomcat653C / 23H / 29M / 10L
+ tutti i bump in-major433C / 12H / 18M / 10L

Completamente risolte: Tomcat (34), Jackson (7), log4j (1). Complessivamente 99 → 43, High 39 → 12.

Verificato dopo ogni bump: l'app si avvia su Tomcat/9.0.118, tutti e sei gli endpoint restituiscono 200, e la CVE si riproduce byte per byte. Spring Boot è ancora 2.7.18 e Spring Security ancora 5.7.11, quindi CVE-2026-22732 è intatta — che è il punto di questa sezione, e anche il suo onesto limite: correggere tutto ciò che la circonda non fa nulla per la CVE dell'application-framework. Correggere quella richiede il bump del parent, non una proprietà.

Perché Logback si ferma alla 1.2.13

L'ultima 1.x è la 1.6.3 — stessa major, quindi nominalmente in ambito. Non funziona. Logback 1.3+ ha sostituito lo StaticLoggerBinder di SLF4J 1.7 con il provider ServiceLoader di SLF4J 2.x, e il LogbackLoggingSystem di Boot 2.7 chiama StaticLoggerBinder direttamente. Misurato con la 1.5.38:

root@kitploit:~
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)

Uscire da questo richiede SLF4J 2.x (un bump di major) e un logging system di Boot 3.x. La 1.2.13 è il vero tetto, lasciando 6 risultati Logback (2 Medium, 4 Low) non correggibili su questa linea.

I 43 che rimangono

ComponentePerché non può essere corretto in-major
spring-webmvc / -expression / -core / -context (25)5.3.39 è l'ultima 5.3.x OSS; 14 dei 15 risultati webmvc non hanno alcuna correzione, e la 5.3.42 citata da grype è solo commerciale
logback-core (6)richiede SLF4J 2.x, vedi sopra
spring-security-* (8)linea EOL; CVE-2026-22732 è deliberata
spring-boot / -autoconfigure (3)nessuna correzione pubblicata per 2.7.x
snakeyaml (1)CVE-2022-1471 è corretta solo nella 2.0

Due delle tre Critical rimanenti meritano una lettura attenta anziché basata sul punteggio:

  • CVE-2016-1000027 (spring-web, fix 6.0.0) — deserializzazione tramite HttpInvokerServiceExporter. Questa app non usa HTTP Invoker, quindi non è raggiungibile qui.
  • CVE-2024-38821 (spring-security-web, fix 5.7.13) — bypass di autenticazione su risorse statiche in WebFlux. Questa è un'app servlet, quindi anch'essa non raggiungibile. È correggibile in-major (5.7.13/5.7.14 sono su Central) ed è stata lasciata solo per mantenere questa sezione fissata alla 5.7.11. Il parent corretto la risolve come effetto collaterale, poiché 5.7.14-0.cgr.2 è oltre la versione di correzione.
  • CVE-2026-22732 — intenzionale nello stato vulnerabile; risolta dal bump del parent.

Il residuo è strutturale: Spring Framework 5.3.x e Spring Security 5.7.x sono entrambe EOL. Questo, non Tomcat o Jackson, è il vero argomento per una migrazione a Boot 3.x.

Struttura

root@kitploit:~
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

L'autenticazione è permitAll e CSRF è disattivato così curl funziona senza autenticazione — nessuno dei due fa parte di questa CVE.

Fonti

  • spring.io/security/cve-2026-22732 — advisory ufficiale
  • GHSA-mf92-479x-3373
  • NVD CVE-2026-22732
  • Broadcom / Tanzu write-up
  • HeroDevs analysis
  • semgrep/cve-2026-22732-demo — la riproduzione le cui affermazioni su stream/flush non hanno retto qui
  • Red Hat Bugzilla #2449306
Scarica lo strumento