TL;DR
Apache Struts S2-074は、 Localized-text処理に存在するサービス拒否の脆弱性です。固定Localeを設定していない Applicationでは、Strutsが受信RequestのLocaleを型変換・Validation error等の Message lookupへ使用していました。対応するCacheは無制限に増加できたため、未認証の Remote clientがRequest localeを変化させ、Java heapを枯渇させて他利用者へのServiceを 停止させることが可能でした。
影響範囲は2.0.0–2.3.37、2.5.0–2.5.33、6.0.0–6.10.0、7.0.0–7.2.1です。
少なくとも6.11.0または7.3.0へ更新してください。Apacheが案内するWorkaroundは、
struts.localeを対応する固定値に設定することです。
JVN#08517956ではCVSS v4.0 8.7が 付与され、公開Reporterクレジットも確認できます。
検知で重要なのはRequest rateだけではありません。Cache entryを作るRequest由来値の カーディナリティを、heap増加やGC負荷と関連付ける必要があります。
調査理由 / なぜ重要か
この問題は、非対称なResource消費の分かりやすい例です。Request自体は小さく正常に 見えても、Serverに新しいCache entryを保持させられます。異なる値を繰り返すことで、 一般的なVolume型DoSの閾値を下回るTrafficでもProcess全体の状態を増加させられます。
同じ設計Riskは、Tenant ID、Model名、Tool ID、Callback ID、Header値、利用者が選ぶ LocaleをKeyにするCacheにも存在します。攻撃者が影響できるKey空間には、意味的な ValidationとResourceのHard limitが両方必要です。AuthenticationはMemory管理の制御に ならず、S2-074は影響を受ける設定では未認証で到達可能です。
Trust Boundary
関係するデータフローは次のとおりです。
remote request
-> servlet request locale
-> Struts localized-text lookup
-> process-wide cache key / entry
-> retained Java heapstruts.localeが固定されていない場合、Request localeはRemoteから制御可能な
Request単位のContextから、Process全体で保持される状態へ渡ります。Trust Boundaryは
単なるHTTP input parsingではなく、一時的な入力がRequest終了後もResource costを持つ
Cacheへ変換される箇所です。
Apacheは、固定Localeを設定したApplicationはLocalized-text lookupにRequest由来Localeを
使用しないため影響を受けないと説明しています。一般的なServlet stackではRequest
localeはAccept-Language等のLocale negotiationから解決されますが、防御側は特定の
Proxy・Container動作を仮定せず、Applicationが実際に利用したLocaleを記録すべきです。
根本原因
二つの通常機能が、安全でない形で組み合わされていました。
- 固定Localeがない場合に、StrutsがRequest由来Localeを選択する。
- Localized-textの結果を、Size上限のない内部Cacheへ保持する。
未認証ClientがProcess全体のCache keyの種類数を増やせることにより、脆弱性になりました。 正当なLocale数、設定された最大数、有限容量のEviction policyによってCache growthが 制約されていませんでした。
これはResource lifetimeのBugです。一つのLocale値をParseできるかだけでなく、時間経過 とともに攻撃者が何種類の値をProcessへ保持させられるかを検証する必要があります。
Safe reproducer
共有環境やProductionに対してheap枯渇を試してはいけません。JVMを停止させずに、次の 安全なValidationで必要なSecurity propertyを確認できます。
- 隔離Networkに使い捨てのStruts Applicationを構築し、意図的に小さいJVM heapと Test timeoutを設定する。
- 通常のLocalized-text lookupを行うRouteを一つ用意する。人工的なMemory allocation loopは追加しない。
- Struts version、
struts.locale設定、実効Request locale、heap使用量、GC activity、 Response statusを記録する。 - 少数に固定したSynthetic locale corpusを低いRateで送信する。Corpusと総Request数は、 承認済みLab budget未満に制限する。
- 6.11.0または7.3.0でMemoryが上限内に保たれ、Applicationが応答を続けることを確認 する。Cache metricsを計装できる場合は、Entry数が設定上限を超えないことも確認する。
- 固定
struts.localeでも繰り返し、Request localeの変更がLookupに使うLocaleを変えない ことを確認する。
非破壊的なTest harnessの制御ロジックは次のように表せます。
for locale in FINITE_SYNTHETIC_LOCALE_CORPUS:
send_one_request(locale)
stop_if(heap_delta > LAB_SAFETY_BUDGET or service_unhealthy)
assert service_healthy
assert fixed_release_cache_is_bounded短時間のTestでheap増加が見えないことを、旧Versionが安全である証拠にしてはいけません。 Versionと設定の棚卸しが、Exposure判定の正本です。有限Corpusは修正とTelemetryの検証用で あり、Service停止を再現するためのものではありません。
修正差分の評価
Apacheは修正版における二つの補完的な制御を説明しています。
- Localized-text cacheを有限化し、
struts.i18n.cacheMaxSizeで最大Sizeを設定可能にする。 struts.locale.validateRequestLocaleにより、Request由来LocaleをRuntimeで利用可能な Locale集合へ制限可能にする。
Apacheによると、後者のRequest locale制限はBackward compatibilityのためDefaultで無効 です。したがって、Validationを有効にしなくても成立しなければならないResource safety 制御は、Cacheの有限化です。Cacheが上限へ達するとEntryは透過的にEvictionされます。 同じ修正が7.3.0と6.x Maintenance releaseの6.11.0に含まれます。
struts.localeを対応する固定値にするWorkaroundは、攻撃者が制御するLocaleをLookupから
除外します。即時のRisk低減には有効ですが、Support終了済み2.x branchを更新せず使い
続ける根拠にはなりません。
Fix reviewでは、新しい設定Keyが存在するだけでなく、Concurrency下でもMemoryが有限で あることを確認します。公開Bulletinが説明するのはBehavior changeであり、本記事は 非公開の行単位Patch historyやPrivate test resultを断定しません。
回帰テスト
| テスト | 期待結果 |
|---|---|
固定struts.localeへ異なるRequest localeを送る | 常に設定済みLocaleを利用 |
| Validation無効で有限の多数Variantを送る | Cacheが設定上限内に留まる |
struts.locale.validateRequestLocale有効時に利用不能Localeを送る | 安全にFallbackし、無制限な新規Keyを作らない |
| Validation有効時に利用可能Localeを送る | 正しいLocalized messageを返す |
| Cacheが設定最大数へ到達 | Evictionし、Serviceは正常を維持 |
| Eviction済みLocaleを再要求 | Staleな言語混在なく正しく再計算 |
| 有限の多数VariantをConcurrency下で送る | Heap、Latency、GCがLab budget内 |
| 型変換・Validation errorがLookupを起動 | どちらの経路も有限Cacheを利用 |
Fallback languageとCache evictionの正しさも確認します。Memoryを直しても、別利用者の Localeを返す、古いMessageを保持する、Concurrency raceを作る場合は完全な修正では ありません。
Detection
高カーディナリティなLocale入力
実効LocaleをApplicationまたは信頼できるEdgeで記録します。次のSPLは、その値が
request_localeへ正規化されている前提です。5分間に20種類という閾値は初期仮説に
すぎません。正規TrafficとNATの特性に合わせて調整してください。
index=YOUR_WEB_INDEX request_locale=*
| bin _time span=5m
| stats count as requests dc(request_locale) as distinct_locales
values(user_agent) as user_agents
by _time src_ip
| where distinct_locales > 20
| sort - distinct_localesService全体のLocale種類数も計算します。攻撃者がSource addressを分散する場合や、Reverse proxyにより多数Clientが同じAddressに見える場合があります。法令とPolicy上取得できる 範囲で、Session ID、TLS client fingerprint、User agent、Route、Edge request IDもPivotに 利用します。
JVMとの相関
Locale cardinalityの増加を次と相関します。
- Baselineへ戻らないOld generationまたは総heap占有率。
- Full GC回数・Pause timeの増加。
- AllocationまたはPromotion pressure。
OutOfMemoryError、Health check失敗、Container restart、Process exit。- Localized-text lookupを起動するRequestのLatency増加。
Request volumeだけでは弱いSignalです。ほぼすべてのRequestが新しい保持Keyを作る場合、 低いRequest rateでも問題になります。
Incident Response
- 代表的なRequest evidenceを保全しながら、信頼できるEdgeで異常なLocale patternを Rate limitまたはFilterする。
- 全Instanceの正確なStruts versionと
struts.locale設定を記録する。Fleet全体が同じ という前提を置かない。 - Access log、実効Locale telemetry、GC log、JVM/Container metrics、Restart history、 Deployment metadataを保全する。
- 6.11.0または7.3.0へ更新する。直ちに更新できない場合は、Apacheの案内どおり対応
する固定
struts.localeを設定する。 - Evidence収集後に影響JVMを計画的にRestartして保持状態を解消し、修正済みまたは緩和 済みInstanceだけがTrafficを処理することを確認する。
- Memory上限のRegression testを実行し、復旧後のheapを監視する。
Heap dumpにはCredentialや利用者Dataが含まれ、取得自体が運用負荷になる場合があります。 組織のForensics・Privacy policyに従い、DiskとMemoryに十分な余裕がある場合だけ取得します。
Disclosure Timeline
- 報告日: S2-074には公開されていません。
- 2026-08-13: 公開S2-074 pageにLast updatedとして記録され、Reporterとして Kuniyoshi Noguchi(野口 晋義), @KuniNoguを掲載。
- 2026-08-25: JVNがJVN#08517956をCVSS v4.0 8.7および公開Reporter クレジットとともに公開。
- 修正版: S2-074は6.11.0と7.3.0を最低修正版として案内。本記事は非公開のPatch日・ Validation日を推測しません。
Commit historyから非公開のTriage・調整日を再構成していません。