Pillow: SpiderImagePlugin.seek()における非圧縮爆弾チェックのバイパスにより、無限にメモリを割り当てるDoS攻撃が発生する
## 概要
SpiderImagePluginのseek()関数は、非圧縮爆弾チェックを回避し、非常に小さい(<1KB)の改ざんされたSPIDERスタックファイルによって、im.load()関数が数ギガバイトのメモリを割り当ててしまう。
## 影響を受けるバージョン
現在までのすべてのバージョン(v13.0.0.dev0、コミットe7a7ea84を含む)
## 根本原因
Image.open()関数は、_open_core関数(src/PIL/Image.py:3668)で、初期の_open()の直後に、Image._decompression_bomb_check(im.size)関数を厳密に1回だけ実行する。
SPIDERスタックファイルはマルチフレーム形式であり、SpiderImageFile.seek(frame)(SpiderImagePlugin.py:179-190)は、攻撃者が制御できるstkoffsetを計算し、self._open()関数を再度直接呼び出す(190行目)—— Image.open()関数を介しては呼び出さない。_open()関数は、そのオフセットにある新しい27個の浮動小数点数ヘッダーを再解析し、以下のように再割り当てする。
self._size = int(h[12]), int(h[2]) # SpiderImagePlugin.py:131
これは、検証されていないヘッダーの浮動小数点数から直接取得される。isSpiderHeader関数は、内部のlabrec*lenbyt==labbytの一貫性のみを確認し、nsam/nrowの範囲は確認しない。この新しいサイズに対しては、非圧縮爆弾チェックは実行されない。
その後のload()関数呼び出しにおいて、ImageFile.load_prepare()(ImageFile.py:436-437)は、検証されていない、攻撃者が選択したサイズで、`self.im = Image.core.new(self.mode, self.size)`を実行する。モードは常に"F"(1ピクセルあたり4バイト)であるため、例えば40000x40000の場合、6.4GBになる。
## なぜこれが深刻なバグなのか
他のマルチフレーム形式のプラグインでは、代替パスで非圧縮爆弾チェックが再実行される。
- GifImagePlugin.py:286 - Image._decompression_bomb_check(self._size)(seek関数内)
- TiffImagePlugin.py:1309 - Image._decompression_bomb_check(self._tile_size)
- IcnsImagePlugin.py:121/138、IcoImagePlugin.py:236/240(サブ画像の選択)
SpiderImagePluginには、どこにも非圧縮爆弾チェックが存在しない。これは、GdImageFile(GHSA-phj9-mv4w-65pm)やFontFileクラス(GHSA-5x94/8v84/45hq)で修正されたバグと同じ種類であり、Image.open()関数を経由しないパスで、攻撃者が指定したヘッダーフィールドによってサイズが決定され、1回のピクセル爆弾チェックとは独立して、メモリが割り当てられる。
## 再現方法
Python 3.13、Pillow HEAD e7a7ea84
添付ファイル:pillow_poc_spider_bomb.py
Image.MAX_IMAGE_PIXELSを5000に設定した後、以下の動作を確認しました。
- 検証:Image.open(スタンドアロンの100x100のSPIDER画像)を実行すると、DecompressionBombWarningが正しく発生します。
- 迂回:Image.open(スタック、frame0=1x1)を実行すると、エラーは発生せず、im.seek(1)でサイズが(100,100)に設定されますが、DecompressionBombWarning/Errorは発生しません。これにより、チェックが迂回されます。
現実的な影響:非常に大きなフレームサイズ(例:40000x40000)の画像の場合、im.load()が約6.4GBのメモリを割り当てようとします(モードF)。この際、爆弾チェックが完全に迂回され、ファイルサイズは1KB未満である可能性があります。
## 提案される修正
SpiderImageFile._open()の最後に(または_open()の後のseek()で)、Image._decompression_bomb_check(self.size)を呼び出します。これは、GifImagePlugin / TiffImagePluginと同様の処理です。オプションとして、_open()でnsam/nrowが正の値であり、妥当な値であることを検証することもできます。
情報提供者:Yazan Balawneh - CyStack Security Team
[pillow_poc_spider_bomb.py](https://github.com/user-attachments/files/30244916/pillow_poc_spider_bomb.py)
影響を受けるパッケージ:
- pip (Python) pillow: 13.0.0より前のバージョンに影響あり、13.0.0で修正済み。