ディレクトリトラバーサルをローカルの仮想環境で実験して学ぶ
はじめに
ルビイちゃん! (は~い!)何が好き? チョコミントよりもディレクトリトラバーサル!
はい
業務でディレクトリトラバーサルの対応をするので、勉強がてらローカルで環境作って試してみました
ディレクトリトラバーサルとは?
ディレクトリトラバーサル(Path Traversal)とは、../
などの特殊文字列を使って、サーバー上の意図しないファイルやディレクトリにアクセスする脆弱性です
たとえば以下のようなPHPコードがあるとします:
<?php
$filename = $_GET['file'];
readfile('/var/www/files/' . $filename);
?>
ここで file=../../../../etc/passwd
などと指定すると、システムファイルを読めてしまう場合があります
作った仮想環境
以下のような構成のローカル環境を用意しました
作成したコードはこちらです
traversal-test/
├── files/
│ └── hello.txt
├── secret/
│ └── flag.txt
├── vulnerable.php
└── secure.php
-
vulnerable.php
:脆弱なファイル読み込み処理 -
secure.php
:安全なファイル読み込み処理(ホワイトリスト方式)
実際に試してみる
vulnerable.php
<?php
$filename = $_GET['file'];
$path = __DIR__ . '/files/' . $filename;
if (file_exists($path)) {
echo nl2br(file_get_contents($path));
} else {
echo "File not found.";
}
?>
なんで脆弱なのか
上記ファイルはユーザー入力をファイルパスにそのまま使っているため、../
による意図しないファイルへのアクセスが可能になるため脆弱です
実際にhttp://localhost:8000/vulnerable.php?file=../secret/flag.txt
でアクセスするとファイルの中身が閲覧できてしまいます
対策してみる
ディレクトリトラバーサルは以下のような対策があげられます
- ホワイトリスト方式
- 許可されたファイル名のみを受け付ける
- 入力が想定通りか明示的に確認できる
- シンプル・堅牢・安全性が高い
- realpath() によるパス制限
- 実際の絶対パスを取得し、許可ディレクトリ内に収まっているかを確認
- ../ などのトリックを正規化で排除可能
- 柔軟性が高く、動的なファイル指定にも使える
- basename() を使ってファイル名だけに制限
- ディレクトリ構造を切り捨て、ファイル名だけを抜き出す
- 簡易的な対策にはなるが、完全ではない
→ 今回は再現用のファイルが限定的だったため、最も安全なホワイトリスト方式を採用しました。
ホワイトリスト方式(secure.php)
<?php
$whitelist = ['hello.txt'];
$filename = $_GET['file'];
if (!in_array($filename, $whitelist, true)) {
http_response_code(403);
exit("Access denied.");
}
$path = __DIR__ . '/files/' . $filename;
echo nl2br(file_get_contents($path));
?>
上記のように、ユーザーが指定できるファイル名を制限することで、安全なファイルアクセスが実現できます。
実際にhttp://localhost:8000/secure.php?file=../secret/flag.txt
でアクセスすると以下のようにアクセスをブロックすることができました
所感
最初は「ファイル名とかをそもそも入力できないようにすればいいじゃん!」って思ったんですが実際のシステムではユーザーIDとか入力する必要とかもあり、そういうのは難しいとchatGPTが教えてくれました
ちなみに実業務でやってるディレクトリトラバーサルの対応は、今絶賛対応中です。果たして無事に終わるのでしょうか・・・
Discussion