
OpenRewrite-Rezept, das die Unterdrückung von Spring-Security-Headern erkennt und behebt (CVE-2026-22732), indem es die missbräuchliche Verwendung des Content-Length-Headers identifiziert und eine Konfiguration zum sofortigen Schreiben von Headern generiert.
OpenRewrite-Rezept, das Code erkennt, der für CVE-2026-22732 anfällig ist – einen Spring-Security-Fehler, bei dem das Setzen von Content-Length über eine von drei Response-Methoden Spring Securitys OnCommittedResponseWrapper umgeht. Da der Wrapper den Header nie sieht, wird onResponseCommitted() nie ausgelöst, und die lazy hinzugefügten Security-Header (X-Frame-Options, X-Content-Type-Options, Cache-Control usw.) werden stillschweigend verworfen.
Die tatsächlichen Auslöser, bestätigt gegen verwundbares Spring Security 6.4.12 mit Spring Boot 3.4.3 / eingebettetem Tomcat:
Servlet Content-Length über die Wrapper-umgehenden Overloads
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
Diese drei Overloads sind in OnCommittedResponseWrapper nicht überschrieben. Nachfolgende Body-Writes vervollständigen die deklarierte Länge und der Container committet, ohne den lazy Header-Writer auszulösen.
WebFlux Content-Length über HttpHeaders
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
WebFlux unbedingte Response-Commits
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
Das Rezept ist an das Vorhandensein von Spring Security gekoppelt – es erzeugt nichts in Dateien, die keinen org.springframework.security.*-Typ referenzieren – sowie an betroffene Spring-Security-Versionsbereiche. Laut der Spring-Advisory, veröffentlicht am 19.03.2026, sind die betroffenen Bereiche und Fix-Versionen:
| Serie | Betroffen | Behoben |
|---|---|---|
| 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) |
Projekte, die eine Spring-Security-Version auf oder über dem Fix ihrer Serie auflösen (oder auf einer zukünftigen Serie nach 7.0 / 6.5), gelten als nicht betroffen und erhalten keine Marker pro Sink oder pro Datei. Projekte, deren Version nicht aufgelöst werden kann, fallen auf die übliche musterbasierte Erkennung zurück, sodass ein Scanner eher einen Befund meldet, den er nicht widerlegen kann. Die Datentabelle SpringSecurityVersionByProject zeichnet weiterhin die aufgelöste Version auf und kennzeichnet jedes Projekt als betroffen oder nicht, sodass du nachvollziehen kannst, was gefiltert wurde.
Diese sehen gefährlich aus, werden aber vom Wrapper verfolgt, sodass Security-Header geschrieben werden, bevor die Response committet:
| Code | Warum es sicher ist |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | Überschrieben – der Wrapper zeichnet die deklarierte Länge auf und löst onResponseCommitted() aus, wenn der Body abgeschlossen ist. |
response.flushBuffer() | Überschrieben – ruft doOnResponseCommitted() vor super.flushBuffer() auf. |
response.getOutputStream().write(..) / flush() / close() | Gibt SaveContextServletOutputStream zurück; jeder write/flush/close löst doOnResponseCommitted() vor der Delegierung aus. |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | Gibt SaveContextPrintWriter zurück; gleiches Muster. |
response.addHeader("Content-Length", v) | Im Wrapper speziell behandelt – über setContentLength(long) geleitet. |
Der /vuln/flush-Endpunkt der Semgrep-Demo behauptet, flushBuffer() sei der Auslöser, aber auf einem verwundbaren Spring Security 6.4.12 gibt die Response tatsächlich alle sechs Security-Header zurück. Die echten Auslöser in der Demo sind die setIntHeader("Content-Length", ...)-Aufrufe in /vuln/stream und /vuln/content-length.
Führe dieses aus:
| Rezept | Zweck |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | Führt jede Erkennung aus und erzeugt den Versionsbericht |
Der Aggregator oben besteht aus zwei kleineren Rezepten. Du kannst sie einzeln aufrufen, wenn du nur eine Erkennung möchtest.
| Rezept | Zweck |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | Taint-Flow für das "Content-Length"-Literal, das setHeader / setIntHeader / addIntHeader (Servlet) oder HttpHeaders.set / add (WebFlux) erreicht |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | WebFlux unbedingte Commits: writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength |
Führe dieses aus:
| Rezept | Zweck |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | Wählt die günstigste Abhilfe, die jedes Projekt tatsächlich umsetzen kann |
Es führt zwei Schritte in Reihenfolge aus.
1. Upgrade auf den Fix der eigenen Serie des Projekts. Spring Security hat den Fix als 6.5.9
und 7.0.4 auf Maven Central veröffentlicht. Jedes Upgrade ist an eine FindAffectedSpringSecuritySeries-
Vorbedingung gekoppelt, weil UpgradeDependencyVersion nur prüft, dass sein Ziel neuer ist – angewiesen
auf 7.0.4 würde es ein 5.8-Projekt bereitwillig über zwei Major-Versionen ziehen.
2. Füge eine eager Header-schreibende Konfiguration zu dem hinzu, was Schritt 1 nicht beheben konnte. Dies erzeugt
eine @Configuration-Klasse pro Projekt:
@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;
}
};
}
Das vorab Schreiben der Header macht es irrelevant, ob der Wrapper den Commit jemals beobachtet, sodass
dies jeden Sink im Projekt auf einmal schließt – einschließlich derer, die die Taint-Analyse nicht erreichen kann,
wie den Map-Flow unter „Bekannte Einschränkungen". Der BeanPostProcessor sieht Filter, die von der
HttpSecurity-DSL erstellt wurden, weil AutowireBeanFactoryObjectPostProcessor sie über die Bean-Factory
initialisiert. Zwei unabhängig veröffentlichte Fixes für diese CVE verwenden exakt diese Form
(hmcts/idam-web-public, und
armory-ios Spinnaker-Forks über den äquivalenten ObjectPostProcessor).
Schritt 2 deckt die Projekte ab, denen Schritt 1 nicht helfen kann:
| Situation | Warum das Upgrade nicht funktioniert |
|---|---|
| 5.7, 5.8, 6.3, 6.4 | Fix wird nur an Spring-Enterprise-Abonnenten ausgeliefert – nicht auf Maven Central |
| 6.0 - 6.2 | Für diese Serien wurde nie ein Fix veröffentlicht |
| Version durch ein importiertes BOM verwaltet | Es ist lokal nichts deklariert, das das Upgrade bearbeiten könnte |
Die letzte Zeile ist kein Randfall. nla/bamboo löst 7.0.3 – eine Serie mit Open-Source-
Fix – vollständig aus dem Spring-Boot-BOM auf, sodass eine reine Versionsprüfung es unter beiden Schritten
überspringen und verwundbar lassen würde. AddEagerHeaderWriterConfiguration weicht daher nur dann auf das Upgrade
aus, wenn das Projekt sowohl einen verfügbaren Open-Source-Fix hat als auch eine eigene Version deklariert.