何が起きたのか:Google Brotliの境界外書き込み(CVE-2026-111497)の仕組み
Google Brotliのデコーダーに発見された脆弱性には、CVE-2026-111497が割り当てられています[1]。CVEレコードには、公開日(datePublished)が2026年10月22日、開示日(datePublic)が2026年2月27日と記録されています[1]。セキュリティコミュニティで「Node2Shell」や「HTTP2Shell」と呼ばれている事象も、この脆弱性を指します[8]。
本脆弱性の主分類は、デコーダーにおける境界外書き込み(CWE-787)です[2, 8]。根本原因は、Google BrotliのC言語デコーダーが、無効なblock switch(ブロック切り替え)を処理する際に関数の戻り値やエラー状態を確認しないこと(戻り値の未検査、CWE-252)にあります[2, 8, 17]。高速デコード経路が無効なblock switchの戻り値を無視し、破損した状態のまま処理を続けるため、内部のring bufferの範囲外に書き込みます[17]。Googleの上流コードには、無効なblock switchを検知した際、専用のフォーマットエラー(BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH)を直ちに返す修正が追加されました[17]。
Node.jsの公式告知によると、無効なblock switchが一時的な入力待ちとして扱われ、ストリーミングデコーダーが容量8バイトの内部バッファに入力バイトを追記し続けることで、境界外書き込みに至ります[18]。
リモートコード実行の実現性については、Impervaが公式のNode.js 24.22.0 Linux arm64環境で、Brotliレスポンスと設定済みリクエストパーサーの両方を経由したコード実行を報告しています。コード実行の成功には複数回の試行が必要だったとされています[23]。一方、実地での悪用状況について、マレーシアのCERT(MyCERT)は2026年10月23日時点で悪用の報告はなく、米CISAの悪用確認済み脆弱性カタログ(KEV)にも掲載されていないと報告しています[62]。これは調査時点で報告が確認されていないことを示す記録であり、悪用がなかったことを客観的に証明するものではありません[62]。
深刻度の評価は、評価主体や利用環境によって異なります。上流CVEレコードでは、CVSS v4.0の基本スコアが9.2(Critical)と評価されています[4, 16]。Node.jsの公式告知では、製品向けの評価を「High」としています[4, 16]。Red Hatは攻撃の複雑さ(Attack Complexity)をHighと評価し、CVSS v3.1の基本スコアを8.1(Important)としています[4]。そのため、上流ライブラリ全体に対する基準値と、各製品・ランタイムの利用形態を踏まえた個別の評価を分けて参照する必要があります[4, 16]。
| 評価主体 | 対象・製品 | 評価体系 / スコア | 深刻度レベル | 評価の文脈・備考 |
|---|---|---|---|---|
| CVE Project(CVEレコード) | 上流Brotliライブラリ全体 | CVSS v4.0: 9.2 | Critical | 基本スコア9.2(10点満点)[4, 16] |
| Node.js 公式セキュリティ告知 | Node.js ランタイム | 独自基準 | High | node ^4 [16] |
| Red Hat | Red Hat製品群 | CVSS v3.1: 8.1 | Important | 攻撃複雑性(AC)をHighと評価[4] |
補足:CWE-787とCWE-252とは
CWE-787は、プログラムが意図されたメモリバッファの範囲外にデータを書き込む脆弱性(境界外書き込み)です。CWE-252は、関数の呼び出し結果やエラーコードを確認せずに処理を続ける不具合(戻り値の未検査)を指します。
なぜ今になって波及したのか:上流修正の潜伏と「Node2Shell」「HTTP2Shell」の経緯
本脆弱性が2026年10月になって広く注目された背景には、CVEレコードに開示日(datePublic)として記録された2026年2月27日の上流修正から、公開日(datePublished)である2026年10月22日のセキュリティ情報開示まで、約8か月の開きがありました[1, 13]。Google Brotliのソースコードには2026年2月の時点で修正コミットが取り込まれていました(CVEレコードのdatePublicは2026-02-27)[1, 13]。しかし、CVEレコードに開示日(datePublic)の記録がある一方で、当時はセキュリティ修正としての新バージョンがリリースされておらず、脆弱性として公に認識が広がったのは10月の開示以降だったとされています[1, 13]。
事態が大きく動いたのは2026年10月です。Node.jsプロジェクトがセキュリティリリースを行い、CVE-2026-111497が公式に開示されると、同じデコーダーを共有する下流パッケージの存在も次々と明らかになりました[13]。先行して公開されたImpervaの「Node2Shell」検証レポートは、Node.jsの組み込みデコーダーを対象に、外部サーバーから返される圧縮レスポンスと、設定済みパーサーが処理する圧縮リクエストの両方からコード実行できることを実証しました[9]。
続いて、セキュリティ企業のAikidoが「HTTP2Shell」と題したレポートを公開しました[10]。このレポートが新たに提起したのは、本脆弱性の影響がNode.jsランタイムにとどまらず、同じC言語デコーダーをソースコードレベルで同梱するPythonのPyPIパッケージ(Brotliおよびbrotlicffi)と、それらを利用するPython HTTPクライアントにも及ぶという点です[10]。
「HTTP2Shell」という呼称は、HTTP/2プロトコル固有の欠陥を示すものではなく、HTTP通信などを通じて信頼できないBrotliデータがデコーダーに届く経路全般を指す通称と捉えるのが適切です[12]。公開資料が示しているのは、無効なBrotliストリームがデコーダーに渡る経路です。HTTP/2の利用が攻撃成立の必須条件であるとは確認されていません[22]。Node.jsの公式告知でも、アプリがnode:zlibを使って攻撃者の用意したBrotliデータを展開することが成立条件として示されており、HTTP/2に限定する記述はありません[58]。
下流エコシステムへの情報反映には時間差もありました。Aikidoの初期レポートは「2026年10月23日時点でPyPIパッケージが脆弱性データベースに未登録」と注意喚起していましたが、これは調査時点の状況を示したものです[15]。その後、PyPA(Python Packaging Advisory Database)にはBrotliとbrotlicffiの両方について、同一のCVE-2026-111497をエイリアスとする正式な勧告が登録されています[15]。公開された各種セキュリティ勧告は、影響を受けるライブラリ名やバージョン範囲を示していますが、稼働中のクライアントやサーバーの総数、実害を受けるアプリケーションの総数は公表していません。そのため、エコシステム全体の定量的な影響規模は、公開資料から算出できません[63]。
後輩2月の時点で修正コードが入っていたのに、なぜ10月になってから急激に警戒が高まったんですか?
先輩修正当時は重大なセキュリティ欠陥として認識されておらず、公式リリースも出されなかったんだよ。10月にNode.jsコミュニティでコード実行のリスクが公表されて初めて、同じCデコーダーを抱え込んでいた他の言語やパッケージにも危機感が広がったんだね。
後輩C言語の上流ライブラリをそのまま取り込んでいた別言語のエコシステムに、時間差で波及したわけですね。
先輩そうだね。さらにHTTP2Shellという名前からHTTP/2のプロトコル自体の問題と捉えられやすいけれど、本質はHTTPの種別を問わず、非信頼なBrotliデータがデコーダーに渡る経路にあるんだよ。
誰に影響するのか:クライアント・サーバー別の到達経路と依存関係
修正前のパッケージが環境に導入されているだけで、直ちに攻撃が成立するわけではありません[21, 37]。攻撃者が、デコーダーに渡される非信頼なBrotliデータを制御できる入力経路がある場合に、攻撃経路が成立します[21, 37]。具体的な経路は、クライアントとしての受信処理と、サーバーとしての受信処理に大別できます。
HTTPクライアントでは、接続先の外部サーバーが悪意あるBrotliレスポンスを返し、クライアント側が自動で展開する経路が該当します[21]。Pythonでは、アプリケーションコードがBrotliライブラリを直接importしていなくても影響を受ける可能性があります[11, 57]。Python環境にPyPIのBrotliまたはbrotlicffiがあり、主要なHTTPクライアントがContent-Encoding: br付きのレスポンスを自動的にデコードする構成になっている場合が該当します[5, 11, 19, 57]。
各HTTPクライアントの呼び出し経路は次の通りです。
- Requests: 環境に
brotliまたはbrotlicffiが導入されていると、Brotliレスポンスを自動展開します[27]。内部ではResponse.iter_content()からurllib3のHTTPResponse.stream(..., decode_content=True)、さらにurllib3.response.BrotliDecoderを経由して、インストール済みのデコーダーオブジェクトに到達します[27]。urllib3[brotli]というオプション依存(extra)もありますが、現行メタデータが指定する下限はBrotli 1.2.0またはbrotlicffi 1.2.0.0です[28]。そのため、依存定義にextraを記載しただけでは修正版が導入されたとは判断できません。ロックファイルで解決された実バージョンを確認する必要があります[28]。 - HTTPX: 通常のインストールでは、Brotliサポートは任意依存です[29]。環境に
httpx[brotli]を追加するか、brotliまたはbrotlicffiをインストールすると、HTTPXのBrotliDecoderが有効になり、brotli.Decompressor()のdecompress()またはprocess()を呼び出して展開します[29]。 - aiohttp:
ClientSessionのauto_decompressパラメータは既定でTrueです[30]。Brotli対応は任意ですが、環境内でbrotlicffiまたはbrotliが検出され、Content-Encoding: brを受信すると、HTTPパーサーがBrotliDecompressorを動的に生成してデータを展開します[30]。
サーバー側では、クライアントから送信されたContent-Encoding: br付きのリクエストボディを展開するミドルウェアやリクエストパーサーが、攻撃経路になります[20, 61]。PyPAの勧告も、リクエストボディを展開するサービスを影響経路として示しています。Impervaの検証環境でも、リクエストパーサーを設定したNode.jsアプリケーションでコード実行が実証されました。ただし、すべてのビルドやアプリケーションを網羅的に検証した結果ではありません[20, 61]。
JavaScriptランタイムのNode.jsとBunは、外部ライブラリの有無にかかわらず、ランタイム自身に組み込まれた機能が影響を受けます。
- Node.js: Brotliの圧縮・展開機能は、サードパーティのnpm依存ではなく、標準モジュールの
node:zlibに組み込まれています[31]。Node.js標準のfetchは内部で同梱ライブラリUndiciを使用し、Content-Encoding: br付きのレスポンスをzlib.createBrotliDecompress()に流して展開します[31]。node-fetch、Axios(Node HTTPアダプターの既定設定decompress: true)、Got(既定設定decompress: true、内部のdecompress-response経由)などの主要クライアントも、Node.jsランタイムのnode:zlibが提供するcreateBrotliDecompress()を呼び出します[33, 34, 35]。そのため、これらのNode.jsクライアントでは、更新対象はクライアントパッケージではなく、実行基盤であるNode.jsランタイム本体です[34, 35]。 - Bun: Google Brotliライブラリをランタイムに内包しています[7, 36]。
fetchによる自動展開に加え、node:zlibやDecompressionStream("brotli")を呼び出す処理からもデコーダーにデータが到達します[7, 36, 59]。
後輩アプリケーション側でBrotliを直接importしていなくても、HTTPクライアントを使っているだけでデコーダーに達してしまうんですね。
先輩その通りだね。クライアントがレスポンスの圧縮ヘッダーを見て自動的に展開する親切な設計になっているから、同じ環境内にパッケージが存在するだけで処理経路が開いてしまうんだよ。
後輩それはクライアント側の話ですよね。うちのWeb APIサーバー側はどう確認すればいいですか?
先輩サーバー側では、クライアントから送られてきたBrotli圧縮リクエストの本文を展開するミドルウェアやリクエストパーサーが有効になっているかどうかが判断基準になるよ。
何が変わるのか:上流・ランタイム・言語パッケージの修正版と互換性
本脆弱性に対処するため、上流リポジトリ、各言語ランタイム、パッケージ配布元から、それぞれ修正版が公開されています。
各環境の影響範囲と修正バージョンは次の通りです。
- Google Brotli上流コード: 影響を受けるのは1.0.3以上1.0.10未満、1.1.0以上1.1.1未満、1.2.0以上1.2.1未満です[3]。上流プロジェクトは修正版として
v1.0.10、v1.1.1、v1.2.1を公開しました[3, 24]。 - Node.js: 22.x、24.x、26.x系が影響を受けます[6, 32, 51]。修正版として
v22.23.4、v24.22.1、v26.12.1がリリースされています[6, 14, 24, 32, 51]。公式告知では、サポートが終了したEOL(End-of-Life)版も、セキュリティリリース時点では影響対象として案内されています[6, 24, 32, 51]。 - Bun: 1.0.21以上1.4.4未満が影響を受けます[7, 25, 59]。Brotli 1.2.1に更新された
1.4.4が修正版です[7, 14, 25, 36, 53, 59]。 - Pythonパッケージ(PyPI): PyPAのセキュリティ勧告では、
Brotliは1.2.0以下(1.2.1未満)、brotlicffiは1.2.1.0未満が修正前の対象です[5, 26, 39, 50]。修正版はそれぞれ1.2.1と1.2.1.0です[5, 14, 24, 26, 39, 50, 57]。
更新時には、コンパイル済みバイナリの再ビルドが必要になる場合があります。Bunのbun build --compileで生成した単一実行ファイルには、Bunランタイム全体が埋め込まれています[7, 14, 25, 36, 53, 60]。そのため、ホスト環境のBun本体を1.4.4に更新しても、既存バイナリ内の脆弱なデコーダーは置き換わりません。修正版のBunを使って実行ファイルを再ビルドする必要があります[7, 14, 25, 36, 53, 60]。
挙動と互換性については、上流コミットの修正により、無効なblock switchを含む不正なストリームに対して専用のエラーコード(BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH)を返すようになりました[17, 55]。不正入力時の例外処理や特定のエラーメッセージに依存する実装がある場合は、回帰確認が必要です[55]。参照した一次情報資料では、仕様に準拠した有効なBrotliストリームの展開における後方互換性の問題は報告されていません[55]。
性能面について、公開されている上流リリースノートや各製品の公式勧告には、脆弱性修正の内容や不正データの処理方法が記載されています。一方、正常なBrotliデータの展開速度やCPU・メモリ使用率について、修正前後を比較した定量的なベンチマークデータはありません[64]。そのため、展開処理への性能影響は、公開情報から定量的に評価できません[64]。
| 対象レイヤー / パッケージ | 影響を受けるバージョン範囲 | 修正バージョン | 更新時の留意事項 |
|---|---|---|---|
| Google Brotli(上流) | 1.0.3〜1.0.9 / 1.1.0 / 1.2.0 | 1.0.10, 1.1.1, 1.2.1 | マイナー系統ごとに修正版を提供[3, 24] |
| Node.js ランタイム | 22.x, 24.x, 26.x(EOL版含む) | 22.23.4, 24.22.1, 26.12.1 | node ^6 [24, 32, 51] |
| Bun ランタイム | 1.0.21 以上 1.4.4 未満 | 1.4.4 | 内包Brotliを1.2.1へ更新。バイナリ再ビルド必要[7, 25, 36, 53] |
| PyPI: Brotli | 1.2.0 以下(1.2.1 未満) | 1.2.1 | 上流修正を含むバージョンへ更新[5, 24, 26, 50] |
| PyPI: brotlicffi | 1.2.1.0 未満 | 1.2.1.0 | 同梱Cソースおよびリンク設定を更新[5, 24, 26, 50] |
実務での確認と更新判断:同梱デコーダーの特定・再配備・緩和策
実務で影響を調査し、対策を判断する際は、HTTPクライアントの製品名だけでなく、実行環境で実際にロードされるBrotliデコーダーを特定することが基本です[38, 46]。PyPAの勧告が明記している通り、本脆弱性の原因はクライアントライブラリや特定ランタイム独自の実装ではなく、Googleが提供するC言語ライブラリにあります[38, 46]。
特にPython環境では、パッケージの配布形式やビルド設定によって、デコーダーがシステム共有ライブラリとして使われる場合と、拡張バイナリにコードがコンパイルされて同梱される場合があります[41, 42, 43, 49]。
- 配布形式(wheelとsdist): Pythonのsdist(ソース配布)は導入環境でCソースをビルドします。一方、C拡張を含むwheel(ビルド済み配布)には、コンパイル済みコードがあらかじめ含まれています[41]。
- Google Brotli(Pythonバインディング): 標準のビルド構成では、
c/dec/decode.cをPython拡張バイナリに直接コンパイルして含めます[42, 49]。環境変数USE_SYSTEM_BROTLIを指定した場合に限り、システムのlibbrotli共有ライブラリをリンクします[42, 49]。そのため、OS側のパッケージを更新しても、wheelなどで導入された拡張バイナリ内の同梱コードは自動では更新されません[42, 49]。 - brotlicffi:
setup.pyの標準設定では、同梱されたlibbrotli/c配下のCソースを直接コンパイルして利用します[43, 49]。環境変数USE_SHARED_BROTLI=1を指定した場合に限り、システムのbrotliencとbrotlidecを動的リンクします[43, 49]。OSライブラリの更新だけで対策が完了するかどうかは、導入した配布物とビルドオプションによって異なります[43, 49]。
実環境では、次の手順で調査と更新を進めます。
- Python環境の依存調査: 本番サービスが稼働する環境で、該当のPythonを使って
python -m pip show Brotli brotlicffiを実行し、導入バージョンを確認します[40]。ロックファイルや固定pinの記載と、実環境に導入されたバージョンを照合します[40, 50]。実務では、本番と同じインタープリター上でpython -m pip inspectを実行して導入バージョンと配置場所を調べ、実際にimportされるモジュールの__file__または__spec__.originを確認してロード元を特定します[47]。 - OSパッケージとディストリビューション情報の確認: OS配布のパッケージを利用している場合、上流のバージョン番号(1.0.9など)だけで脆弱性の有無を判断せず、各Linuxディストリビューションのセキュリティトラッカーとパッケージ改訂番号を確認します[44, 52]。たとえばDebian bookwormでは、上流版1.0.9を基にバックポート修正を適用した
1.0.9-2+deb12u1が修正済みパッケージとして提供されています[44, 52]。Debianが配布するnodejsパッケージのように、Node.js本体がシステム提供のBrotli共有ライブラリをリンクする構成もあります[52]。 - 実行中プロセスの検証: パッケージ一覧を確認するだけでは、実行中プロセスのメモリ状態は分かりません[45]。Linuxでは、調査対象プロセスの
/proc/<PID>/mapsを確認し、メモリにマップされた共有ライブラリのファイルパスを調べます[45]。マップ領域にシステムのlibbrotliが見当たらない場合は、Python拡張バイナリや実行ファイル自体に静的に組み込まれていないかも確認します[45]。Linuxコンテナなどで/proc/<PID>/exeと/proc/<PID>/mapsを照合すると、実行コマンドやマップファイルを確認できます。ただし、この方法ではデコーダーのバージョンそのものを直接判定できません[48]。 - 再配備とプロセスの再起動: ロックファイルやインストール済みパッケージの情報だけでは、稼働中のコンテナやプロセスが修正済みデコーダーをロードしているとは保証できません[56]。コンテナイメージのダイジェスト(digest)、実際のランタイム、モジュールのimport先、メモリマップを照合します。脆弱なコードを含むwheelやコンパイル済みバイナリは再ビルドし、本番プロセスを再起動して適用状態を確認します[56]。
- 暫定的な緩和策の適用: 修正版の適用や再配備が完了するまでの回避策として、信頼できない入力を受け取るすべての経路でBrotliの自動展開を停止するか、
Content-Encoding: brを拒否する設定にします[54]。対象には、クライアント側の自動展開に加え、サーバー側の受信リクエストパーサーや手動で呼び出すデコードAPIも含めます[54]。Bunでは、リクエスト単位のワークアラウンドとして、fetchのオプションにdecompress: falseを渡す方法が示されています[54]。
補足:wheelとsdistとは
wheelは、コンパイル済みのバイナリやモジュールを含むPythonの標準パッケージ形式で、導入環境でのコンパイルを必要としません。sdist(ソースディストリビューション)はソースコードをまとめた配布形式で、パッケージの導入時にローカル環境でビルド処理が実行されます。
後輩パッケージ管理ツールで依存バージョンを更新しただけでは、実務上の確認としては不十分なんですか?
先輩wheelの中にデコーダーがコンパイル済みで静的に含まれていたり、OSの共有ライブラリをリンクしていたりと、ビルドや配置の形式によって実体の場所が異なるんだよ。
後輩パッケージの名前や定義ファイルを見るだけでなく、実行時にどの実体バイナリがメモリへ読み込まれているかまで確認しないと見落としが起きるわけですね。
先輩その通りだね。バイナリの再ビルドや配備イメージの更新を正しく行い、プロセスを再起動して実体を確認することが確実な防御につながるよ。



