こんにちは!新人IT担当者の皆さん、そして日々の開発やインフラ管理でお疲れ様の一般開発者の皆さん。
今回は、Webアプリケーションのセキュリティにおいて、切っても切り離せない「パス・トラバーサル(ディレクトリトラバーサル)」という脆弱性についてお話しします。
「名前からしてなんだか難しそう……」と感じるかもしれませんが、安心してくださいね。今回は私たちの身近にある「家の鍵」や「郵便受け」に例えながら、攻撃がどのように行われ、どうやって守ればいいのかを一緒に優しく紐解いていきましょう!
一歩ずつ、確実に安全なコードを書けるようになっていきましょうね。
—
1. パス・トラバーサルってどんな攻撃?(身近な例えで考えてみよう)
まずは、パス・トラバーサルがどんなものか、イメージを掴むために「マンションの管理人室」を想像してみてください。
あなたはマンションの管理人さんで、住人から「私の部屋の合鍵を貸して!」と頼まれる受付にいます。
安全な仕組みであれば、住人が来たら「〇号室ですね、はいどうぞ」と、その人の部屋の鍵だけを渡すはずですよね。
でも、もし管理人さんがうっかりしていて、こんな風に考えてしまったらどうでしょう?
「住人が『 ../上の階の管理人室の金庫の鍵 』って書いたメモを持ってきたから、言われた通りにその場所を探して渡しちゃおう!」
……これ、大問題ですよね!悪意ある人が「隣の部屋」や「管理人の金庫」の鍵を勝手に指定して、本来持ってはいけないものを受け取ってしまう。これが、まさにパス・トラバーサル(ディレクトリトラバーサル)の正体です。
ITの世界に置き換えると、Webサイトがユーザーからの「このファイルを見せて」という要求をちゃんと確認せず、サーバーの奥深くにあるパスワードファイルや設定ファイルをそのまま見せてしまう現象を指します。
—
2. 攻撃者はどうやってシステムに入り込むの?(メカニズムの解説)
では、実際のWebアプリケーションで、この攻撃がどうやって仕掛けられるのかを見ていきましょう。
例えば、Webサイト上で画像を表示するために、次のようなURLを使っているシステムがあるとします。
https://example.com/view.php?file=cat.jpg
このシステムは内部で、file=に指定されたファイル名を受け取って、サーバーの中にある images/ というフォルダから画像を探し出して画面に表示する仕組みになっています。プログラムのイメージとしてはこんな感じです。
<?php
// ユーザーから受け取ったファイル名を取得
$filename = $_GET['file'];
// 「images/」というフォルダの中にあるファイルを探して読み込む
$filepath = "images/" . $filename;
include($filepath);
?>
一見すると、「images/フォルダの中を見るんだな」と安全そうに見えますよね。
しかし、ここで攻撃者は file= の部分に、次のような特別な文字を混ぜ込んでリクエストを送ります。
https://example.com/view.php?file=../../../../etc/passwd
ここで登場するのが、おなじみの ../(ドット・ドット・スラッシュ)という表現です。これはコンピュータの世界で「一つ上の階層(フォルダ)に戻る」という意味を持っています。
プログラムがこれをどう処理するか追ってみましょう。
1. images/ というフォルダに入る
2. ../ で一つ上のフォルダに戻る
3. もう一度 ../ でさらに上のフォルダに戻る……これを繰り返して、最終的にサーバーの根っこ(ルートディレクトリ)まで戻る
4. そこから、サーバーのパスワードが保存されている秘密のファイル(/etc/passwd など)を指定して開いてしまう!
このように、本来は触られたくない「フォルダの階層(パス)」を自在に行き来(トラバースト)されてしまうことから、パス・トラバーサルと呼ばれているんです。
—
3. なぜ防げないの? 開発者がやりがちな「うっかりミス」
初心者の頃や、急いで開発を進めていると、次のような甘いチェックをしてしまいがちです。
- 「拡張子が
.jpgで終わっていれば大丈夫だろう」とファイル名の後ろだけを確認する - 「ファイル名に
etcという文字が入っていなければセーフだろう」と思い込む
これらは、泥棒に対して「玄関の鍵は閉めたけれど、窓の鍵の締め忘れを確認しなかった」ような状態です。攻撃者は cat.jpg/../../../../etc/passwd のように、表面上は画像っぽく見せかけつつ、中身でこっそり上の階層に移動するテクニックを持っています。ファイル名の検証だけに頼るのは、非常に危険なんですね。
—
4. どうやって守るの? 実践的な防御テクニック
それでは、ここからが本番です!私たちのシステムをパス・トラバーサルから守るための具体的な対策を、二つのアプローチから学んでいきましょう。
対策①:ユーザーからの入力をそのまま信用しない(ファイル名の検証とホワイトリスト)
一番確実な防衛策は、ユーザーが入力した文字列を「そのままファイルパスとして使わない」ことです。
例えば、ユーザーにファイル名を自由に入力させるのではなく、あらかじめ用意された選択肢(IDなど)の中から選んでもらい、プログラム側で安全なファイル名に置き換える「ホワイトリスト方式」を採用しましょう。
どうしてもファイル名を受け取る必要がある場合は、basename()関数などを使って、余計なパス(../ や / など)を綺麗に切り捨てる処理を入れます。
以下は、PHPでの安全な実装例です。
<?php
// ユーザーからの入力を受け取る
$input_file = $_GET['file'];
// 【重要】basename()関数を使って、パスの階層(ディレクトリ構造)をすべて削ぎ落とす
// 例: "../../etc/passwd" -> "passwd" に変換される
$safe_filename = basename($input_file);
// 許可された安全なフォルダのパスと結合する
$filepath = "/var/www/images/" . $safe_filename;
// ファイルが本当に存在するか、読み込んでも大丈夫なものか確認する
if (file_exists($filepath) && is_file($filepath)) {
// 安全にファイルを表示・読み込む処理
readfile($filepath);
} else {
echo "指定されたファイルは見つかりません。";
}
?>
このように、basename() を使うことで、どれだけ ../ をたくさん並べられても、最終的なファイル名だけを抽出して安全なフォルダの中に閉じ込めることができます。
対策②:chroot環境やコンテナによる隔離(多層防御)
アプリケーションのプログラムだけで防ぐのが不安な場合は、インフラ(サーバー)の仕組みで守りを固めます。
家全体に泥棒が入ってきたとしても、貴重品を置いている部屋に「頑丈な二重のセキュリティ扉」があれば安心ですよね。これがchroot(チェールルート)や、Dockerなどのコンテナ技術を用いた隔離です。
chrootを使うと、ある特定のフォルダを「そのプログラムから見た世界の一番上(ルート)」として錯覚させることができます。
たとえプログラムが不正に ../ を使って上の階層に行こうとしても、そこがそのプログラムにとっての「世界の果て」なので、システム全体の重要なファイル(/etc/passwd など)には絶対に手が届かない仕組みになっているのです。
—
5. まとめ:安全なアプリケーション作りに向けて
今回は、パス・トラバーサルの仕組みと、私たちができる対策についてお話ししました。
- パス・トラバーサルは、
../を使って本来見えてはいけない上の階層のファイルにアクセスしてしまう脆弱性。 - ユーザーが入力したファイル名をそのまま信用してパスを組み立ててはいけない。
basename()などの関数を利用して、余計なパスの指定を取り除く(ホワイトリストや入力値検証の徹底)。- サーバー側でもコンテナやchrootを使って、万が一のときに被害を最小限に抑える(多層防御)。
セキュリティ対策は、一度覚えたら終わりではなく、日々の開発の中で「本当にこの入力値は信用しても大丈夫かな?」と一歩立ち止まって考える習慣が何よりも大切です。
焦らず、一歩ずつ安全なコードの書き方をマスターしていきましょうね。応援しています!
コメント