
Recette OpenRewrite qui détecte et corrige la suppression d'en-têtes Spring Security (CVE-2026-22732) en identifiant l'utilisation abusive de l'en-tête Content-Length et en générant une configuration d'écriture anticipée des en-têtes.
Recette OpenRewrite qui détecte le code susceptible d'être vulnérable à CVE-2026-22732, un défaut de Spring Security où la définition de Content-Length via l'une des trois méthodes de réponse contourne OnCommittedResponseWrapper de Spring Security. Comme le wrapper ne voit jamais l'en-tête, onResponseCommitted() ne se déclenche jamais, et les en-têtes de sécurité ajoutés paresseusement (X-Frame-Options, X-Content-Type-Options, Cache-Control, etc.) sont silencieusement supprimés.
Les déclencheurs réels, confirmés contre Spring Security 6.4.12 vulnérable avec Spring Boot 3.4.3 / Tomcat embarqué :
Content-Length servlet via les surcharges contournant le wrapper
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
Ces trois surcharges ne sont pas redéfinies dans OnCommittedResponseWrapper. Les écritures ultérieures du corps complètent la longueur déclarée et le conteneur valide la réponse sans déclencher l'écriture paresseuse des en-têtes.
Content-Length WebFlux via HttpHeaders
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
Validations de réponse WebFlux inconditionnelles
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
La recette est conditionnée à la présence de Spring Security — elle n'émet rien dans les fichiers qui ne référencent aucun type org.springframework.security.* — et aux plages de versions Spring Security affectées. Selon l'avis Spring publié le 2026-03-19, les plages affectées et les versions corrigées sont :
| Série | Affectée | Corrigée |
|---|---|---|
| 5.7.x | 5.7.0 – 5.7.21 | 5.7.22 (Enterprise) |
| 5.8.x | 5.8.0 – 5.8.23 | 5.8.24 (Enterprise) |
| 6.3.x | 6.3.0 – 6.3.14 | 6.3.15 (Enterprise) |
| 6.4.x | 6.4.0 – 6.4.14 | 6.4.15 (Enterprise) |
| 6.5.x | 6.5.0 – 6.5.8 | 6.5.9 (OSS) |
| 7.0.x | 7.0.0 – 7.0.3 | 7.0.4 (OSS) |
Les projets résolvant une version de Spring Security égale ou supérieure au correctif de leur série (ou sur toute série future au-delà de 7.0 / 6.5) sont considérés comme non affectés et ne reçoivent aucun marqueur par sink ou par fichier. Les projets dont la version ne peut pas être résolue retombent sur la détection habituelle basée sur les motifs, afin qu'un scanner privilégie le signalement d'un résultat qu'il ne peut pas réfuter. La table de données SpringSecurityVersionByProject enregistre toujours la version résolue et marque chaque projet comme affecté ou non, ce qui permet d'auditer ce qui a été filtré.
Ces éléments semblent dangereux mais sont suivis par le wrapper, donc les en-têtes de sécurité sont écrits avant la validation de la réponse :
| Code | Pourquoi c'est sûr |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | Redéfini — le wrapper enregistre la longueur déclarée et déclenche onResponseCommitted() lorsque le corps est terminé. |
response.flushBuffer() | Redéfini — appelle doOnResponseCommitted() avant super.flushBuffer(). |
response.getOutputStream().write(..) / flush() / close() | Renvoie SaveContextServletOutputStream ; chaque write/flush/close déclenche doOnResponseCommitted() avant de déléguer. |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | Renvoie SaveContextPrintWriter ; même schéma. |
response.addHeader("Content-Length", v) | Cas particulier dans le wrapper — routé via setContentLength(long). |
Le point de terminaison /vuln/flush de la démo Semgrep prétend que flushBuffer() est le déclencheur, mais sur un Spring Security 6.4.12 vulnérable, la réponse renvoie en réalité les six en-têtes de sécurité. Les véritables déclencheurs de la démo sont les appels setIntHeader("Content-Length", ...) dans /vuln/stream et /vuln/content-length.
Exécutez celle-ci :
| Recette | Objectif |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | Exécute toutes les détections et émet le rapport de versions |
L'agrégateur ci-dessus est composé de deux recettes plus petites. Vous pouvez les invoquer individuellement si vous ne souhaitez qu'une seule détection.
| Recette | Objectif |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | Flux de taint pour le littéral "Content-Length" atteignant setHeader / setIntHeader / addIntHeader (servlet) ou HttpHeaders.set / add (WebFlux) |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | Validations WebFlux inconditionnelles : writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength |
Exécutez celle-ci :
| Recette | Objectif |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | Choisit la remédiation la moins coûteuse que chaque projet peut réellement appliquer |
Elle exécute deux étapes dans l'ordre.
1. Mise à niveau vers le correctif de la propre série du projet. Spring Security a publié le correctif en versions 6.5.9 et 7.0.4 sur Maven Central. Chaque mise à niveau est conditionnée par une précondition FindAffectedSpringSecuritySeries, car UpgradeDependencyVersion vérifie uniquement que sa cible est plus récente — si on lui demandait d'aller vers 7.0.4, elle ferait passer un projet 5.8 à travers deux versions majeures sans sourciller.
2. Ajout d'une configuration d'écriture anticipée des en-têtes pour tout ce que l'étape 1 n'a pas pu corriger. Cela génère une classe @Configuration par projet :
@Bean
public static BeanPostProcessor eagerHeaderWriterFilterBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HeaderWriterFilter) {
((HeaderWriterFilter) bean).setShouldWriteHeadersEagerly(true);
}
return bean;
}
};
}
Écrire les en-têtes en amont rend sans importance le fait que le wrapper observe ou non la validation, ce qui ferme tous les sinks du projet d'un coup — y compris ceux que l'analyse de taint ne peut pas atteindre, comme le flux Map sous « Limitations connues ». Le BeanPostProcessor voit les filtres construits par le DSL HttpSecurity car AutowireBeanFactoryObjectPostProcessor les initialise via la fabrique de beans. Deux correctifs publiés indépendamment pour cette CVE utilisent exactement cette forme (hmcts/idam-web-public, et les forks Spinnaker d'armory-io via l'ObjectPostProcessor équivalent).
L'étape 2 couvre les projets que l'étape 1 ne peut pas aider :
| Situation | Pourquoi la mise à niveau ne fonctionne pas |
|---|---|
| 5.7, 5.8, 6.3, 6.4 | Le correctif est réservé aux abonnés Spring Enterprise — pas sur Maven Central |
| 6.0 - 6.2 | Aucun correctif n'a jamais été publié sur ces séries |
| Version gérée par un BOM importé | Rien n'est déclaré localement que la mise à niveau puisse modifier |