Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
cve-2026-22732-poc — Preuve de concept démontrant CVE-2026-22732, une faille de Spring Security où setIntHeader("Content-Length") supprime tous les en-têtes de sécurité, avec des builds vulnérables et corrigés. | Kitploit
Outils/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Outils DéfensifsAnalyse des VulnérabilitésExploitationVirtualisation de SécuritéSécurité WebApprentissage et Éducation
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

Preuve de concept démontrant CVE-2026-22732, une faille de Spring Security où setIntHeader("Content-Length") supprime tous les en-têtes de sécurité, avec des builds vulnérables et corrigés.

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Voir le dépôt
il y a 2h 52mPas encore vérifié
Partager

CVE-2026-22732 — Preuve de concept

Spring Security supprime silencieusement les en-têtes de sécurité des réponses HTTP. Démo / usage éducatif uniquement ; ne l'exécutez contre rien d'autre que cette application locale.

CVECVE-2026-22732 (CWE-425), publiée le 2026-03-19
CVSS 3.19.1 CRITIQUE — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Dépendance directespring-boot-starter-web + spring-boot-starter-security 2.7.18
Composant vulnérablespring-security-web / -config / -core 5.7.11 — transitif uniquement, jamais nommé dans pom.xml
Composant corrigéspring-security-web 5.7.14-0.cgr.2, atteint par un seul changement de <version> — voir la transition
Vérifié surTomcat 9.0.118, JDK 17.0.18, macOS arm64

Plages affectées : 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 épingle Spring Security 5.7.11, en plein dans la première plage :

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

L'exécuter

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

run.sh épingle JAVA_HOME car Spring Boot 2.7.x ne peut pas s'exécuter sur le JDK 25 que mvn résout par défaut sur cette machine. Remplacez avec JAVA_HOME_17=/path/to/jdk17.

Il compile aussi avec -s settings-chainguard.xml par défaut, car le parent corrigé n'est pas sur Maven Central. Définissez MAVEN_SETTINGS=/path/to/your/settings.xml pour pointer ailleurs, ou MAVEN_SETTINGS= pour compiler uniquement depuis Central — ce qui ne fonctionne que pour le 2.7.18 d'origine.

exploit.sh lit les versions réelles de spring-security-web et spring-boot depuis target/*.jar, donc sa bannière rapporte toujours ce qui s'exécute réellement plutôt qu'une chaîne codée en dur.

La configuration

SecurityConfig n'applique aucune personnalisation d'en-tête — les valeurs par défaut de Spring Security sont en vigueur, ce qui est exactement ce sur quoi une application soucieuse de la sécurité s'appuie. Chaque endpoint renvoie le même corps sensible :

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

La seule chose qui varie est la façon dont le contrôleur écrit la réponse.

Résultats mesurés

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

Seul /vuln/content-length/account change d'état lorsque la CVE est corrigée, c'est donc le seul endpoint dont exploit.sh tire son verdict. Les autres sont des contrôles.

La CVE — setIntHeader("Content-Length", n) → contournement total

Trois lignes de code de contrôleur d'apparence ordinaire suppriment tous les en-têtes que Spring Security promettait :

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

Pas de X-Frame-Options, pas de X-Content-Type-Options, pas de Cache-Control, pas de Pragma, pas d'Expires, pas de X-XSS-Protection. Comparez avec /safe/account, qui porte les six. La réponse est encadrable par n'importe quelle origine, sensible au reniflage MIME et cachable — tout en servant un numéro de carte.

Le cas par conception — en-tête de cache défini par l'application → suppression du cache

Corrigé : une version antérieure de ce README appelait cela « exploit 2 » et affirmait qu'il s'agissait de la condition documentée par l'avis. Cela ne fait pas partie de CVE-2026-22732 et aucune mise à niveau ne le corrige. CacheControlHeadersWriter est identique octet pour octet en 5.7.11, 5.7.14-0.cgr.2, 6.5.8 (dernière vulnérable) et 6.5.9 (première corrigée) — vérifié en comparant les jars de sources. Sa Javadoc énonce le comportement sans détour : « Insère des en-têtes pour empêcher la mise en cache si aucun en-tête de contrôle du cache n'a été spécifié. »

Cela vaut quand même la peine d'être démontré, car la fuite est réelle et le risque résiduel survit au correctif. CacheControlHeadersWriter abandonne si Cache-Control, Expires ou Pragma est déjà présent, donc définir l'un des trois supprime toutes les directives no-store de Spring Security. Une seule ligne bien intentionnée suffit :

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 compte comme « présent » dans le tableau ci-dessus, mais avec une valeur favorable à l'attaquant choisie par l'application — l'Expires: 0 de Spring Security a été remplacé, pas simplement supprimé. Les données du porteur de carte sont désormais stockables par chaque navigateur et proxy partagé sur le chemin jusqu'en 2099.

Sur la version corrigée, /vuln/content-length/account bascule vers NOT cacheable, tandis que /vuln/cache/account reste exactement comme ci-dessus. Seul le code applicatif ou un reverse proxy le corrige — ce qui est la chose utile à dire à voix haute dans une démo : mettre à niveau la bibliothèque ferme la CVE et laisse ceci intact.

Deux résultats négatifs, conservés à dessein

Plusieurs articles largement diffusés — y compris un dépôt de reproduction public — listent response.getOutputStream() et response.flushBuffer() comme déclencheurs, expliquant que « la réponse est validée avant que Spring Security puisse injecter ses en-têtes ». Sur Spring Security 5.7.11, c'est faux. Les deux endpoints délivrent les six en-têtes.

/diag/committed montre pourquoi l'explication ne tient pas. Après une écriture de 12 Ko, la réponse est réellement validée à l'intérieur du contrôleur, et pourtant les en-têtes arrivent toujours :

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

OnCommittedResponseWrapper redéfinit flushBuffer() et les écritures du flux de sortie, donc il obtient ses en-têtes avant ces validations. L'ordre de validation seul n'est pas le bug ; le chemin avec Content-Length déclaré l'est. L'avis Spring lui-même ne cautionne jamais l'histoire de l'ordre de validation.

Conserver ces deux endpoints rend la PoC falsifiable : elle montre ce qui ne se reproduit pas aussi clairement que ce qui se reproduit, et les deux restent verts à travers le correctif, ce qui rend le seul endpoint qui bascule significatif.

La transition vulnérable → corrigée

Une ligne dans pom.xml, rien d'autre. Aucun changement de source, aucun changement de propriété, aucune montée de version majeure de 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>

Cela ré-épingle spring-security.version de 5.7.11 à 5.7.14-0.cgr.2 (et spring-framework.version de 5.3.31 à 5.3.39-0.cgr.4) via le spring-boot-dependencies du parent. Mesuré :

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)
exploit.sh verdictVULNERABLEPATCHED

Le backport est le correctif amont

En comparant les jars de sources de spring-security-web, un seul fichier compte. 5.7.11 → 5.7.14-0.cgr.2 ajoute les redéfinitions setHeader / setIntHeader / addIntHeader à OnCommittedResponseWrapper, chacune routant Content-Length via setContentLength() :

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

Avant le correctif, seul addHeader faisait cela, donc setIntHeader("Content-Length", n) laissait la longueur suivie par le wrapper à 0, onResponseCommitted() ne se déclenchait jamais, et HeaderWriterFilter n'écrivait jamais ses en-têtes avant que Tomcat ne valide la réponse.

Le même bloc apparaît mot pour mot en comparant l'amont 6.5.8 (dernière vulnérable) avec 6.5.9 (première corrigée), donc il s'agit du correctif officiel rétroporté, pas d'une réimplémentation. La version Chainguard ajoute deux gardes de nullité que l'amont 6.5.9 n'a pas (value != null sur la surcharge String, et (csq != null) ? csq.length() : 4 dans append).

Autres atténuations

Spring Security 5.7.x est en fin de vie en amont ; les correctifs OSS n'arrivent que dans 6.4.15 / 6.5.9 / 7.0.4+. Si une version 5.7.x reconstruite n'est pas une option :

  1. Quitter la 5.7.x — une migration vers Spring Boot 3.x.
  2. Contournement — définir HeaderWriterFilter.shouldWriteHeadersEagerly = true via un ObjectPostProcessor. Selon l'avis, cela change le comportement : les en-têtes écrits par l'application ne remplacent alors que des en-têtes spécifiques plutôt que de supprimer ceux de Spring Security. Celui-ci corrige aussi /vuln/cache/account, ce que la montée de version ne fait pas.
  3. Support commercial — rétroportages Tanzu Spring Enterprise pour 5.7.x/5.8.x.
  4. Défense en profondeur — définir les en-têtes au niveau du reverse proxy / ingress afin qu'un en-tête applicatif supprimé ne soit pas le seul contrôle. C'est la seule option listée qui couvre les deux endpoints.

Aucune de ces options n'est câblée dans ce projet, donc le comportement vulnérable est la valeur par défaut et l'état corrigé est atteignable par le seul changement de <version> ci-dessus.

Corriger le Tomcat embarqué sans mettre à niveau Spring Boot

Boot 2.7.18 épingle Tomcat 9.0.83, que grype . signale avec 34 CVE (4 Critiques). Toutes sont corrigées en 9.0.118 ou en dessous, et 9.0.118 est la dernière version 9.0.x — donc une seule propriété règle l'ensemble :

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

spring-boot-dependencies déclare chaque artefact tomcat-embed-* via cette seule propriété, donc la remplacer ré-épingle core, el et websocket ensemble. Vérifié :

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

Contrainte : restez sur la ligne 9.0.x. Tomcat 10+ a déplacé l'API Servlet vers jakarta.* tandis que Spring Framework 5.3 compile contre javax.servlet, donc une montée en 10.x/11.x échoue à l'exécution avec NoClassDefFoundError sur les types servlet.

Corriger tout le reste, toujours dans chaque ligne majeure

Même mécanisme appliqué au reste des dépendances gérées de Boot 2.7.18. Aucune montée de version majeure, et aucune mise à niveau de Spring Boot :

PropriétéDéfaut Boot 2.7.18Épinglé iciRaison du plafond
tomcat.version9.0.839.0.118dernière 9.0.x ; 10+ est jakarta.*
spring-framework.version5.3.315.3.39dernière 5.3.x OSS sur Central
jackson-bom.version2.13.52.22.2dernière 2.x
log4j2.version2.17.22.26.1dernière 2.x
snakeyaml.version1.301.33dernière 1.x ; le correctif pour la CVE restante est 2.0
logback.version1.2.121.2.13dernière 1.2.x — voir ci-dessous
spring-security.version5.7.11laissée telle quellec'est le sujet de la démo

Ces remplacements interagissent avec le parent corrigé, donc sachez ce qu'ils font avant de faire la démo. Sur 2.7.18-0.cgr.3, le parent fournit déjà tomcat.version 9.0.118, logback.version 1.2.13 et snakeyaml.version 1.33 — ces trois lignes deviennent des doublons exacts et peuvent être supprimées sans rien changer. Les lignes jackson-bom.version et log4j2.version font encore un vrai travail : le parent corrigé conserve les 2.13.5 / 2.17.2 d'origine de Boot, donc les remplacements l'emportent et ces deux dépendances se résolvent en versions amont pures plutôt qu'en versions construites par Chainguard. spring-framework.version est commentée à dessein, ce qui laisse passer le 5.3.39-0.cgr.4 du parent.

Progression de grype .

ÉtatConstatsRépartition
Boot 2.7.18 d'origine997C / 39H / 38M / 15L
+ montée Tomcat653C / 23H / 29M / 10L
+ toutes les montées dans la majeure433C / 12H / 18M / 10L

Entièrement nettoyés : Tomcat (34), Jackson (7), log4j (1). Globalement 99 → 43, Highs 39 → 12.

Vérifié après chaque montée : l'application démarre sur Tomcat/9.0.118, les six endpoints renvoient 200, et la CVE se reproduit octet pour octet. Spring Boot est toujours 2.7.18 et Spring Security toujours 5.7.11, donc CVE-2026-22732 est intacte — ce qui est le but de cette section, et aussi sa limite honnête : corriger tout ce qui l'entoure ne fait rien pour la CVE du framework applicatif. Corriger celle-là nécessite la montée du parent, pas une propriété.

Pourquoi Logback s'arrête à 1.2.13

La dernière 1.x est 1.6.3 — même majeure, donc nominalement dans le périmètre. Cela ne fonctionne pas. Logback 1.3+ a remplacé le StaticLoggerBinder de SLF4J 1.7 par le fournisseur ServiceLoader de SLF4J 2.x, et le LogbackLoggingSystem de Boot 2.7 appelle StaticLoggerBinder directement. Mesuré avec 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)

S'échapper de cela nécessite SLF4J 2.x (une montée majeure) et un système de journalisation Boot 3.x. 1.2.13 est le vrai plafond, laissant 6 constats Logback (2 Moyens, 4 Faibles) non corrigeables sur cette ligne.

Les 43 qui restent

ComposantPourquoi il ne peut pas être corrigé dans la majeure
spring-webmvc / -expression / -core / -context (25)5.3.39 est la dernière 5.3.x OSS ; 14 des 15 constats webmvc n'ont aucun correctif du tout, et la 5.3.42 que cite grype est commerciale uniquement
logback-core (6)nécessite SLF4J 2.x, voir ci-dessus
spring-security-* (8)ligne en fin de vie ; CVE-2026-22732 est délibérée
spring-boot / -autoconfigure (3)aucun correctif publié pour 2.7.x
snakeyaml (1)CVE-2022-1471 n'est corrigée qu'en 2.0

Deux des trois Critiques restants méritent d'être lus correctement plutôt que par score :

  • CVE-2016-1000027 (spring-web, correctif 6.0.0) — désérialisation via HttpInvokerServiceExporter. Cette application n'utilise pas HTTP Invoker, donc ce n'est pas atteignable ici.
  • CVE-2024-38821 (spring-security-web, correctif 5.7.13) — contournement d'authentification des ressources statiques dans WebFlux. C'est une application servlet, donc également non atteignable. Elle est corrigeable dans la majeure (5.7.13/5.7.14 sont sur Central) et a été laissée uniquement pour garder cette section épinglée à 5.7.11. Le parent corrigé la nettoie comme effet secondaire, puisque 5.7.14-0.cgr.2 dépasse la version du correctif.
  • CVE-2026-22732 — intentionnelle dans l'état vulnérable ; nettoyée par la montée du parent.

Le résidu est structurel : Spring Framework 5.3.x et Spring Security 5.7.x sont tous deux en fin de vie. C'est cela, pas Tomcat ou Jackson, le véritable argument pour une migration vers Boot 3.x.

Arborescence

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'authentification est permitAll et CSRF est désactivé pour que curl fonctionne sans authentification — ni l'un ni l'autre ne fait partie de cette CVE.

Sources

  • spring.io/security/cve-2026-22732 — avis officiel
  • GHSA-mf92-479x-3373
  • NVD CVE-2026-22732
  • Article Broadcom / Tanzu
  • Analyse HeroDevs
  • semgrep/cve-2026-22732-demo — la reproduction dont les affirmations stream/flush ne tenaient pas ici
  • Red Hat Bugzilla #2449306
Télécharger l’outil