ssecutils
Security

パストラバーサル詳解 - ../攻撃・URLエンコード回避・防御の基本

著者 aki公開 更新 10

パストラバーサルとは

パストラバーサル(Path Traversal)は、別名 ディレクトリトラバーサルとも呼ばれ、ユーザー入力で指定されたファイルパスに ../(親ディレクトリへの遷移)を含めることで、本来アクセスできないはずのファイルを読み書きさせる攻撃です。

OWASP Top 10 2021 では A01:2021 - Broken Access Control の代表例に分類されます。意外と古くから知られている攻撃ですが、ファイルアップロード機能、テンプレートエンジン、ZIP展開処理など、ファイルパスを扱うあらゆる箇所で今でも見つかります。

典型的な脆弱なコード

# 脆弱な例(Python)
@app.route("/files/<filename>")
def serve_file(filename):
    path = os.path.join("/var/www/uploads", filename)
    return open(path).read()  # ← パス検証なし

攻撃者は次の URL を送ります:

GET /files/../../../../etc/passwd
GET /files/..%2f..%2f..%2fetc%2fpasswd     # URLエンコード
GET /files/..%252f..%252fetc%252fpasswd    # ダブルエンコード
GET /files/....//....//etc/passwd          # ../を二重に書く
GET /files/..\..\..\windows\win.ini       # Windows用バックスラッシュ

サーバー側では os.path.join("/var/www/uploads", "../../../../etc/passwd")"/etc/passwd" に解決され、システム全体のファイルが読まれます。Linux の /etc/passwd/proc/self/environ、Windows の win.iniboot.ini が典型的な検証ターゲットです。

検査回避の典型パターン

テクニック狙い
URLエンコード%2e%2e%2f文字列 ../ の単純検査を回避
ダブルエンコード%252e%252e%252f1回デコードしてから検査するコードを回避
UTF-8 オーバーロング%c0%ae%c0%ae/非正規UTF-8シーケンス(旧IIS脆弱性で有名)
16bit Unicode%u002e%u002e%u002fIIS の独自デコードを利用
NULL バイトfile.txt%00.pngC言語系で %00 以降を無視させる(Java 7以前、PHP)
二重 ../....//../ を削除する単純フィルタを回避
絶対パス/etc/passwd../ を使わず直接アクセス

URL エンコードの実際の変換は URL Encoder / Decoder で確認できます。例えば ../%2e%2e%2f、ダブルエンコードで %252e%252e%252f のように二段階で確認できます。

派生攻撃: ZipSlip

ZIP ファイルやTAR を展開する処理でも、エントリ名に ../ が含まれていれば任意のパスへ書き出せます。これは ZipSlipと呼ばれ、2018年に Snyk によって多数のライブラリで脆弱性が報告されました。

# 危険な ZIP のエントリ例
../../../etc/cron.d/malicious     # cron に書き込み
../../../root/.ssh/authorized_keys  # SSH 公開鍵を上書き

対策は ZIP 解凍時に各エントリの解決後パスがベースディレクトリ配下であることを必ず検証することです。

派生攻撃: パス Confusion(テンプレート / Include)

テンプレートエンジンや require/include でユーザー入力をパスに使う場合、パストラバーサルから一段進んだ LFI(Local File Inclusion)RFI(Remote File Inclusion)に発展します。PHP の include $_GET['page'] はその代表で、任意ファイルの読み込み+場合によってはコード実行に直結します。

防御1: 正規化後の許可リスト

最も確実な防御は、「パスを正規化(canonicalize)してから、許可ディレクトリ配下にあるかを確認」するパターンです。

# Python の安全実装例
import os

BASE_DIR = os.path.realpath("/var/www/uploads")

def safe_read(filename: str) -> bytes:
    requested = os.path.realpath(os.path.join(BASE_DIR, filename))
    if not requested.startswith(BASE_DIR + os.sep):
        raise PermissionError("Path traversal detected")
    return open(requested, "rb").read()

ポイントは os.path.realpath() でシンボリックリンク・../ をすべて解決した 絶対パスを取得してから、ベースディレクトリの prefix チェックを行うこと。os.path.join() だけでは ../ 解決が行われないため不十分です。

防御2: ファイル名のホワイトリスト化

そもそも任意の文字列をパスに使わせず、「ID から内部マッピングでファイルパスを引く」設計にすると、パストラバーサルの余地がなくなります。

# 良い設計: IDからDB引きでパスを決定
@app.route("/files/<int:file_id>")
def serve_file(file_id):
    record = db.files.find_one({"id": file_id, "owner": current_user.id})
    if not record:
        abort(404)
    return send_from_directory(UPLOAD_DIR, record["storage_name"])

storage_name はサーバー側で生成した UUID やハッシュにしておけば、ユーザーが任意のパスを指定する余地は一切ありません。

防御3: 抽象化APIの利用

フレームワークが提供する安全なファイル送信関数を使うのも有効です:

  • Flask の send_from_directory(): パストラバーサル検証を内部で実施
  • Express の res.sendFile(): root オプション併用でディレクトリ脱出を防止
  • ASP.NET の VirtualPathUtility.IsAppRelative(): アプリ相対パスのみを許容
  • Rails の send_file: :url_based_filename 等で安全化

自前で open()/fopen() を呼ぶよりも、フレームワーク提供の API を経由するのが安全です。

防御4: 実行ユーザーの権限最小化

どれだけアプリ層で防御しても完璧ではありません。「万一突破されても被害が限定される」ように、アプリ実行ユーザーの権限を最小化します:

  • Web サーバーは nobody や専用ユーザーで動かす(root 厳禁)
  • 必要なファイル以外を chmod 600 で root 所有にする
  • コンテナで動かす(root のままでもホストへの影響は限定的)
  • SELinux/AppArmor で読み取り可能パスを制限
  • chroot やコンテナで /etc/passwd 等にアクセスできなくする

実体験:ファイルアップロード機能でパス指定を意識した話

ファイルのアップロードやダウンロード機能について調べる中で、ファイルパスをユーザーの入力から作る場合には注意が必要だと意識するようになりました。最初は、例えばユーザーが指定したファイル名をそのままディレクトリに結合して、「このフォルダの中のファイルだから大丈夫」と考えてしまいがちでした。

ただ、パストラバーサルについて調べると、../ のような文字列を使って想定していたディレクトリの外側に移動できてしまう可能性があることが分かりました。そこからは、ユーザーから渡されたファイル名をそのままパスとして使うのではなく、上記の正規化後の許可リストファイル名のホワイトリスト化のように、許可するディレクトリの範囲を明確にする必要があると考えるようになりました。特に印象に残ったのは、「ファイル名」という一見単純な入力値でも、サーバー側でそのままパスとして扱うと、システムに対する操作につながってしまうという点です。入力値を単なる文字列として見るのではなく、「その値をサーバーがどう解釈するか」まで考える必要があると感じました。

実装チェックリスト

  • ユーザー入力のパスを直接 open() に渡していないか
  • パスを 正規化(realpath / canonicalize)してから許可ディレクトリ配下か検査しているか
  • URL エンコード・ダブルエンコード・NULL バイトを意識して検査しているか
  • 可能なら「ID → DB → パス」の間接参照に置き換えているか
  • ZIP/TAR 展開時、エントリパスがベース配下か検証しているか
  • ファイル送信はフレームワーク提供の安全関数を使っているか
  • アプリ実行ユーザーの権限を最小化しているか

まとめ

パストラバーサルは、Web 黎明期から存在しながら現在も繰り返し発見される根強い脆弱性です。../ 文字列を単純にブロックするだけでは URL エンコード・ダブルエンコード・NULL バイトなどで簡単に回避されます。

本質的な対策は 「パスを正規化してから許可ディレクトリ配下か検証する」か、そもそも 「ユーザーに任意の文字列を渡させず、ID 経由で間接参照する」設計に切り替えること。組み合わせれば、たとえ検査ロジックに穴があっても被害は限定されます。

URL エンコードの確認には URL Encoder / Decoder、関連リスクは OWASP Top 10 入門 も合わせてご覧ください。

この記事を書いた人
akiサイバーセキュリティ実務者 / エンジニア

サイバーセキュリティ業界で実務に携わるエンジニア。脆弱性・認証・暗号・ネットワークなど、現場で必要になる知識を開発者・運用担当者向けにかみ砕いて解説しています。

運営者・編集方針について

本記事は一般的な情報提供を目的とした解説であり、特定環境での動作・安全性・成果を保証するものではありません。実際の対策の適用は、各自の環境と責任において判断してください。

Tool companion

この記事と一緒に使えるツール

Related reading

関連記事

Security15
CompTIA Security+ 完全ガイド - 難易度・出題範囲・日本語受験SY0-701 の試験形式と5つの出題ドメイン、2024年から始まった日本語受験、支援士・CISSPとの難易度比較、3年ごとの更新の仕組み、平日30分から回せる学習ロードマップを整理します。各ドメインで問われる内容は、本サイトのどの解説記事で補えるかも対応づけました。
Security16
個人情報が漏えいしたときの報告義務 - 速報・確報と本人通知の実務個人データの漏えいで個人情報保護委員会への報告が必要になるのはどんな場合か、速報・確報の期限、確報で書く9項目、本人通知の要否、委託先の免除規定を整理します。よくある「72時間以内」という誤解にも触れます。
Security18
情報セキュリティ10大脅威 2026 - IPAの順位を自社の優先順位に翻訳するIPAの「情報セキュリティ10大脅威 2026」を、順位の丸写しではなく実務の優先順位に落とし込むために読み解きます。前年からの変動、初選出された3位「AIの利用をめぐるサイバーリスク」の中身、各脅威に対応する国内事例、着手順の決め方まで整理します。
Security23
日本国内の主要セキュリティインシデント事例(2020–2025)国内の主要なセキュリティインシデント12件を一覧で俯瞰し、VPN未更新・委託先/グループ経由・弱い認証という繰り返し現れる侵入経路と失敗パターン、日本企業が今すぐ確認すべき対策チェックリストを事例研究として整理します。