ディレクトリトラバーサルとは?攻撃の仕組みと正規化後検証による実装対策を解説
ディレクトリトラバーサルは、外部から渡された文字列をそのままファイルパスに連結する実装で成立します。攻撃者は「../」のような相対パス記法を差し込み、公開ディレクトリの外にある設定ファイルや認証情報を読み出す。古典的な脆弱性ですが件数は減っておらず、2025年のCWE Top 25でCWE-22は6位、2026年6月に公開されたCVEの集計では最も多く観測された分類でした。この記事では、文字列除去フィルタが破られる理由、正規化してベースディレクトリと前方一致で照合する実装手順、Go 1.24の走査耐性APIまでを手を動かす順に整理します。WAFで塞げる範囲と塞げない範囲の線引きも条件つきで示します。
まとめ:ディレクトリトラバーサル対策で最初に直すべき実装箇所
結論から言えば、直すべき場所はひとつに絞れます。外部入力をファイルパスに連結している処理を洗い出し、「デコードを完了させる→正規化する→ベースディレクトリの配下に収まるかを前方一致で照合する→開く」という順序に統一してください。「../」を除去する方式は採用しません。二重エンコードや文字コードの表現ゆれ、除去後に復元される並びで破られるためです。
もう一段強い手は、ファイル名を外部から受け取らない設計へ変えること。識別子だけを受け取ってサーバ側の表で実ファイルへ対応づければ、パス文字列を検証する必要そのものが消えます。Go 1.24で追加されたos.Rootのように、ディレクトリ外への参照を処理系の側で拒む仕組みも選べるようになりました。WAFや権限設定は緩和策で、コード修正を置き換えるものではありません。
ディレクトリトラバーサルの定義とパストラバーサル・LFIとの呼び分け
用語の整理から。ディレクトリトラバーサルとパストラバーサルは同じ現象の別名で、日本語圏では前者、英語圏の脆弱性データベースでは後者の表記が優勢です。LFI(Local File Inclusion)はPHPのinclude系関数のように読み込んだファイルがそのまま実行される文脈を指し、単なる読み出しより影響が一段重くなります。
ファイル名を外部入力から受け取る処理が相対パス記法で破綻する原理
成立条件は単純です。アプリケーションが「公開用ディレクトリのパス」と「リクエストで指定されたファイル名」を文字列連結し、その結果を開く。この構造があれば脆弱性が生まれます。帳票ダウンロードのfileパラメータ、テンプレート名の指定、多言語サイトの言語ファイル読み込みが典型的な発生箇所です。
OSのパス解決は連結後の文字列を左から評価し、「..」が現れるたびに一階層戻ります。アプリ側が公開フォルダの配下にあるつもりでも、実際の解決先はその外側。読み出される代表例は、DB接続情報を含むアプリ設定ファイル、クラウドの資格情報、秘密鍵、ソースコードです。ソースが読めれば次の攻撃の手がかりにもなります。
読み取り型と書き込み型で変わる被害の深さ・復旧コストの具体的な差
同じCWE-22でも、読み取り側と書き込み側では深刻度が変わります。読み取り型は情報漏洩で止まり、対応は漏洩範囲の特定と資格情報のローテーションが中心。書き込み型は任意の場所にファイルを作成・上書きできるため、起動スクリプトの書き換えやWebシェル設置を経てリモートコード実行へつながります。
復旧コストの差は明確です。書き込み型を踏まれると、ホスト上のどのファイルが改変されたかを確定できず、原則としてサーバの再構築が前提になります。2026年6月にCISAの悪用実績カタログへ追加されたCVE-2026-20262も、この書き込み型でした。改修の優先順位を決めるとき、まず読み書きのどちら側かを確認してください。
CWE-22の定義と2025年Top 25で6位という発生頻度の実態
CWE-22の正式名称は “Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)”、日本語では「制限付きディレクトリへのパス名の不適切な限定」と訳されます。OWASP Top 10の2021年版では独立項目ではなく、A01「アクセス制御の不備」に含まれる位置づけ。
件数の実態も数字で押さえておきます。CISAとMITREが2025年12月11日に公表した2025 CWE Top 25は、2024年6月から2025年6月までのCVE 39,080件を分析したもので、CWE-22は6位でした。前年の5位から1つ下降。ただし順位の低下は沈静化を意味しません。Recorded Futureが集計した2026年6月のCVEでは、CWE-22が最も多く観測された分類です。
単純な文字列フィルタをすり抜ける4系統の回避手口と実際の侵入経路
対策として最初に思いつくのが「入力から『../』を消す」処理です。この方式が実務で破られる系統を4つに分けます。いずれも原因は同じで、検証対象が「文字列の見た目」であって「最終的に解決されるパス」ではありません。
二重URLエンコードとUTF-8オーバーロングによるフィルタ回避
URLデコードの回数が、Webサーバとフレームワークとアプリ自身の間でずれると回避が成立します。「%252e%252e」は一度デコードすると「%2e%2e」、もう一度で「..」に戻る。アプリが一度だけデコードして検証し、その後にライブラリが再度デコードすれば、検証時点で無害に見えた文字列が最終的に相対パスへ変わります。
文字コード側の抜け道もあります。UTF-8のオーバーロング符号化を使い、本来1バイトで表す「.」を複数バイトで表現する手口です。Apache HTTP Server 2.4.49系で見つかったCVE-2021-41773とCVE-2021-42013のパストラバーサルも、パス正規化の修正漏れとエンコード処理の組み合わせで成立しました。教訓は一つ。デコードを完全に終えてから検証し、検証後に再デコードしない構造にすることです。
Windows環境の逆スラッシュ・8.3形式名・代替データストリーム
Windows上で動くアプリでは、Unix系を前提にした文字列ルールが通用しません。区切り文字として「\」も有効なため、「/」だけを弾くフィルタは無効化されます。固有の抜け道も重なります。
- 8.3形式の短縮名(PROGRA~1 など)で、長い名前を対象にした拒否リストを迂回する
- 代替データストリーム(ファイル名の後にコロンとストリーム名を付ける記法)で拡張子判定を迂回する
- 末尾のドットや空白がファイルAPI側で除去され、拡張子チェックの結果と実際に開かれる対象がずれる
個別に潰すと、OSごとの文字列ルールを網羅する終わりのない作業になります。列挙して弾く方式そのものを捨てる判断が要ります。
Zip Slipとtar展開で起きる書き込み側トラバーサルの成立条件
アーカイブの展開処理は、書き込み型トラバーサルの温床です。ZIPやtarのエントリ名には相対パスをそのまま格納でき、展開時に出力先と単純結合すると外へ書き出される。2018年にSnykがZip Slipとして整理し、多数の言語のライブラリが影響を受けました。
言語側の対応も進んでいます。PythonではPEP 706によりtarfileへ抽出フィルタが導入され、3.12ではfilter未指定時にDeprecationWarningが出て、3.14からは安全側の ‘data’ フィルタが既定になりました。既定に頼れないバージョンなら、展開前に各エントリの解決先が出力先の配下に収まるか1件ずつ照合し、シンボリックリンクのエントリは拒否してください。
シンボリックリンクとマウント経由でベースディレクトリを脱出する経路
文字列としてはベースディレクトリの配下に見えても、途中にあるシンボリックリンクの実体が外を指していれば脱出されます。ここで効くのが、使っている「正規化」関数がリンクを解決するかどうかの差。JavaのPathでいえばnormalizeは文字列操作にすぎずリンクを解決しませんが、toRealPathは解決します。両者を取り違えた実装は検証を素通りします。
もう一段厄介なのがTOCTOU(検査時と使用時の差)です。検証を通した直後、開く前にリンクを差し替えられると検証結果は無意味になる。文字列検証では原理的に塞げないため、後述する走査耐性APIへ寄せるのが現実的な解です。
正規化してから前方一致で検証する実装手順と主要言語別のAPI選定
ここからは修正の手順です。順序の固定が要点で、どの言語でも構造は変わりません。既存コードもこの4段階のどこが欠けているかで診断できます。
「../」除去フィルタが失敗する理由と検証順序を固定する考え方
除去方式が破られる最小の例が「….//」です。この文字列から「../」を一度だけ取り除くと、残った文字が結合して「../」が復元される。再帰的に除去すればこの例は防げますが、前節のエンコードやWindows固有の記法までは追いきれません。除去ではなく判定へ切り替えます。
- URLデコードとパーセントデコードを完了させ、以降は再デコードしない
- ベースディレクトリと結合したうえで、シンボリックリンクを解決する正規化関数へ通す
- 正規化後の絶対パスが、正規化済みのベースディレクトリと区切り文字込みで前方一致するか判定する
- 一致した場合のみ開く。一致しなければ理由を出し分けず一律で拒否する
3段階目で区切り文字まで含める理由は、単純な前方一致だと /var/www/pub を許可したときに /var/www/public_backup まで通るためです。許可リストの考え方は入力バリデーションの設計と共通しており、パス以外の入力にも同じ判断が使えます。
Java・Python・Node.js・PHPの正規化APIと前方一致判定の書き方
言語ごとに、リンクを解決する正規化APIと判定関数の組み合わせを整理します。中央列の関数はいずれもシンボリックリンクを解決する側で、文字列操作だけの関数とは区別してください。
| 言語 | 正規化に使う関数 | 配下判定に使う関数 |
|---|---|---|
| Java | Path.toRealPath() |
Path.startsWith() |
| Python | Path.resolve() |
Path.is_relative_to() |
| Node.js | fs.realpath() |
区切り込みの前方一致 |
| PHP | realpath() |
str_starts_with() |
| Go | filepath.EvalSymlinks |
strings.HasPrefix |
注意点が2つあります。realpath系は対象が存在しないとfalseやnullを返す実装が多く、書き込み側では親ディレクトリを解決してから判定する必要がある。もう1つは、ベース側も必ず正規化しておくことです。設定に相対パスやリンクが混ざったまま比較すると、正しい要求まで拒否されます。
Go 1.24のos.Rootなど走査耐性APIで根本から封じる選択
検証を書かずに済ませる方向も選べます。Go 1.24ではos.Rootとos.OpenRootが標準ライブラリへ追加され、開いたディレクトリの配下に閉じたファイル操作だけを行える。相対パスの解決もシンボリックリンクの追跡もroot内に制限されるため、TOCTOUを含めて処理系の側で拒まれます。
Goの公式ブログは、固定ディレクトリと外部由来のファイル名をfilepath.Joinで結合しているコードはos.Rootへ置き換えるべきだと明示しています。アーカイブ展開はその代表例。同種の仕組みはLinuxのopenat2(RESOLVE_BENEATHフラグ)やJavaのSecureDirectoryStreamにもあります。新規実装なら、文字列検証を自作するより先にこちらを検討してください。
識別子と実ファイルを対応づける許可リスト方式の設計とその運用負荷
最も確実なのは、パス文字列を外部から受け取らない設計です。リクエストでは識別子だけを受け取り、サーバ側の表で実ファイルへ対応づける。帳票ダウンロードなら、ファイル名の代わりに文書IDを渡し、閲覧権限を確認してから内部でパスを組み立てます。
副次的な利点もあります。パス検証が不要になるうえ、認可判定が同じ場所に集まるため権限漏れのレビューが1箇所で済む。増える運用負荷は対応表の管理だけです。テンプレートや言語ファイルのように候補が有限で静的な対象なら、定数の配列に列挙するだけで足ります。
診断ツールとWAFで塞げる範囲・塞げない範囲を分ける多層防御の設計
コードを直したうえで、抜けを見つける手段と被害を狭める手段を重ねます。各手段の限界を把握しないと「導入したから安全」という誤った完了判断につながります。
SASTで拾えるパターンとDAST・IASTが担う実行時の確認範囲
SASTはソースコードを解析し、リクエスト由来の値がファイル操作関数へ到達する経路を追います。データフロー解析を備えた製品ならCWE-22の典型形は検出できますが、フレームワークのルーティングやDIコンテナ経由の値の流れを解釈できないと取りこぼしが出る。DASTは走査文字列を送って応答の差分から判定する手法で、認証後の画面や複数ステップのフォームの奥には到達しにくい。
IASTは実行時にアプリ内部を計測し、実際に開かれたパスを観測するため誤検知が少なく、書き込み側も捕捉できます。守備範囲と導入コストの違いはSASTとDAST・SCAの使い分けで整理しました。単独で完結する手段はなく、CIに組み込むSASTとリリース前のDASTを組み合わせる構成が現実的です。
WAFは緩和であって修正ではないという線引きと運用上の正しい扱い
WAFはHTTP層で既知の攻撃パターンを弾きます。走査文字列のシグネチャは各製品が標準で持つため、導入直後から一定の効果は出る。ただし前述したエンコードのゆれと正規化の差を突かれると回避されうるうえ、ファイル名に「..」を含む正当なリクエストを誤って落とす副作用もあります。WAFの仕組みと製品選定の観点でも、位置づけは境界での緩和です。
使いどころは2つに限定しましょう。修正版のリリースまでの時間稼ぎと、コード改修が現実的でないレガシーシステムの延命。「WAFルールを追加したので対応済み」という報告は受け取らないでください。
実行ユーザ権限の絞り込みとプロセス分離で被害範囲を限定する設定
侵入後の被害を狭める層も用意します。実行ユーザから設定ファイル・秘密鍵・ソースコードの読み取り権限を外すのが第一歩。コンテナなら不要なディレクトリをイメージに含めず、書き込みが不要なボリュームは読み取り専用でマウントします。
設定と実装の抜けを外から確かめる工程も要ります。当社では脆弱性診断・セキュリティ診断として、パス操作を含むファイル入出力まわりの検証を実施しています。認証後の画面や管理系機能は自動走査では到達しにくく、手動確認が効く領域です。
修正着手の優先順位と対策を積み増さずに済ませるための判断条件と失敗例
既存システムを抱えていると、すべてを一度には直せません。どこから手を付け、どこで手を止めるかを条件つきで示します。
最優先で直す箇所=外部入力を連結してファイルを開くコードの特定
洗い出しは機械的に進められます。ファイルを開く・読む・書く・削除する関数をリポジトリ全体から拾い、引数にリクエスト由来の変数が到達するものだけを残す。この段階は過検出でかまいません。残った箇所を次の順で処理します。
- 書き込み・アーカイブ展開・アップロード保存先の決定に関わる箇所
- 認証なしで到達できる読み取り処理
- 認証後に到達する読み取り処理
- 管理画面や社内向けバッチの処理
1番を最優先にする理由は、リモートコード実行まで一直線につながり、復旧にサーバ再構築を要するためです。読み取り型が3件残っていても、書き込み型が1件あるならそちらを先に閉じてください。
WAFルール追加だけで完了と判断した改修が後日破綻する具体的な条件
次の3条件のいずれかに当たるなら、WAFのみでの完了判断は誤りだと断言できます。第一に、書き込み型である場合。上書き1回でリモートコード実行へ到達しうる以上、ミスの許容度がありません。第二に、WAFを経由しない到達経路が残っている場合で、内部APIの直接呼び出し、バッチ処理、VPN内からの管理画面アクセスが該当します。
第三は、アーカイブ展開のようにHTTPリクエストの文字列として攻撃が現れない場合。ZIPを受け取る機能では走査文字列がバイナリ内側のエントリ名にあり、WAFはこれを検査できません。3条件のどれにも当たらず、かつ修正リリースの日程が確定している場合に限り、一時的な緩和として認めます。
対策が過剰投資になる場面と、そこで追加実装を見送ると決める基準
逆に、追加の対策を入れない判断も明確にしておきます。パス文字列を外部から一切受け取らない実装、つまり識別子方式・固定パス・フレームワーク管理下のテンプレート解決に、さらに検証層を重ねる価値はありません。二重に検証すると、どちらが正なのかが後任に伝わらず、改修時に片方だけ更新されて穴が開く。検証は1箇所に集約してください。
優先度を落としてよい場面もあります。社内限定のツールで、認証が必須かつ利用者全員が対象ファイルの読み取り権限をもともと持つ場合、読み取り型の走査は権限昇格になりません。ここは後回しにできます。ただし書き込み型はこの例外に含めず、例外なく直す線で運用します。
CVE-2026-20262に見る、認証済みでも成立する攻撃への備え
2026年の実例を1件挙げます。Cisco Catalyst SD-WAN Managerで見つかったCVE-2026-20262は、CWE-22に分類されるパストラバーサル。認証済みのリモート攻撃者が基盤OS上に任意のファイルを作成または上書きできるというものでした。CISAは2026年6月15日に悪用実績カタログへ追加し、拘束的運用指令22-01に基づく連邦機関の是正期限を6月29日に設定しています。
読み取るべき示唆は、「認証の内側だから低リスク」という前提を置かないこと。管理画面や社内向け機能ほど入力検証が甘くなりがちで、認証情報の窃取や内部犯行を想定すれば認証は障壁になりません。優先順位の4番に置いた管理画面も、着手しないのではなく順番が最後になるだけです。
よくある質問
ディレクトリトラバーサルの実装対策で、開発現場から問い合わせの多い論点をまとめました。
ディレクトリトラバーサルとパストラバーサルは違うものですか?
同じ脆弱性を指す別名で、技術的な違いはありません。日本語の解説では「ディレクトリトラバーサル」、CVEやCWEなど英語圏の脆弱性データベースでは「Path Traversal」の表記が使われます。社内資料で用語を統一するなら、脆弱性情報を照合する機会が多い現場ほどCWE-22という番号で管理するのが確実。読み込んだファイルが実行されるLFIは、影響範囲が一段重い別カテゴリとして区別します。
「../」を除去するだけでは不十分なのはなぜですか?
検証対象が最終的に開かれるパスではなく、入力文字列の見た目にすぎないためです。「….//」のように、除去処理そのもので攻撃文字列が復元される並びも作れます。二重URLエンコード、Windowsの逆スラッシュや短縮名、途中のシンボリックリンクにも対応できません。デコード完了後に正規化し、ベースディレクトリと前方一致するかを判定する方式へ切り替えてください。
WAFを導入していればコードの修正は後回しにできますか?
書き込み型であれば後回しにはできません。任意ファイルの上書きは1回成立するだけでリモートコード実行につながり、復旧にはサーバ再構築が必要になります。読み取り型で、かつWAFを経由しない到達経路がなく、修正リリースの日程が確定しているケースに限り一時的な緩和として許容できる。アーカイブ展開時のトラバーサルは走査文字列がバイナリの内側にあるため、そもそもWAFの検査対象になりません。
静的ファイルの配信をWebサーバに任せていれば安全ですか?
アプリケーション側でパスを組み立てていないなら、リスクは大きく下がります。ただしWebサーバ自身の実装にも同種の脆弱性は見つかっており、Apache HTTP Server 2.4.49系のCVE-2021-41773がその一例でした。配信をミドルウェアへ寄せる判断は妥当ですが、バージョン管理と更新適用の運用が前提。リバースプロキシとオリジンで正規化仕様が異なる構成も、差分を突かれる余地を残します。
アップロード機能にもディレクトリトラバーサルの対策は要りますか?
必要です。むしろ優先度は読み取り側より高くなります。クライアントから送られてくるファイル名は信用できない入力で、そこに相対パスを含めれば保存先を公開ディレクトリの外へ動かせる。対策は、受け取った名前をそのまま保存名に使わず、サーバ側で生成した識別子を保存名にすることです。元の名前は表示用メタデータとして持たせ、パスの組み立てには使いません。
関連記事
- Apache HTTP Serverのパストラバーサル脆弱性(CVE-2021-41773):正規化漏れとエンコードで成立した実際のCVE事例
- 入力バリデーションとは:許可リスト設計の考え方と防げる攻撃の線引き
- SSRFとは:外部入力をそのまま参照先に使って成立する脆弱性の対処
- SASTとは:静的解析でCWE-22の典型形を継続的に検出する方法
- RASP(実行時アプリケーション自己保護)とは:実行時のファイル操作を観測して遮断する層の導入判断