FlawOpen

flawopen.com/パストラバーサル/Zip Slip

Zip Slip 脆弱性とは?

Reference page
ELI5

ZIP の内部には ../../outside.txt のような名前を含められます。展開処理がその名前を信頼すると、プロセスの権限が及ぶ範囲で展開先の外へファイルを書き込めます。

見落としやすい理由

展開処理はありふれていますが、信頼できない ZIP や TAR の各エントリ名は作成者が制御します。パスを検証せず outputDir + entry.name と連結すると、アーカイブ内容を通じたディレクトリ脱出が起きます。

VULNERABLE (conceptual)
for entry in zip_file:
    dest = os.path.join(output_dir, entry.name)
    write_file(dest, entry.read())
# entry.name は "../../outside.txt" の可能性がある
字句的な包含確認のみ — 単独では競合状態に対処できません
root = os.path.realpath(output_dir)
os.makedirs(root, exist_ok=True)
for entry in zip_file:
    dest = os.path.realpath(
      os.path.join(root, entry.name)
    )
    try:
        inside_root = os.path.commonpath([root, dest]) == root
    except ValueError:
        inside_root = False
    if not inside_root:
        raise ValueError("Unsafe entry path")
    write_file(dest, entry.read())

この検査は、文字列の接頭辞だけが共通する隣接パスも含め、ルート外への逸脱を防ぎます。新規の非公開ディレクトリに展開し、symlink エントリを拒否してください。既存 symlink や並行したファイルシステム変更への対策には、競合に強い API または保守された展開ライブラリが必要です。

よくある質問

Python のアーカイブ API は異なるパス保護を提供する

上のコード比較は独自の展開ループを示す概念例であり、Python 組み込みの ZipFile.extractall() ではありません。Python のドキュメントによると、ZipFile.extract() と extractall() は絶対パスや .. による展開先からの逸脱を防ごうとしますが、信頼できないアーカイブは事前に検査するよう警告しています。zipfile.Path はメンバー名をサニタイズしないため、呼び出し側で検証が必要です。TAR の展開フィルターは Python 3.12 で追加され、Python 3.14 では既定値が filter="data" になりました。既定値がより緩い可能性のある対応ランタイムでは、このフィルターを明示してください。data フィルターでも、あらゆる危険な動作やリソース枯渇を防げるわけではありません。新しい非公開の一時ディレクトリに展開し、アーカイブサイズ、メンバー数、展開後の合計サイズ、処理時間に上限を設けてください。

最近のアーカイブライブラリなら自動で防げますか?

2018 年にこの手法が広く知られた後、保護を追加したバージョンもありますが、既定でパスを検証しないものもあります。使用中のバージョンの文書を確認し、各エントリを明示的に検証してください。

参考資料

関連項目 パストラバーサル(CWE-22)の仕組みと各言語での防ぎ方 — flawopen.com Node.js path.join() では Path Traversal を防げない PHP include() とユーザー制御パス Python os.path.join() のパストラバーサル対策