圧縮爆弾によるサービス拒否

展開処理での無制限のリソース消費

説明

展開Readerの出力をサイズ制限なしでコピーすると、小さな入力が非常に大きな出力に広がり、CPU、メモリ、ディスクを枯渇させるおそれがあります。

想定される影響

  • サーバーのリソース枯渇やプロセスの障害が発生する可能性があります。
  • アップロードやアーカイブ処理がサービス拒否攻撃の入口になるおそれがあります。

対処方法

  • 実際の展開出力に上限を設け、超過したデータを拒否してください。io.LimitReader は上限に達してもEOFを返すため、サイズ超過を別途判定する必要があります。
  • 宣言された非圧縮サイズだけを信頼せず、実際に読み取ったバイト数を確認してください。アーカイブ全体の出力サイズ、エントリ数、処理時間も制限してください。
  • Readerの作成・コピーのエラーを処理し、失敗した処理の部分出力を破棄してください。

例

エラーを返す関数内部の抜粋です。src は io.Reader、dst は一時出力で、compress/gzip、io、fmt を使います。呼び出し元はエラー時に部分出力を破棄する必要があります。上限の10 MiBは例で、超過を確認するために最大1バイトを追加で読みます。

変更前

go
r, _ := gzip.NewReader(src)
_, _ = io.Copy(dst, r)

変更後

go
const maxExpanded int64 = 10 << 20
r, err := gzip.NewReader(src)
if err != nil {
    return err
}
defer r.Close()
n, err := io.Copy(dst, io.LimitReader(r, maxExpanded+1))
if err != nil {
    return err
}
if n > maxExpanded {
    return fmt.Errorf("expanded data exceeds limit")
}
return nil

解説:

  • 変更前: 展開Readerから上限なしの io.Copy や io.CopyBuffer で最後までコピーすると、小さな入力が膨張し、CPU、メモリ、ディスクを枯渇させるおそれがあります。
  • 変更後: エラーを処理し、実際の出力サイズを確認します。上限を超えるとエラーを返すため、呼び出し元で一時出力を破棄する必要があります。

参考資料