こんにちは!システム開発やインフラの管理、本当にお疲れ様です。
新人のIT担当者として日々奮闘していると、セキュリティのニュースを見るたびに「自分が書いたコードや管理しているサーバーは大丈夫かな…」と、ドキッとしちゃいますよね。
セキュリティの世界って、専門用語が多くて最初はチンプンカンプンになりがちです。でも大丈夫です!一歩ずつ、身近な例えから紐解いていけば、誰でもしっかりと安全なシステムを作れるようになりますよ。
今回は、Webアプリケーションの代表的な弱点の一つである「パストラバーサル(ディレクトリトラバーサル)」について、お話ししていきますね。
—
1. パストラバーサルってなに? 身近な「家の鍵」で考えてみよう
まずは、パストラバーサルがどんな攻撃なのか、私たちの日常生活に置き換えてイメージしてみましょう。
皆さんが住んでいるマンションやアパートを想像してください。自分の部屋の玄関のドアには鍵がついていますよね。通常、あなたは自分の部屋の鍵を使って中に入り、リビングや寝室でリラックスします。
ここで、もし「隣の部屋のクローゼットの中身が見たい!」と思ったとします。普通なら絶対にそんなことはできませんよね。なぜなら、管理人がきちんと部屋ごとに鍵を分け、隣の部屋へ自由に行き来できないようにしているからです。
ところが、もしアパートの設計や管理がボロボロだったらどうでしょう?
玄関の鍵を開けて廊下に出たとき、変な構造になっていて、ちょっとドアノブをガチャガチャと特殊な動かし方をしたら、「隣の部屋の合鍵」が偶然手に入ってしまったり、隣の部屋との壁にぽっかりと穴が開いていたりしたら……? 恐ろしいですよね。隣の人のプライベートな荷物や通帳が丸見えになってしまいます。
この「本来は入れないはずの場所(他の部屋)へ、建物の構造の隙をついて侵入してしまう」という現実のトラブルを、そのままデジタル世界(Webシステム)に置き換えたものが、パストラバーサル攻撃なんです。
—
2. Webの世界ではどうやって起きるの? 攻撃のメカニズム
Webアプリケーションを作るとき、ユーザーからのリクエストに応じてサーバーの中にある画像やテキストファイルを表示する機能を作ることはよくありますよね。
例えば、ユーザーが選んだ写真を表示するプログラムがあるとします。
https://example.com/download.php?file=photo1.jpg
このとき、裏側のプログラム(PHPなど)は、次のような処理をしていることが多いです。
<?php
// 基本となるフォルダの場所を決める
$base_dir = "/var/www/uploads/";
// ユーザーが指定したファイル名を受け取る
$file_name = $_GET['file'];
// 基本フォルダとファイル名をくっつけて、ファイルのフルパスを作る
$file_path = $base_dir . $file_name;
// ファイルを読み込んで画面に出力する
echo file_get_contents($file_path);
?>
一見すると、何の問題もなさそうに見えますよね。「photo1.jpg が指定されたら、/var/www/uploads/photo1.jpg を開くんだな」と、ごく自然な仕組みです。
悪意あるリクエストの正体
しかし、ここで攻撃者が次のような意地悪なリクエストを送ってきたらどうなるでしょうか?
https://example.com/download.php?file=../../../../etc/passwd
この ../ という記号を見たことはありますか?
これはコンピュータの世界で「一つ上の階層(フォルダ)に戻る」という意味のコマンドです。
プログラムは、私たちが意図しないようなチェックをサボっていると、次のようにファイルを結合してしまいます。
1. 基本フォルダ:/var/www/uploads/
2. ユーザーの入力:../../../../etc/passwd
3. くっついた結果:/var/www/uploads/../../../../etc/passwd
コンピュータは賢いので、../ があると「じゃあ一つ上のフォルダに戻ろう」と、どんどん上の階層へ遡っていきます。
その結果、本来の保存場所である /var/www/uploads/ を飛び出して、最終的にLinuxサーバーの心臓部にある機密ファイル(パスワード情報が記載された /etc/passwd など)にたどり着いてしまうのです!
これが、パストラバーサル(ディレクトリトラバーサル)の恐ろしい仕組みです。
—
3. 開発者がやりがちな「危ない落とし穴」
新人の開発者や、少し急いでシステムを作ったときにやってしまいがちな、よくあるミスをいくつか見ておきましょう。自分たちのコードに当てはまっていないか、チェックしてみてくださいね。
落とし穴その1:「ファイル名だけ見ていれば大丈夫」という思い込み
「拡張子が .jpg だから安全だろう」とか、「アルファベットだけだから大丈夫だろう」と、ファイル名の文字種だけをチェックして、../ のようなパス操作文字の入力をスルーしてしまうケースです。文字のバリデーション(入力値検証)だけでは、パストラバーサルは防げません。
落とし穴その2:ベースディレクトリの概念がない
サーバーのルートディレクトリからの相対パスや絶対パスをそのまま受け入れてしまうと、どこでも好きなファイルを指定できてしまいます。
—
4. しっかり対策しよう! 安全なコードの書き方
「じゃあ、どうやってこの泥棒を防げばいいの?」という話ですよね。
ここからは、実務でそのまま使える具体的な防御テクニックを見ていきましょう!一歩ずつ、確実にマスターしていきましょうね。
対策1:ユーザーからの入力をそのままファイルパスに使わない(ブラックリストよりホワイトリスト)
一番確実なのは、ユーザーに自由なファイル名を入力させないことです。
「ファイル名を直接指定させる」のではなく、「データベースに保存されたID(数字など)を指定させ、サーバー側で正式なファイル名をルックアップする」方法を取りましょう。
どうしてもファイル名を使いたい場合は、英数字だけに制限し、../ などの記号を徹底的に排除(あるいはエラーに)します。
対策2:PHPの basename() 関数を使う
ファイル名からディレクトリのパス成分(/ や \ や .. など)を綺麗に削ぎ落として、純粋な「ファイル名だけ」を取り出してくれる便利な関数があります。PHPなら basename() がそれにあたります。
安全な実装のサンプルコードを見てみましょう。
<?php
// 基本となる安全なフォルダの場所
$base_dir = "/var/www/uploads/";
// ユーザーからの入力を受け取る
$raw_input = $_GET['file'];
// 【超重要】basename() 関数を使って、余計なパスや記号(../など)を強制的に切り捨てる!
$safe_file_name = basename($raw_input);
// 安全に加工されたファイル名とベースディレクトリを結合する
$file_path = $base_dir . $safe_file_name;
// 実在するファイルかどうか、また本当にベースディレクトリ内のものかを厳密にチェック
if (file_exists($file_path) && is_file($file_path)) {
// ファイルを読み込んで出力
echo file_get_contents($file_path);
} else {
// ファイルが存在しない、または不正なアクセスの場合はエラーを返す
header("HTTP/1.0 404 Not Found");
echo "お探しのファイルは見つかりません。";
}
?>
対策3:realpath() 関数で最終的な着地点をチェックする
さらに強固に守るため、PHPの realpath() という関数を使って、「実際にそのファイルが存在する場所の絶対パス」を割り出し、それが想定しているベースディレクトリから始まっているかをプログラムで厳密に比較する手法もプロの現場ではよく使われます。
<?php
$base_dir = "/var/www/uploads/";
$user_input = $_GET['file'];
// 指定されたファイルパスの「実際の絶対パス」を取得する(存在しない場合は false になる)
$real_path = realpath($base_dir . $user_input);
// realpath が取得でき、かつ、そのパスが $base_dir で始まっているかをチェック!
// (strpos が 0 番目に見つかる = ベースディレクトリの中に収まっているということ)
if ($real_path && strpos($real_path, $base_dir) === 0 && is_file($real_path)) {
// 安全なのでファイルを表示
echo file_get_contents($real_path);
} else {
// 範囲外へのアクセスや不正なファイルならブロック!
header("HTTP/1.0 403 Forbidden");
echo "アクセスが拒否されました。";
}
?>
このコードでは、仮に攻撃者が ../../etc/passwd と入力しても、realpath() はシステムの奥深くにあるそのファイルの本当の場所を計算し返しますが、先頭が /var/www/uploads/ から始まらないため、強力に弾き返すことができます。これぞ鉄壁の防御ですね!
—
5. まとめ
今回は、パストラバーサル(ディレクトリトラバーサル)の仕組みと、その防ぎ方についてお話ししました。
- 攻撃のイメージ:鍵の隙をついて、本来入れない隣の部屋(機密ファイル)を覗き見されるようなもの。
- 原因:ユーザーからの入力を信用しすぎて、そのままファイルパスとして結合してしまうこと。
- 対策:
basename()で余計なパスを削ぎ落としたり、realpath()で移動先の範囲を厳しくチェックしたりする。
セキュリティの対策は、最初は難しく感じるかもしれませんが、一度正しいパターンを覚えてしまえば、どんなシステムを作る時にも応用できる強力な武器になります。
「どうしてこのコードを書く必要があるのかな?」という理由を一つずつ理解しながら、安全で信頼される素敵なシステムを一緒に作っていきましょうね!応援しています!
コメント