ディレクトリトラバーサル

はじめに
本稿では、プログラム内部でファイルを開く際に、外部から受け取った文字列をファイル名の一部として利用することにより、意図しないファイルを操作してしまう脆弱性であるディレクトリトラバーサルについて解説します。
ディレクトリトラバーサルとは
ディレクトリトラバーサルとは、外部から与えられた文字列をアプリケーション内でファイル名の一部として利用するときに発生する脆弱性です。攻撃者がその文字列に「../」などを含めることで、サーバー内で意図していないディレクトリ上のファイルにアクセスしてしまうという問題です。
たとえば、URL内で「image.png」のようにファイル名を指定することで、サーバー上に保存されているそのファイルをダウンロードできるアプリケーションがあったとします。
サーバー上では、ダウンロードするためのファイルは「/www/data」ディレクトリに保存されるようになっています。

ここで、攻撃者がファイル名に「../../etc/passwd」を指定したとします。
本来であれば「/www/data」ディレクトリ内のファイルを返すのですが、攻撃者が指定したファイル名により、返されるファイル名は「/www/data/../../etc/passwd」つまり「/etc/passwd」になってしまい、サーバー上のパスワードファイルが攻撃者に返されることになります。

攻撃者がこのようにディレクトリの操作を行うことで、本来であればアクセスされるべきでないファイルへのアクセスを許してしまう脆弱性あるいは攻撃手法をディレクトリトラバーサルと言います。
ここでは、ファイルのダウンロードを例に示しましたが、公開されていないファイルの閲覧や取得だけでなく、ファイルのアップロード機能やデータの書き込み機能などにディレクトリトラバーサルが発生することで、サーバー上に意図しないファイルが作成されたり、意図しないファイルが改ざんされたりすることもあります。
ディレクトリトラバーサルが発生する原因
ディレクトリトラバーサルは、攻撃者によって与えられた文字列をそのままファイル名の一部として利用しているときに、文字列中に含まれる「../」や「..\」などもそのままファイル名の一部として利用するために発生します。
脆弱性のある例としては、例えば以下のようなコードとなります。
<?php
// 脆弱性のあるコード例
$data_dir = '/www/data/';
if (isset($_GET['filename'])) {
$download_file = $data_dir . $_GET['filename'];
if ($fp = fopen($download_file, 'r')) {
// 対象ファイルを読み込む
...
3行目でデータが保存されるディレクトリ「/www/data/」を定義し、5行目では filename というパラメータで渡された値をそのディレクトリ名と連結したフルパスを $download_file という変数に保存しています。その後、6行目でfopen関数を用いて実際にそのファイルを開いています。
攻撃者はfilenameパラメータに「../」や「..\」などの相対ディレクトリを表す文字列を含めることで、この5行目の部分で任意の場所のファイルが指定されることになります。
実際のアプリケーションでは、fopen関数だけでなくファイル名を取り扱うあらゆる箇所が攻撃される可能性があります。
アプリケーションの実装上、ファイル名を示すデータがエンコードされている場合には、「../」などが含まれていないか検査している場合でもデータの検査とデコードとの順序に気を付ける必要があります。
以下の脆弱性のあるコード例は、$filenameという変数にURLエンコードされたファイル名が格納されているという想定です。
<?php
// 脆弱性のあるコード例
// $filenameには '%2E%2E%2F%2E%2E%2Fetc%2Fpasswd' の
// ようにエンコードされたデータが含まれているとする
$data_dir = '/www/data/';
if (strpos($filename,'../') === false) {
$download_file = $data_dir . urldecode($filename);
if ($fp = fopen($download_file, 'r')) {
// 対象ファイルを読み込む
...
6行目で $filename に「../」という文字列が含まれていないか検査していますが、この時点では $filename の内容はURLエンコードされたままとなっており、7行目でデコードしていますので、このようなコードでは検査を行っているつもりでも簡単にバイパスされてしまいます。
ディレクトリトラバーサルの対策
ディレクトリトラバーサルの対策としてもっとも効果的な方法は、あらかじめアクセス可能なファイルの一覧を定義しておき、ユーザーによって指定されたファイルがその一覧に合致するものであったときにだけアクセスするという方法です。ファイル名を攻撃者がコントロールすることができなくなりますので、意図しないサーバー上のファイルへのアクセスを原理的に防ぐことができます。
<?php
$data_dir = '/www/data/';
$arrow_files = array('image1.png', 'image2.png', 'image3.png');
if (isset($_GET['filename'])) {
if (in_array($_GET['filename'], $arrow_files)) {
$download_file = $data_dir . $_GET['filename'];
if ($fp = fopen($download_file, 'r')) {
...
プログラムからアクセス可能なファイル名をあらかじめ決定できない場合には、与えられたファイル名からbasename関数などを用いてディレクトリ名部分を確実に取り除きます。
basename関数は、引数として与えられた文字列のうち、ディレクトリ名の部分を取り除きファイル名に相当する部分のみを返す関数です。
<?php
$data_dir = '/www/data/';
if (isset($_GET['filename'])) {
$download_file = $data_dir . basename($_GET['filename']);
if ($fp = fopen($download_file, 'r')) {
...
まとめ
外部から与えられた文字列をファイル名の一部として利用する場合に、相対ディレクトリを示す「../」や「..\」が入力されると、意図しないファイルへのアクセスを許すことになります。そのような脆弱性はディレクトリトラバーサルと呼ばれます。本記事ではディレクトリトラバーサルの脆弱性が発生するコード例や対策手法を解説しました。
下の表では、実装しようとする機能に対して発生する可能性のある脆弱性や、その機能に対して発生し得る攻撃手法などをまとめています。表の左側にはWebアプリケーションで実装されるそれぞれの機能、表の右側にはその機能を実装するときに発生しがちな代表的な脆弱性や攻撃手法を掲載しています。
どのような機能の実装時にどのような脆弱性を作りこんでしまうのかの参考にしてください。
| 実装する機能 | 発生する脆弱性や関連する攻撃手法 | |
|---|---|---|
| ログイン処理 | 認証機能の突破、セッションに関連する問題 | |
| ユーザーごとの機能 | 認可制御の問題、強制ブラウズ | |
| データの 入出力 |
全体 | パラメータ操作、コードインジェクション |
| HTMLの出力 | クロスサイト・スクリプティング(XSS) | |
| データベースへの アクセス |
SQLインジェクション | |
| ファイルへのアクセス | ディレクトリトラバーサル | |
| 外部コマンドの実行 | OSコマンドインジェクション | |
| サーバー上で状態が変化する処理 | クロスサイト・リクエスト・フォージェリ(CSRF) | |
| エラー処理 | エラーメッセージによる情報漏えい | |
| サーバーからのリクエスト発行 | サーバーサイド・リクエスト・フォージェリ(SSRF) | |
| 正規表現の使用時 | 正規表現によるDoS(ReDoS) | |
| アプリケーション全体 | レースコンディション、バックドアやデバッグモードの残存 | |

