PHPのセキュリティを確保する

PHPは世界中の数百万台のサーバ上で動的なWebサイトの作成に使用されている人気の高いスクリプト言語である。サーバで動的ページを生成することは、ユーザにサーバ上のコマンドやファイルやネットワーク接続へのアクセスを与えることを意味し、潜在的に多数のセキュリティリスクを生じる可能性を伴う。そのようなリスクはサーバの設定を適切に行うことで大幅に低減できるが、プログラマは作成するスクリプトについても責任を持ってそのセキュリティの万全を期すべきだ。

Apacheのインストールについては以前の記事で取り上げた。今回の記事では、そのApacheのセットアップにPHPを加えるために必要な追加的手順を説明し、続いて、PHPをサーバ上で安全に実行する方法について論じる。

インストール方法の選択

PHPのインストールでは、サーバ上のCGIインタプリタとしてインストールする方法と、Apacheモジュールとしてインストールする方法のいずれかを選択できる。どちらの方法にもそれぞれメリットとデメリットがある。

PHPをCGIインタプリタとしてインストールすると、ApacheのsuEXEC機能を利用することにより、 複数のPHPプロセスを個別の仮想ホストのもとにおいて別々のユーザIDで実行することが可能になる。しかし、この方法でインストールされたPHPは十分な動作速度を実現できない場合があり、また、設定を誤ると数々のリスクを抱え込むおそれがある。

その代わりとして選択可能なのがPHPをApacheモジュールとしてインストールする方法であり、この記事ではこちらの方法に的を絞って解説することにする。Apacheモジュールとしてインストールする場合は、さらにモジュールの形式が選択可能で、静的モジュールとしてコンパイルするか動的モジュールとしてコンパイルするかを選ぶことができる。静的モジュールにはパフォーマンスが向上するという利点があるが、アップグレードの際にはPHPとApacheの両方をコンパイルし直す必要が生じる。動的モジュールとして使用するとパフォーマンスが多少低下し、また、あらかじめ、mod_soモジュールを要求する動的モジュールのサポートが可能な形でApacheをコンパイルしておく必要がある。だが、パッチの適用やPHPのアップグレードは、動的モジュールとしてインストールした場合の方が格段に容易である。どちらを選ぶかは、個々の場合の必要性、特にパフォーマンスの重要性とアップグレードの簡便性の兼ね合いに応じて決めることになる。

ApacheとPHPのコンパイル

Apacheのインストール手順の詳細については、以前の記事「Apacheをchroot環境で使用する」の説明を参照してほしい。この記事では、そのセットアップにPHPを加える場合に追加的に必要となる考慮点のみを論じる。

Webサーバ上にPHPを配備する際にもう1つ考慮すべきなのは、インストールするApacheにmod_securityを加えて、クロスサイトスクリプティングやSQLインジェクション攻撃に対する防御を一層強化することだ。

静的モジュール

PHPを静的モジュールとしてコンパイルする場合は、以下のコマンドを使用して、Apacheをコンパイルする前にPHPのコンパイルを実行する必要がある。

./configure --with-mysql=/usr/local/mysql --with-apache=/path/to/apache_source --enable-safe-mode
make
su
make install

次にmod_securityのソースからapache1/mod_security.cをApacheソースディレクトリ内のsrc/modules/extraにコピーし、続いて、Apacheを通常と同様にコンパイルする。その際、configureのオプションとして--activate-module=src/modules/extra/mod_security --enable-module=security --activate-module=src/modules/php4/libphp4.aを指定する。

動的モジュール

PHPを動的モジュールとしてインストールする場合は、先にApacheをインストールしてから、以下のコマンドを使用して、PHPをソースからコンパイルする。

./configure --with-mysql=/usr/local/mysql --with-apxs=/usr/sbin/apxs --enable-safe-mode
make
su
make install

mod_securityを動的モジュールとしてインストールするには、ソースを展開して、cd mod_security-1.8.3/apache1としてから、ルート権限でapxs -cia mod_security.cを実行する。

どの方法でPHPをインストールする場合でも、有効化するオプションは必要最小限にとどめるべきだ。多くの機能を組み込むことは、それだけ脆弱性のリスクを増やすことにほかならない。また、特にマルチユーザ環境では--enable-safe-modeオプションの使用を強く推奨する(セーフモードについては、この後で詳しく述べる)。

chroot環境へのインストール

PHPとmod_securityを動的モジュールとしてコンパイルした場合は、そのモジュールを次のようにchroot環境にコピーする必要がある。

cp /usr/libexec/apache/libphp4.so /chroot/httpd/usr/libexec/
cp /usr/libexec/apache/mod_security.so /chroot/httpd/usr/libexec/

また、libmysqlclientなど、必要ないくつかのライブラリも以下のようにコピーする。

cp /usr/local/mysql/lib/mysql/libmysqlclient.so.12 /chroot/httpd/usr/lib/
cp /usr/lib/libm.so.2 /chroot/httpd/usr/lib/
cp /usr/lib/libz.so.2 /chroot/httpd/usr/lib/

PHPをインストールする際に追加的な機能を有効にした場合は、さらに多くのライブラリが必要となる可能性がある。必要なライブラリを確認するには、動的モジュールとしてインストールした場合はldd /usr/libexec/apache/libphp4.so、静的モジュールとしてインストールした場合はldd /usr/local/bin/httpdを実行する。

続いて、以下のように、/chroot内に/tmpディレクトリを作成する。

mkdir /chroot/httpd/tmp
chown root.root /chroot/httpd/tmp
chmod 1777 /chroot/httpd/tmp
最後に、次のコマンドでPHPの設定ファイルをコピーする。 cp /etc/apache/php.ini /chroot/httpd/etc/

PHPを利用するためのApacheの設定

PHPを有効にするには、httpd.confに以下の行を追加する。

LoadModule php4_module        libexec/libphp4.so
AddModule mod_php4.c
AddType application/x-httpd-php .php
AddType application/x-httpd-php .php3
AddType application/x-httpd-php .inc
AddType application/x-httpd-php .class

これらのAddTypeディレクティブを追加することにより、.php、.php3、.inc、および.classの拡張子を持つファイルはすべてPHPスクリプトとして処理されるようになる。スクリプトにincludeする外部ファイルとして*.classや*.incの拡張子のファイルを使用するプログラマは少なくない。.incや.classに関するディレクティブを記述しておかないと、これらのファイルがクライアントのブラウザにプレーンテキストとして表示されてしまい、パスワードなどの機密データを見られてしまう可能性が生じる。

同じ効果を実現する別の方法として、Filesディレクティブを使用して特定のパターンにマッチするファイルへのアクセスを拒否することもできる。たとえば、次のFilesディレクティブを記述すると、ファイル名が.incまたは.inc.phpで終わるファイルへのアクセスはすべて拒否されるようになり、スクリプト内でinclude()またはrequire()を使用して読み込む方法以外ではアクセスできなくなる。

<Files ~ "\.inc(.php)?$">
  Order allow,deny
   Deny from all
   Satisfy All
</Files>

サーバ上でPHPを実行している事実をあからさまにしたくない場合には、スクリプトに別の拡張子を付けるという方法がある。たとえば、AddType application/x-httpd-php .dhtmlというディレクティブを使用すると、Apacheは.dhtmlの拡張子を持つファイルをPHPスクリプトとして解釈するようになる。ただし、PHPが使われていることをユーザが突き止める手掛かりは拡張子以外にもあるので、これはあくまで表面的な隠蔽手段にすぎないことに注意すべきだ。

mod_securityを使用してインジェクション攻撃を防御する

mod_securityはWebアプリケーション用の侵入検知・防止エンジンであり、Apacheモジュールとして動作する。mod_securityはサーバへのGETおよびPOSTの要求を監視して、現行の設定に照らして悪意の可能性ありと見なされる要求は拒否する。

mod_securityモジュールを有効にするには、httpd.confに以下の行を追加する。

LoadModule security_module        libexec/mod_security.so
AddModule mod_security.c

そして、mod_securityを使用してGET要求とPOST要求のスキャンを行うには、以下の設定を追加する。

<IfModule mod_security.c>
    # フィルタリングエンジンを有効または無効にする
    SecFilterEngine On

    # URLのエンコーディングが正当であることをチェックする
    SecFilterCheckURLEncoding On

    # Unicodeのエンコーディングをチェックする
    SecFilterCheckUnicodeEncoding On

    # この範囲のバイト値のみを許可する
    SecFilterForceByteRange 0 255

    # 不審な要求のみログに記録する
    SecAuditEngine RelevantOnly

    # 監査ログファイルの名前
    SecAuditLog logs/audit_log
    # デバッグレベルを最小に設定する
    SecFilterDebugLog logs/modsec_debug_log
    SecFilterDebugLevel 0

    # mod_securityによるPOSTペイロードの検査を有効にする
    SecFilterScanPOST On

    # 不審な要求に対するデフォルトの対応として
    # 要求を拒否してログに記録し、HTTPのステータスコード500を返すように設定する
    SecFilterDefaultAction "deny,log,status:500"

</IfModule>

各ディレクティブの意味はコメントによる説明で十分に明らかだろう。 これは考え得る最も基本的な設定だが、この設定でも次のような有用な機能が提供される。
連続するフォワードスラッシュ(//)を削除する
自ディレクトリの参照(./)を削除する
URLのデコードを実行する
nullのバイト値(%00)をスペースに置換する
URLのエンコーディングを検証する
Unicodeのエンコーディングを検証する
バイト値の範囲を検証し、要求の中では特定範囲の値の文字しか許可しない

mod_securityを使用する大きな目的の1つは、SQLインジェクション攻撃とクロスサイトスクリプティング (XSS)攻撃を防ぐことにある。そのためには、上に示したIfModuleブロック内に以下の行を追加する。

SecFilter "\.\./"
SecFilter "<(.|\n)+>"
SecFilter "'"
SecFilter "\""

最初の行は、ディレクトリトラバーサル攻撃(接続しているWebサイトが置かれているディレクトリ以外のファイルにアクセスを試みる攻撃)を防止するための設定だ。2行目は、要求内の山かっこ("<"">")を検出することによってHTMLタグを含む要求を拒否する設定で、XSS攻撃に対する防御を高めるために有効である。次の2行はそれぞれ単一引用符と二重引用符を対象とするフィルタの設定で、SQLインジェクション攻撃を困難にする効果がある。

これらのフィルタを使用すると、フィルタ対象のいずれかの文字がHTMLのフォームを通じて送信された場合、その要求は拒否されることになる。したがって、プログラマはフォームの内容をサーバに送信する前に、JavaScriptを使用して、使用禁止文字を適切な形式の特別なタグ(><、"など)に変換する必要があるだろう。

SecFilter "<[:space:]*script"というフィルタ設定を使用すれば、XSS攻撃の検知をもう少しゆるやかな基準で実施できる。このフィルタがチェックするのは<scriptタグのみで、その他のHTMLタグは使用が許される。

セーフモード

マルチユーザ環境でのPHPのセキュリティに関する大きな不安要素の1つは、スクリプトがサーバのユーザIDのもとで実行されることだ。これは、あらゆるリソースに関してPHPのユーザがサーバと同じアクセス権を持つことを意味するだけでなく、どのPHPスクリプトも同じUIDで実行されることから、すべてのスクリプトが互いにアクセス可能となることも意味する。

アーキテクチャの観点から言えば、この問題はオペレーティングシステムのレベルで解決するのが最善であり、実際にさまざまなソリューションが存在するが、完全に満足のいく方法は見当たらない。その欠如を埋めるためにPHPの開発者たちが導入したのがセーフモードである。

セーフモードでは、スクリプトからファイルにアクセスするたびに、そのファイルの所有者がスクリプト自体の所有者と一致しているかがチェックされ、所有者が異なるファイルにはアクセスできない。セーフモードを有効にするには、既に述べたようにセーフモードのサポートを有効にするオプションを指定してPHPをコンパイルした上で、php.iniにsafe_mode = Onのディレクティブを追加する。セーフモードがOnの場合には、さらにsafe_mode_gid = Onを設定することにより、もう少し制約のゆるいセーフモードを適用することもできる。safe_mode_gidを有効にすると、スクリプトとアクセス対象ファイルのユーザIDの代わりに、グループIDの一致がチェックされるようになる。

セーフモードには、この他にも設定可能なオプションがいくつか存在する。最も重要なのは以下のオプションだ。

safe_mode_exec_dir = /some/dir ―― system()など、サーバ上のバイナリを実行する関数の呼び出しにおいて、関数から実行可能なプログラムを、指定したディレクトリ内のプログラムのみに制限する。

safe_mode_allowed_env_vars = PHP_ ―― ユーザが値を設定できる環境変数を、PHP_というプレフィックスで始まる環境変数のみに制限する。プレフィックスはPHP_に限らず、必要に応じて自由に設定できる(カンマを区切り文字として複数のプレフィックスのリストを指定することも可能)。ただし、プレフィックスの指定を空白のままにすると、ユーザによる任意の環境変数の設定が許可されてしまうため、プレフィックスを何も指定しないでこのディレクティブを使用することは避けるべきだ。

その他のPHP設定オプション

以下に示すように、セーフモード以外にも、設定ファイルのphp.iniで設定可能なセキュリティ関連のオプションが多数存在する。

open_basedir = /some/dir ―― PHPによるオープンや読み込みが可能なファイルを、指定したディレクトリツリーのファイルのみに制限する。ディレクトリとしてドット(.)を指定すると、現在のスクリプトが格納されているディレクトリのファイル以外はオープンできなくなる。コロンを区切り文字として複数のディレクトリを指定することも可能だ。このオプションを指定しないデフォルトの状態では、任意のディレクトリへのアクセスが許可される。

disable_functions = function1,function2 ―― 指定した関数の使用を禁止する。複数の関数を指定する場合は、コンマを区切り文字とするリストとして記述する必要がある。

expose_php = Off ―― 通常、PHPはヘッダに情報を追加することによって、サーバ上にPHPがインストールされている事実を開示している。このディレクティブをOffに設定すると、そのような形で情報が明らかになるのを防ぐことができる。

display_errors = Off ―― このオプションをOffに設定すると、クライアントのブラウザに対するエラーの出力が行われなくなる。このようなエラーメッセージは、ファイルパスやデータベーススキーマといったサーバに関する重要な設定情報が漏洩する原因となる可能性があるので、稼働用のサーバではエラー出力の機能を無効にすることを強く推奨する。

log_errors = On ―― このオプションをOnに設定すると、エラーのログがファイルとして記録されるようになる。記録先として使用するログファイルはerror_logで指定する。

error_log = /var/log/php_errors ―― エラーメッセージのログを記録するファイルを指定する。

register_globals = Off ―― 環境変数、GET変数、POST変数、Cookie変数、および組み込み変数がグローバル変数として登録されることを防ぐ。これらの値をグローバル変数として持つと、プログラマが変数にアクセスする上での利便性は大きく向上するが、コードがよく考え抜かれたものでない場合にはセキュリティリスクが大幅に高まるおそれがある。このオプションはOffに設定することを強く推奨する。(バージョン4.2.0以降ではデフォルトでOffになっている。)

まとめ

サーバ上で動的なWebページの生成を実行することは、その本質上、必然的にリスクを伴うが、ここで論じた方法を用いれば、不正侵入のリスクを低く抑えるとともに、セキュリティが侵害された場合に発生し得る損害を軽減することができる。もちろん、改めて言うまでもなく、新たな脆弱点の発見に直ちに対応できるよう、常に最新情報の入手を心がけ、セキュリティ関連のパッチは提供された時点で怠りなく適用することが大切である。また、基盤となっているWebサーバやオペレーティングシステムのセキュリティもおろそかにしてはならない。そして最後に、システムの総合的なセキュリティを実現する責任の一部はWebアプリケーション自体が担っていることを銘記すべきである。