Pillow FlashPixのヒープオーバーフローによるRCE(CVE-2026-112398)
olefileをインストールしたPillow 12.3.0以前で細工されたFlashPix画像をデコードすると、攻撃者がコマンドを実行できます。CVE-2026-112398はPillow 13.0.0で修正されています。
要点
CVE-2026-112398は、PillowのFlashPix JPEGデコーダーにあるクリティカル(CVSS v4.0 9.2)なヒープバッファオーバーフローです。olefileがインストールされた12.3.0までの全リリースで、アプリケーションが信頼できない画像をデコードすると、攻撃者の選んだコマンドが実行される可能性があります。
- PillowのFlashPixプラグインは、MicrosoftのCompound File Binary FormatであるOLE2コンテナー内のJPEGタイルをデコードします。FlashPixメタデータが宣言するカラーモードからデコードバッファを決める一方、libjpegは埋め込まれたJPEG自身の成分数に従って行を出力します。1ピクセル2バイトと宣言するファイル内のJPEGが1ピクセル4バイトを出力すると、各行でデコードバッファがオーバーフローします。
- 信頼できないアップロードを開いてデコードするサービスは、オプションのolefileパッケージがインストールされたPillow 12.3.0以前を使っていれば危険にさらされます。ファイルは拡張子ではなく内容のマジック値で検出され、ユーザー操作なしで通常の
Image.open().load()呼び出し内でオーバーフローします。 - ベンダーはCWE-122(ヒープベースのバッファオーバーフロー)とCWE-787(境界外書き込み)を割り当てています。アドバイザリには標準のPillow 12.2.0でコマンドを実行する概念実証(PoC)が含まれます。CISAのAuthorized Data Publisher(ADP)による付加情報は、悪用エビデンスを「poc」としています。2026年10月24日時点でKEVカタログに登録されておらず、実環境の攻撃も確認されていません。
- 2026年10月16日公開のPillow 13.0.0にアップグレードしてください。難しい場合は、olefileをアンインストールしてFlashPixとMicrosoft Image Composerの対応を無効にするか、Pillowに届く前にOLE2ファイルを拒否してください。
影響を受けるか
次の条件をすべて満たす場合に影響を受けます。
- アプリケーション、またはインポートするコードが、
Image.open()で信頼できないファイルを開き、.load()、.thumbnail()、.tobytes()などピクセルデータを読む呼び出しでデコードを実行する。 - インストールされたPillowが12.3.0以前。過去の全リリースが影響を受け、CVEレコードは範囲を13.0.0未満としています。
- 環境にオプションのolefileパッケージがインストールされている。FlashPixの検出処理がこれをインポートし、Pillowの現在のパッケージ設定では
fpxとmic機能のオプション依存関係として宣言されています。
Pillow 13.0.0以降で画像処理している場合、またはolefileがなくFlashPixプラグインが読み込まれない場合は、影響を受けません。
PyPIで直近1週間のpillowダウンロード数は141,847,434件でした(2026年10月24日確認)。Pillowは間接依存として入ることが多いため、直接宣言するプロジェクトだけでなく、すべての環境を監査してください。
| ブランチ | 影響を受けるバージョン | 修正バージョン |
|---|---|---|
| 13.x | なし | 13.0.0以降 |
| 12.xおよびすべての旧バージョン | <= 12.3.0 | 13.0.0 |
コンテナーイメージとビルド環境を含め、各環境で動作するものを確認します。
pip show pillow | grep -E "^(Name|Version)"
python -c "import PIL; print(PIL.__version__)"
python -c "import olefile; print('olefile', olefile.__version__)" 2>/dev/null || echo "olefile absent"
# pip requirements files
grep -i "^pillow" requirements*.txt
# Pipenv and pyproject.toml
grep -i "pillow" Pipfile.lock pyproject.toml 2>/dev/null
# Poetry
grep -A2 "name = \"pillow\"" poetry.lock
# Container images
docker run --rm <image> python -c "import PIL; print(PIL.__version__)"
修正方法
修正済みリリースにアップグレードします。
pip install --upgrade "pillow>=13.0.0"
Pillow 13.0.0はPython 3.10のサポートを終了しています。3.10に固定された環境ではPythonのアップグレードか、以下の回避策が必要です。
まだアップグレードできない場合、脆弱な経路を有効にする依存関係を削除します。FlashPix対応にはolefileが必要なため、アンインストールすればその経路を無効にできます。
pip uninstall -y olefile
これにより、アドバイザリがolefile依存の形式として挙げるFlashPixとMicrosoft Image Composer(MIC)の対応がなくなります。制約は明確です。正当にFlashPixやMICを処理するサービスはその機能を失い、環境内でolefileをインポートする他のコードも動作しなくなります。
代わりに、ゲートウェイでOLE2のマジック値D0CF11E0A1B11AE1から始まるアップロードを拒否できます。これはプラグイン自身が照合する値と同じであり、拡張子に関係なく入口でFlashPixを止められます。
根本原因
FlashPixプラグインはFlashPixメタデータからJPEGデコードバッファの大きさを決める一方、libjpegは埋め込まれたJPEGヘッダーから行の大きさを決めて書き込みます。両者を照合するチェックがありませんでした。
FlashPixプラグインはコンテナー内の色識別子をPillowのモードに対応付けます。12.2.0のsrc/PIL/FpxImagePlugin.py、24~33行目です。
MODES = {
# opacity
(0x00007FFE,): ("A", "L"),
# monochrome
(0x00010000,): ("L", "L"),
(0x00018000, 0x00017FFE): ("RGBA", "LA"),
...
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
}
攻撃者はファイルに色識別子を書き込むことで、適用するテーブル行を選べます。("RGBA", "LA")の行は、1ピクセル2バイトのrawmode LAを選びます。12.2.0のsrc/decode.c、223~226行目で、デコーダーはそのrawmodeから走査線バッファを確保します。
state->bytes = (state->bits * state->xsize + 7) / 8; // "LA" gives 2 * xsize bytes per row
...
state->buffer = (UINT8 *)calloc(1, state->bytes);
次にJPEGデコーダーが、12.2.0のsrc/libImaging/JpegDecode.cでrawmode文字列をlibjpegの出力色空間に対応付けます。CMYK、YCbCr、YCbCrKなど認識されるrawmodeには具体的な色空間が設定されます。LAとYCC;Pは認識されず、219~221行目のフォールバックに進みます。
/* Disable decoder conversions. */
context->cinfo.jpeg_color_space = JCS_UNKNOWN;
context->cinfo.out_color_space = JCS_UNKNOWN;
JCS_UNKNOWNでは、libjpegは色変換せず、1ピクセルあたりnum_componentsバイトを出力します。この値は攻撃者が埋め込みJPEGのSOFマーカーに書き込みます。2バイト用のバッファに対して4に設定すると、252行目のjpeg_read_scanlinesが呼ばれるたびに、1ピクセル2バイトのバッファへ4成分の行を書き込みます。
ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1); // writes 4 * width into a 2 * width buffer
オーバーフローの長さは、画像の幅を通じて攻撃者が制御できます。はみ出すバイトはデコードされたピクセルであり、攻撃者は直接書くのではなくJPEGストリームによって形作ります。この経路に到達させるマジック値のチェックは、同じプラグインファイルの先頭にあります。
def _accept(prefix: bytes) -> bool:
return prefix.startswith(olefile.MAGIC)
これはOLE2のマジック値であり、Pillowが開くよう求められたすべてのファイルの内容で照合されます。
悪用の仕組み
攻撃者は、メタデータが1ピクセル2バイトと宣言する一方、埋め込みJPEGは4成分を宣言するFlashPixファイルを作ります。デコードするとヒープバッファがオーバーフローし、アドバイザリのPoCはこれを型ポインターの乗っ取りにつなげ、system()を呼び出します。
アドバイザリのPoCが示す攻撃経路です。
- 攻撃者はアドバイザリのペイロードビルダーを使い、自分の端末で単独のFlashPixファイルを作成します。作成に対象へのアクセスは不要です。
- ファイルは
("RGBA", "LA")モードを宣言し、Pillowに1ピクセル2バイトのタイルバッファを用意させます。一方、埋め込まれたJPEGのSOFは4成分を宣言するため、libjpegは1ピクセル4バイトを出力します。 - 被害側のサービスはアップロードや取得の経路からファイルを受け取り、脆弱なデコードを実行します。PoCが行う操作の全体は
Image.open(path).load()です。
#!/usr/bin/env python3
# The entire vulnerable operation an application performs on an untrusted file.
import sys
from PIL import Image
Image.open(sys.argv[1]).load()
- ファイルヘッダーのOLE2マジック値により、名前にかかわらずFlashPixプラグインが選ばれます。最初の
load()でオーバーフローが起きます。 - オーバーフローがデコードバッファの隣にある
bytesオブジェクトを上書きし、制御下の同じヒープメモリ内に偽の型オブジェクトとvtableを置きます。Pillowが入力バッファを解放すると、PyBuffer_Releaseが乗っ取られた型ポインターをたどり、そこを介して呼び出します。PoCは呼び出し先をsystem@pltにし、第1引数レジスターにコマンド文字列を置きます。その結果、画像処理の実行ユーザーとしてsystem("id")が実行されます。
公開PoCは、組み込みアドレスを有効に保つため、ASLRを無効化した固定のDockerイメージで動作します。idが実行された後、破損したヒープによってインタープリターがcorrupted size vs. prev_sizeで異常終了しますが、コマンドはすでに実行されています。通常のサーバーでは、同じオーバーフローでもアドレスのランダム化に対処する必要があります。そのため、このPoCは汎用的な一発のエクスプロイトではなく、不具合と攻撃に利用する経路の実証です。
FlashPixのオーバーフローが発生する流れ
一連の処理で、OLE2マジック値がFlashPixプラグインを選び、LA rawmodeが1ピクセル2バイトの行バッファを決め、JCS_UNKNOWNがlibjpegにJPEGの4成分をすべて出力させ、長すぎる行が隣のヒープオブジェクトに書き込まれます。
サービスにアップロード"] --> B["Image.open()がOLE2の
マジック値D0CF11E0に一致"] B --> C["FlashPixの色IDがrawmode LAを選択
1ピクセル2バイト"] C --> D["デコードバッファを
2 * widthバイトで確保"] D --> E["rawmode LAが認識されず
libjpegをJCS_UNKNOWNに設定"] E --> F["埋め込みJPEGが4成分を宣言
1ピクセル4バイトで
行を書き込み"] F --> G["攻撃者の内容による
ヒープ境界外書き込み"] G --> H["隣接オブジェクトの
型ポインターを上書き"] H --> I["解放フックが攻撃者の対象を呼び出し
コードが実行される"]
攻撃者に可能なこと
細工したファイルのデコードにより、画像処理を動かすユーザーとして攻撃者の選んだコマンドが実行されます。これは、そのプロセスと、保持するデータや権限の完全な侵害を意味します。 アドバイザリは影響を、そのプロセスのコンテキストでの任意コマンド実行としています。条件は次のとおりです。
- 攻撃者の影響を受けるファイルを開き、デコード(
.load()、.thumbnail()、.tobytes())を実行するエンドポイントへのネットワーク到達性と、オプションのolefile依存関係。アドバイザリはアップロードやサムネイルサービス、アバターやメディアの処理、文書と画像の変換パイプラインを例示 - ユーザー操作も拡張子の細工も不要。検出は内容に基づくため、OLE2のバイトを含む
avatar.pngというファイルもFlashPix経路へ進む - PoCの一度の実行による成功は、ASLRを無効化した固定イメージで実証されている。アドレスランダム化のある実際の対象では追加の悪用手順が必要だが、根本の境界外書き込みは影響バージョンすべてで発生
修正が機能する理由
修正は、走査線を読む前にImagingJpegDecodeで境界チェックを追加し、出力幅または行サイズがデコードバッファを超えるストリームを拒否します。 #10131としてマージされたパッチコミットは、次のチェックを追加します。
+ /* The scanline buffer was sized from the tile extents and the
+ unpacker, not from the JPEG header, so make sure the lines
+ libjpeg is about to produce actually fit in it. */
+ if (context->cinfo.output_width > (JDIMENSION)state->xsize ||
+ (size_t)context->cinfo.output_components * context->cinfo.output_width >
+ (size_t)state->bytes) {
+ jpeg_destroy_decompress(&context->cinfo);
+ state->errcode = IMAGING_CODEC_BROKEN;
+ return -1;
+ }
このチェックは、幅がタイルを超えるJPEGや、成分数がバッファの1ピクセルあたりのサイズを超えるJPEGを拒否し、デコーダーを破棄して、デコードの代わりに「broken data stream」を報告します。バッファサイズとJPEGヘッダーが最終的に出会う場所で、この種の不一致を解消します。LAモードへの対応が残るFlashPixテーブルではなく、収まらない行の書き込みを拒否するようになったデコーダーで修正しています。
走査線が大きすぎるJPEG入力に対して完全な修正です。行がバッファに収まれば正しく動作するJCS_UNKNOWN経路自体は削除していません。
侵害されたかを確認する方法
この問題について、侵害指標、ドメイン、攻撃者のインフラは公開されていません。オーバーフローによって、自社環境で探せる3種類のエビデンスが残ります。
- 画像処理ワーカーのクラッシュログにあるヒープ破損メッセージ。PoCが残す
corrupted size vs. prev_sizeによる異常終了や、アップロード画像を開くプロセスのSIGSEGV終了など - 先頭8バイトがOLE2のマジック値
D0 CF 11 E0 A1 B1 1A E1であるのに、名前や宣言された種類がFlashPixではないアップロードファイル。これはプラグイン自身のマジック値チェックが示す配信時の特徴 - 画像を開いた後に予期しないコマンドを実行したり、予期しないプロセスを生成した処理ジョブ。コード実行はワーカー自身のコンテキストで発生
経緯
- 2026-10-06:Andrew Murray(radarhere)が走査線の境界チェックをコミットし、#10131としてマージ。
- 2026-10-16:Pillowが修正を含む13.0.0をリリース。
- 2026-10-17:Pillowが完全なPoC付きのアドバイザリGHSA-5x8w-8jj5-q52hを公開。クリティカル、CVSS v4.0 9.2と評価し、Alex Bouffard(pebwalker)に謝辞を記載。
- 2026-10-19:GitHub CNAがCVE-2026-112398レコードを公開。
- 2026-10-22:CISAのAuthorized Data Publisher(ADP)の付加情報が、悪用エビデンスを「poc」と記録。
- 2026-10-23:GitHub Advisory Databaseがアドバイザリを公開・レビュー。
- 2026-10-24:本記事を公開。
よくある質問
影響を受けるにはolefileのインストールが必要か?
はい。FlashPixプラグインはolefileをインポートし、Pillowのパッケージ設定はfpxとmicの追加機能のオプション依存関係として宣言しています。olefileがなければプラグインは読み込まれず、OLE2ファイルはデコード前に拒否されます。インストールしたことのない環境は脆弱な経路を通りません。
.fpxアップロードの名前変更や遮断で止められるか?
いいえ。Image.open()は先頭のOLE2マジックバイトでFlashPixプラグインを選ぶため、拡張子は攻撃にも防御にも関係しません。アップロード境界でマジック接頭辞D0CF11E0A1B11AE1に基づいて拒否すれば、プラグインが必要とする同じシグネチャなので止められます。
公開PoCは完全なリモートエクスプロイトか?
動作するコマンド実行の実証ですが、ペイロードに埋め込んだアドレスを固定するため、ASLRを無効化した固定のDockerイメージを対象にしています。アドレスがランダム化されたサーバーでは、同じオーバーフローでもアドレスを配置したり漏えいさせたりする追加作業が必要です。オーバーフロー自体と、攻撃者に与えるヒープの制御は、影響バージョンのどれでも同じように動作します。
通常のJPEGファイルも危険か?
通常のJPEGはこの経路を通りません。この不具合にはFlashPixコンテナーがLA rawmodeとJCS_UNKNOWNフォールバックを選ぶ必要があり、通常のJPEGを開く際には起こりません。また、パッチは行がバッファを超えるJPEG全般を拒否するため、この種の問題はデコーダー層で解消されています。
Pillow 13.0.0は他に何を修正したか?
同じリリースはCVE-2026-112900も修正しています。これはEPS入力に対するGhostscript呼び出しへの引数インジェクションで、2.6.0以降のすべてのPillowに影響し、プロジェクトはクリティカル、CVSS v4.0 9.2と評価しています。13.0.0へのアップグレードで、両方の問題に同時に対応できます。
画像処理サービスを点検
Ostorlabでセキュリティ評価を開始し、上記の依存関係チェックを使って、各画像処理環境のPillowバージョンとolefileの有無を確認してください。影響を受ける環境はPillow 13.0.0以降にアップグレードします。
参考資料
- PoCとDockerラボ付きのPillowアドバイザリGHSA-5x8w-8jj5-q52h。2026年10月17日公開:https://github.com/python-pillow/Pillow/security/advisories/GHSA-5x8w-8jj5-q52h (2026年10月24日確認)
- CVSS v4.0 9.2ベクトルとCISA ADPの「poc」付加情報を含むCVE-2026-112398レコード:https://cveawg.mitre.org/api/cve/CVE-2026-112398 (2026年10月24日確認)
- GitHub Advisory Database項目:https://github.com/advisories/GHSA-5x8w-8jj5-q52h (2026年10月24日確認)
- 2026年10月6日付の修正コミット242a059a、「Reject JPEG streams whose scanlines are wider or have more components」:https://github.com/python-pillow/Pillow/commit/242a059aedc1935097d9aa1dc3f099750467ba75 (2026年10月24日確認)
- 2026年10月16日公開のPillow 13.0.0リリース:https://github.com/python-pillow/Pillow/releases/tag/13.0.0 (2026年10月24日確認)
- 12.2.0の脆弱なFpxImagePluginソース:https://raw.githubusercontent.com/python-pillow/Pillow/12.2.0/src/PIL/FpxImagePlugin.py (2026年10月24日確認)
- 12.2.0の脆弱なJpegDecode.cソース:https://raw.githubusercontent.com/python-pillow/Pillow/12.2.0/src/libImaging/JpegDecode.c (2026年10月24日確認)
- olefileをfpxとmicのオプション追加機能として宣言するPillowのpyproject.toml:https://raw.githubusercontent.com/python-pillow/Pillow/12.2.0/pyproject.toml (2026年10月24日確認)
- pillowのPyPIダウンロード統計。直近1週間に141,847,434件:https://pypistats.org/packages/pillow (2026年10月24日確認)
- 同じリリースで修正されたGhostscript引数インジェクションの関連アドバイザリ:https://github.com/python-pillow/Pillow/security/advisories/GHSA-w889-685w-c8fh (2026年10月24日確認)