2014年1月10日金曜日

Windows版PHPのbasename関数はドライブレターを除去しない場合がある

Windows版PHPのbasename関数は、パス名にディレクトリセパレータ(「\」および「/」)がない場合、ドライブレター(「C:」など)を除去しないことが分かりました。

具体的には、basename('c:autoexec.bat') の結果は、引数そのまま c:autoexec.bat となります。一方、basename('c:\autoexec.bat') の結果は、autoexec.bat と、期待通りの結果となります。

これにより、以下のようなスクリプトに対しては、c: などドライブレターが取り除かれないことになります。
$file = $_GET['file'];
readfile(basename($file));
上記に対して、file=c:autoexec.bat を指定すると、readfile('c:autoexec.bat') が実行されます。試してみると、ディレクトリセパレータがない場合、カレントディレクトリのautoexec.bat ではなく、c:\autoexec.bat がオープンされるようです。 ドライブレターは c: に限ったわけではなく、d:ドライブなど他のドライブのルートディレクトリ上のファイルもオープン可能となります。
すなわち、以下の条件が全てそろった場合、アプリケーションの想定ディレクトリではなく、任意ドライブのルートディレクトリの任意ファイルにアクセスできることになります。
  • ファイル名として、basename関数を通しただけのパス名を指定している
  • ディレクトリの指定をしていない。
そして、以下の影響が考えられます。
  • ルートディレクトリに重要情報があり、ファイル名を知る手段があれば、情報漏洩となる
  • 指定したファイルに書き込みができる場合、ルートディレクトリの任意ファイル名で書き込みができる(*1)
と、これだけ見ると「そんなにありそうではないけど、該当する場合はやばくないか」と思われるかもしれませんが、この条件に該当するスクリプトは、このbasename関数の挙動がなくても脆弱である可能性が高いです。
  • ディレクトリ修飾をしていないので、カレントディレクトリのPHPソースファイルにアクセスできる可能性が高い
  • 書き込みできるスクリプトの場合、任意内容のPHPスクリプトを外部から書き込むことにより、スクリプトインジェクションができてしまう
拡張子の制限をしている場合など、脅威は減少しますが、いずれにせよ良くない書き方であるわけで、この機会にソースを見直しましょう。

安全なウェブサイトの作り方改訂第6版によると、ディレクトリトラバーサル脆弱性の根本的解決策は以下のいずれかとなっています。
  • 外部からのパラメータでウェブサーバ内のファイル名を直接指定する実装を避ける。(3-(i)-a)
  • ファイルを開く際は、固定のディレクトリを指定し、かつファイル名にディレクトリ名が含まれないようにする。(3-(i)-b)
先に指摘した「該当するケース」は、3-(i)-bの「固定のディレクトリを指定し」ていない状態ですので、これを指定することで、この問題の影響を受けなくなります。
下記は、安全なウェブサイトの作り方改訂第6版のP71から、ディレクトリトラバーサル脆弱性の修正例です。
$dir = '/home/www/image/';
…
$file_name = $_GET['file_name'];
…
if(!file_exists($dir . basename($file_name))) {
  $file_name = 'nofile.png';
}
$fp = fopen($dir . basename($file_name),'rb');
fpassthru($fp);
上記はWindowsに限った話ではなく、Unix/Linuxでも同じですので、この機会にディレクトリトラバーサル対策の見直しをお勧めします。

なお、当該問題はphp.netにてBug #66395として報告済みで、既にリポジトリ上は修正されていますが、完全には対策されないと予想されます。安全なウェブサイトの作り方のようにアプリケーション側の正しい対応をお勧めします。

*1 Windowsのルートディレクトリに書き込むには高い権限が必要ですが、Windows版Apacheの場合、SYSTEM権限で動作するので、Apache上のPHPからルートディレクトリにファイルを書き込みできることを確認しています。

追記(2014/3/6)

この問題はPHP5.4.25 / PHP5.5.9にて改修されました。詳しくは「Windows版PHPのbasename関数がドライブレターを除去しない問題はPHP5.4.25/PHP5.5.9で改修された」を参照下さい。

0 件のコメント:

コメントを投稿

フォロワー