CVE-2026-22732 の影響を受けるコードを検出するOpenRewriteレシピ。これはSpring Securityの欠陥で、3つのレスポンスメソッドのいずれかを通じて Content-Length を設定すると、Spring Securityの OnCommittedResponseWrapper がバイパスされます。ラッパーがヘッダーを認識しないため onResponseCommitted() が発火せず、遅延追加されるセキュリティヘッダー(X-Frame-Options、X-Content-Type-Options、Cache-Control など)が静かに破棄されます。
実際のトリガーは、脆弱なSpring Security 6.4.12(Spring Boot 3.4.3 / 組み込みTomcat)で確認済みです:
ラッパーをバイパスするオーバーロードによるServlet Content-Length
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
これら3つのオーバーロードは OnCommittedResponseWrapper でオーバーライドされていません。後続のボディ書き込みで宣言された長さが完了し、コンテナは遅延ヘッダーライターを発火させずにコミットします。
HttpHeaders によるWebFlux Content-Length
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
WebFluxの無条件レスポンスコミット
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
このレシピはSpring Securityの存在に依存しており、org.springframework.security.* 型を一切参照しないファイルでは何も出力しません。また、影響を受けるSpring Securityのバージョン範囲にも依存します。Springアドバイザリ(2026-03-19公開)によると、影響を受ける範囲と修正バージョンは以下の通りです:
| シリーズ | 影響を受けるバージョン | 修正バージョン |
|---|---|---|
| 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) |
自身のシリーズの修正バージョン以上(または7.0 / 6.5以降の将来のシリーズ)のSpring Securityバージョンを解決するプロジェクトは影響を受けないものとして扱われ、シンク単位・ファイル単位のマーカーは生成されません。バージョンを解決できないプロジェクトは通常のパターンベースの検出にフォールバックするため、スキャナーは反証できない検出結果を報告する方向に倒れます。SpringSecurityVersionByProject データテーブルは解決されたバージョンを記録し、各プロジェクトが影響を受けるかどうかをフラグ付けするため、フィルタリングされた内容を監査できます。
これらは危険に見えますが、ラッパーで追跡されるため、レスポンスがコミットされる前にセキュリティヘッダーが書き込まれます:
| コード | 安全な理由 |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | オーバーライド済み — ラッパーが宣言された長さを記録し、ボディが完了すると onResponseCommitted() を発火します。 |
response.flushBuffer() | オーバーライド済み — super.flushBuffer() の前に doOnResponseCommitted() を呼び出します。 |
response.getOutputStream().write(..) / flush() / close() | SaveContextServletOutputStream を返します。すべてのwrite/flush/closeは委譲前に doOnResponseCommitted() を発火します。 |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | SaveContextPrintWriter を返します。同じパターンです。 |
response.addHeader("Content-Length", v) | ラッパーで特別処理 — setContentLength(long) 経由でルーティングされます。 |
Semgrepデモの /vuln/flush エンドポイントは flushBuffer() がトリガーだと主張していますが、脆弱なSpring Security 6.4.12では実際には6つのセキュリティヘッダーすべてがレスポンスで返されます。デモの実際のトリガーは /vuln/stream と /vuln/content-length にある setIntHeader("Content-Length", ...) 呼び出しです。
こちらを実行してください:
| レシピ | 目的 |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | すべての検出を実行し、バージョンレポートテーブルを出力します |
上記のアグリゲーターは2つの小さなレシピで構成されています。1つの検出のみが必要な場合は個別に呼び出すことができます。
| レシピ | 目的 |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | "Content-Length" リテラルが setHeader / setIntHeader / addIntHeader(サーブレット)または HttpHeaders.set / add(WebFlux)に到達する汚染フロー |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | WebFluxの無条件コミット: writeWith、writeAndFlushWith、setComplete、HttpHeaders.setContentLength |
こちらを実行してください:
| レシピ | 目的 |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | 各プロジェクトが実際に実行できる最も安価な修復策を選択します |
2つのステップを順番に実行します。
1. プロジェクト自身のシリーズの修正バージョンへのアップグレード。 Spring Securityは修正を6.5.9と7.0.4としてMaven Centralに公開しました。各アップグレードは FindAffectedSpringSecuritySeries の前提条件でゲートされています。UpgradeDependencyVersion はターゲットが新しいことだけをチェックするため、7.0.4へのアップグレードを指示すると5.8プロジェクトを2つのメジャーバージョンにまたがって引きずり込む可能性があります。
2. ステップ1で修正できなかったものに対する先行ヘッダー書き込み設定の追加。 これによりプロジェクトごとに1つの @Configuration クラスが生成されます:
@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;
}
};
}
ヘッダーを事前に書き込むことで、ラッパーがコミットを監視するかどうかは無関係になり、プロジェクト内のすべてのシンクが一度に閉じられます — 「既知の制限」にある Map フローなど、汚染分析が到達できないものも含みます。BeanPostProcessor は HttpSecurity DSL によって構築されたフィルターを認識します。これは AutowireBeanFactoryObjectPostProcessor がビーンファクトリーを通じてそれらを初期化するためです。このCVEに対する独立して公開された2つの修正は、まさにこの形状を使用しています(hmcts/idam-web-public、およびarmory-ioのSpinnakerフォークは同等の ObjectPostProcessor を使用)。
ステップ2は、ステップ1が対応できないプロジェクトをカバーします:
| 状況 | アップグレードが機能しない理由 |
|---|---|
| 5.7、5.8、6.3、6.4 | 修正はSpring Enterpriseサブスクライバーのみに提供 — Maven Centralにはありません |
| 6.0 - 6.2 | これらのシリーズでは修正はリリースされていません |
| インポートされたBOMで管理されるバージョン | アップグレードが編集するローカル宣言がありません |
最後の行はコーナーケースではありません。nla/bamboo は7.0.3 — オープンソース修正のあるシリーズ — をSpring Boot BOMからのみ解決するため、バージョンチェックだけでは両方のステップでスキップされ、脆弱なままになります。したがって AddEagerHeaderWriterConfiguration は、プロジェクトがオープンソース修正を利用できかつ独自のバージョンを宣言している場合にのみアップグレードに委譲します。
生成されたクラスは、@EnableWebSecurity クラスが存在する場合はその隣に配置され、存在しない場合は @SpringBootApplication、次に任意の @Configuration にフォールバックするため、常にコンポーネントスキャンが到達する場所に配置されます。すでにヘッダーを先行書き込みしているプロジェクト、またはパッチ適用済みの OnCommittedResponseWrapper をベンダー提供しているプロジェクト(jogetworkflow/jw-community など)はそのまま残されます。
| レシピ | 目的 |
|---|---|
io.moderne.recipe.cve202622732.UpgradeSpringSecurityToPatchedVersion | シリーズでゲートされた6.5.9 / 7.0.4へのアップグレード |
io.moderne.recipe.cve202622732.AddEagerHeaderWriterConfiguration | 生成された設定(単独) |
io.moderne.recipe.cve202622732.FindAffectedSpringSecuritySeries | 影響を受けるシリーズのプロジェクトをマークします。アップグレードの前提条件です |
脆弱なSpring Security 6.4.12上の semgrep/cve-2026-22732-demo に適用し、組み込みTomcatに対して検証しました。その HeaderVerificationTest はセキュリティヘッダーが欠落していることを検証するため、正常に機能する修正ではテストが失敗します:
| エンドポイント | 修正前 | 修正後 |
|---|---|---|
/vuln/stream | X-Content-Type-Options: null | nosniff |
/vuln/content-length | X-Content-Type-Options: null | nosniff |
/safe | nosniff | nosniff |
X-Frame-Options と Cache-Control も同じパターンに従います。
ビルドが成功するコーパスの16リポジトリ(特定された29のうち)全体で、修正は3つに対して設定を生成し、残りは正しくそのまま残しました:
| リポジトリ | 結果 |
|---|---|
semgrep/cve-2026-22732-demo | com/example/vuln に @EnableWebSecurity の隣に生成。コンパイル成功、ヘッダー復元 |
nla/bamboo | ui/src/bamboo に @SpringBootApplication の隣に生成。コンパイル成功。アップグレードが到達できないBOM管理の7.0.3ケース |
star-whale/starwhale | ai/starwhale/mlops/configuration/security に @EnableWebSecurity の隣に生成。コンパイル成功(宣言されたターゲットであるJDK 11) |
hmcts/idam-web-public | そのまま — すでに setShouldWriteHeadersEagerly を呼び出しています |
okta/okta-idx-java (6.5.9)、psi-probe (6.5.11)、Ant-Media-Server (6.5.11) | そのまま — 自身のシリーズの修正を超えています |
apache/shenyu (6.3.1) | そのまま — リアクティブのみ。HeaderWriterFilter の背後にサーブレットAPIがなく、CVEはサーブレットのみです |
brutusin/Brutusin-RPC (4.0.4) | そのまま — setShouldWriteHeadersEagerly(5.2)より前です |
bootplus、template-app、front50、igor、rosco、spring-security、reportserver | そのまま — 影響を受けるSpring Securityバージョンが解決されていません |
修正済みの3つのリポジトリに対して修正を再実行しても追加の生成はないため、この修復は実プロジェクトの自身の出力に対して冪等です。