
Ricetta OpenRewrite che rileva e corregge la soppressione degli header di Spring Security (CVE-2026-22732) identificando l'uso improprio dell'header Content-Length e generando una configurazione di scrittura anticipata degli header.
Ricetta OpenRewrite che rileva codice vulnerabile a CVE-2026-22732, un difetto di Spring Security in cui l'impostazione di Content-Length tramite uno dei tre metodi di risposta bypassa OnCommittedResponseWrapper di Spring Security. Poiché il wrapper non vede mai l'header, onResponseCommitted() non viene mai attivato e gli header di sicurezza aggiunti in modo lazy (X-Frame-Options, X-Content-Type-Options, Cache-Control, ecc.) vengono silenziosamente eliminati.
I trigger effettivi, confermati contro Spring Security 6.4.12 vulnerabile con Spring Boot 3.4.3 / Tomcat embedded:
Content-Length servlet tramite gli overload che bypassano il wrapper
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
Questi tre overload non sono sovrascritti in OnCommittedResponseWrapper. Le successive scritture del body completano la lunghezza dichiarata e il container committa senza attivare il writer di header lazy.
Content-Length WebFlux tramite HttpHeaders
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
Commit di risposta WebFlux incondizionati
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
La ricetta è condizionata alla presenza di Spring Security — non emette nulla nei file che non referenziano alcun tipo org.springframework.security.* — e alle versioni di Spring Security interessate. Secondo l'avviso Spring pubblicato il 2026-03-19, gli intervalli interessati e le versioni corrette sono:
| Serie | Interessate | Corretta |
|---|---|---|
| 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) |
I progetti che risolvono una versione di Spring Security uguale o superiore alla correzione nella propria serie (o in qualsiasi serie futura oltre 7.0 / 6.5) sono trattati come non interessati e non ricevono marcatori per-sink o per-file. I progetti in cui la versione non può essere risolta ricadono nella consueta rilevazione basata su pattern, così uno scanner tende a segnalare un risultato che non può smentire. La tabella dati SpringSecurityVersionByProject registra comunque la versione risolta e contrassegna ogni progetto come interessato o meno, così puoi verificare cosa è stato filtrato.
Questi sembrano pericolosi ma sono tracciati dal wrapper, quindi gli header di sicurezza vengono scritti prima del commit della risposta:
| Codice | Perché è sicuro |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | Sovrascritti — il wrapper registra la lunghezza dichiarata e attiva onResponseCommitted() quando il body è completo. |
response.flushBuffer() | Sovrascritto — chiama doOnResponseCommitted() prima di super.flushBuffer(). |
response.getOutputStream().write(..) / flush() / close() | Restituisce SaveContextServletOutputStream; ogni write/flush/close attiva doOnResponseCommitted() prima di delegare. |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | Restituisce SaveContextPrintWriter; stesso pattern. |
response.addHeader("Content-Length", v) | Caso speciale nel wrapper — instradato tramite setContentLength(long). |
L'endpoint /vuln/flush della demo Semgrep afferma che flushBuffer() è il trigger, ma su Spring Security 6.4.12 vulnerabile la risposta restituisce effettivamente tutti e sei gli header di sicurezza. I veri trigger nella demo sono le chiamate setIntHeader("Content-Length", ...) in /vuln/stream e /vuln/content-length.
Esegui questa:
| Ricetta | Scopo |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | Esegue ogni rilevazione ed emette la tabella del report delle versioni |
L'aggregatore sopra è composto da due ricette più piccole. Puoi invocarle singolarmente se vuoi una sola rilevazione.
| Ricetta | Scopo |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | Flusso di taint per il letterale "Content-Length" che raggiunge setHeader / setIntHeader / addIntHeader (servlet) o HttpHeaders.set / add (WebFlux) |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | Commit WebFlux incondizionati: writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength |
Esegui questa:
| Ricetta | Scopo |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | Sceglie la rimedazione più economica che ogni progetto può effettivamente adottare |
Esegue due passaggi in ordine.
1. Aggiornamento alla correzione sulla serie del progetto. Spring Security ha pubblicato la correzione come 6.5.9
e 7.0.4 su Maven Central. Ogni aggiornamento è condizionato da una precondizione FindAffectedSpringSecuritySeries,
perché UpgradeDependencyVersion controlla solo che il suo target sia più recente — se gli si dicesse di
andare a 7.0.4 trascinerebbe volentieri un progetto 5.8 attraverso due versioni major.
2. Aggiunta di una configurazione eager per la scrittura degli header a qualunque cosa il passaggio 1 non abbia potuto correggere. Questo genera
una classe @Configuration per progetto:
@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;
}
};
}
Scrivere gli header in anticipo rende irrilevante se il wrapper osservi mai il commit, quindi
questo chiude tutti i sink del progetto in una volta — inclusi quelli che l'analisi del taint non può raggiungere,
come il flusso Map in "Limitazioni note". Il BeanPostProcessor vede i filter costruiti dal
DSL HttpSecurity perché AutowireBeanFactoryObjectPostProcessor li inizializza tramite la
bean factory. Due correzioni pubblicate indipendentemente per questa CVE usano esattamente questa forma
(hmcts/idam-web-public, e
i fork Spinnaker di armory-io tramite l'equivalente ObjectPostProcessor).
Il passaggio 2 è ciò che copre i progetti che il passaggio 1 non può aiutare:
| Situazione | Perché l'aggiornamento non funziona |
|---|---|
| 5.7, 5.8, 6.3, 6.4 | La correzione è disponibile solo per gli abbonati Spring Enterprise — non su Maven Central |
| 6.0 - 6.2 | Nessuna correzione è mai stata rilasciata su quelle serie |
| Versione gestita da un BOM importato | Non è dichiarato nulla localmente che l'aggiornamento possa modificare |
L'ultima riga non è un caso limite. nla/bamboo risolve 7.0.3 — una serie con una correzione
open-source — interamente dal BOM Spring Boot, quindi un semplice controllo della versione lo salterebbe in entrambi i passaggi
lasciandolo vulnerabile. AddEagerHeaderWriterConfiguration quindi rimanda all'aggiornamento solo
quando il progetto ha sia una correzione open-source disponibile sia dichiara una versione propria.