なぜPHPアプリにセキュリティホールが多いのか?

第41回 PHP 5.3.4におけるセキュリティ上重要な仕様変更

この記事を読むのに必要な時間:およそ 3 分

NULL文字無効化

PHP 5.3.4で導入されたNULL文字の無効化は,サンプルコードのような脆弱なスクリプトの問題の主要な部分を解決します。分かりやすくするためにファイル名はスクリプト中に記述します。

<?php
$filename = "/etc/passwd\0.png";
if (preg_match('|\.png$|', $filename)) {
  echo readfile($filename);
}
?>

このコードをPHP 5.3.4以前で実行すると

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
[省略]

のようにパスワードファイルをダンプしてしまいます。PHP 5.3.4以降で実行するとreadfile関数がFALSEを返すため何も表示されません。

ローカルスクリプトインクルード

inlude/require文もファイル関数の一種です。サンプルコード1とまったく同じ構造のコードでinlude/require文を使ってしまうとローカルスクリプトインクルードが可能になります。

サンプルコード2

<?php
//モジュール名が指定されたらインクルード
if (preg_match('|\.php$|', $_GET['module_name'])) {
  include('./' . $_GET['module_name'] . '.php');
}
?>

このスクリプトはreadfile関数で脆弱なスクリプトとなってしまう場合よりもさらに悪いスクリプトです。ローカルファイルに限定されますがシステム上の任意のファイルが取得可能なうえ,ファイルアップロードが可能な場合は任意のスクリプトを実行される可能性がありました。

仕様変更の効果

PHPは埋め込み型の言語であるため,イメージファイルなどに埋め込まれたPHPスクリプトでも問題なく実行できます。PHPスクリプトのアップロードができなくても,イメージのアップロードなどができれば攻撃用のコードを埋め込み実行させることが可能でした。サンプルコード2はローカルスクリプトインクルードに脆弱な典型的なコードですが,PHP 5.3.4以降では上記のように脆弱なコードがあっても任意ローカルスクリプト実行と任意ファイル参照のリスクが大幅に低減します。

ファイル関数へNULL文字が含まれる文字列が渡された場合に無効化される仕様変更により,NULL文字攻撃に脆弱なスクリプトへの攻撃を不可能にします。今まで致命的とも言える脆弱性を持つPHPスクリプトであっても脆弱性がなくなる場合もあります。

この仕様変更はPHPスクリプトの安全性向上に大きく貢献します。しかし,この仕様変更は万能ではありません。サンプルコード2のようなコードでは.php拡張子を持つファイルはinclude文で読み込まれてしまいます。ユーザーから送信されたファイル名はしっかりとチェックするようにしなければなりません。

例えば,PHPアプリケーションのモジュールをインクルードする場合なら,数字・小文字アルファベット・アンダースコアのみで構成される文字列であることをチェックします。

if (strspn($_GET['module'], 'abcdefghijklmnopqrstuvwxyz0123456789_') != strlen($_GET['module'])) {
  die('Invalid module!');
}
注意

正規表現関数を利用しなくても済む文字列チェックは文字列関数を使うほうが効率がよい場合が多く,間違いが発生する可能性も少ないでしょう。

まとめ

PHP 5.2よりphp.iniにallow_url_includeディレクティブが導入されたことにより,簡単にサーバ乗っ取りが可能となるリモートファイルインクルード脆弱性のリスクが大幅に低減されました。今回のNULL文字を受け付けなくなる仕様変更によりローカルスファイルインクルード脆弱性のリスクも大幅に低減されました。

PHPは埋め込み型言語であるため,ファイルインクルード脆弱性は任意スクリプト実行につながる致命的な脆弱性となるケースが多くありました。しかし,PHP 5.3.4以降ではローカルの任意スクリプトの実行もかなり難しくなりました。PHPはほかの言語に比べ任意スクリプト実行が容易であったため,PHPによるアプリケーション構築はリスクが高い,と認識されていた開発者も多いでしょう。この仕様変更はその認識を改めさせることとなる仕様変更であり,PHPアプリケーションのセキュリティに重要な意味を持っています。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書