SecureReality は、非常に興味をそそる「A Study In Scarlet - Exploiting Common Vulnerabilities in PHP」[Clowes 2001]」というドキュメントを出して いて、PHP 4.1.0 以前のバージョンにターゲットを当て、安全なプログラムを 書く上で問題となる点のいくつかを論じています。 このドキュメントでは「努力したとしても、PHP で安全なアプリケーションを 書くのは非常に難しい(PHP のデフォルトの設定では)」と結論づけています。
どんな言語にもセキュリティ上の問題はありますが、PHP には安全上おそらく 他の大半の言語と比べて際だった問題点が 1 つあります。それは名前空間に データをロードする方法です。 デフォルトで PHP (バージョン 4.1.0 もしくはそれよりも古いバージョン)は、 Web 上の PHP へと送られる環境変数とその値をすべて、自動的に同じ名前空間 (グローバル変数)にロードします。通常の変数もそこにロードします。したがって、 攻撃者は自由に変数やその値を設定可能で、PHP プログラムがあえて再設定しない 限り、その値はそのままになっています。 さらに PHP は最初に変数を作る要求があった時に、デフォルトの値を設定します。 したがって、PHP プログラムは変数を初期化しないのが普通です。 変数を設定するのを忘れると、PHP は報告をあげますが、デフォルトではそうなって いません。ただ忘れてはいけない点は、これが単にエラーの報告であって、攻撃者 が普通でない方法を見つけて、エラーを起こすことを止められないことです。 つまり PHP はデフォルトでは、プログラムが攻撃者に対して特別に注意を払って 攻撃者を負かさない限り、プログラムにある変数すべての値のコントロールを完全に 許してしまっています。 プログラムが動きだすと、これらの変数を再設定できますが、どの変数の再設定に 失敗しても(はっきりとしていなくても) PHP プログラムの脆弱さがあらわになる でしょう。
たとえば、下記の PHP プログラム(Clowes 氏による例)は、パスワードを知っている 人にだけ何か重要な情報を与えようとしていますが、攻撃者は Web ブラウザで 「auth」に値を設定し、認証チェックの効力を失わせられます。
<?php if ($pass == "hello") $auth = 1; ... if ($auth == 1) echo "some important information"; ?> |
私の他にも、このとりわけ危険な問題を批判する人が大勢います。PHP は広範に 使われているので、問題は深刻です。 結局、簡単に使える言語は、簡単に安全なプログラムを書けるようになっています。 PHP では、設計時に見通しを誤ってしまったこの機能を無効にできます。 「register_globals」を「off」にすればよいのです。しかし、PHP の 4.1.0 より 新しいバージョンでは、デフォルトで「on」になります。4.1.0 以前は register_globals を off にして使用するのは困難です。 PHP の開発者は、PHP 4.1.0 のアナウンスの中で下記のように述べています。 「PHP の次の準メジャー・バージョンアップでは、デフォルトで register_globals は off になってインストールされます」
「register_globals」が「on」になっている PHP は、重要なプログラムにとって 危険な選択となります。いとも簡単に安全でないプログラムが書けるからです。 しかし、「register_globals」が「off」になりさえすれば、PHP は開発するのに なかなか使える言語になります。
デフォルトを安全にするには、「register_globals」を「off」にすること、 ユーザが外部の情報源から得る入力に対して設定を行い、制限をかけやすく できる機能を追加することが挙げられます。 Web サーバ(Apache のような)は、独自に PHP を安全に設定してインストールでき ます。 ユーザが受け取りたい入力変数が簡単にリストアップできるようなルーチンを PHP ライブラリに入れられます。 関数には、変数が持たなければならないパタンおよび変数が強制されなければ ならない型をチェックするものがあります。 私の考えでは、現状、安全な Web 開発に PHP を採用するのはどうかと思います (register_globals が on なので)。しかし、ちょっとした修正をすれば、手ごろな 手段になります。
PHP を使おうと決めたなら、ここでアドバイスをいくつか書いておきます(これら のアドバイスの多くは、Clowes 氏が提起している問題への対処方法をベースにして います)。
PHP の設定オプションである「register_globals」を「off」にして、4.1.0 以上 のバージョンを利用してください。 PHP 4.1.0 ではいくつか特別な配列を用意していて、その中でも $_REQUEST は 「register_globals」を「off」にしている場合に PHP でのソフトウェア開発を 容易にします。 register_globals を設定することで、PHP に対する一番よくある攻撃を完全に 排除できます。またこの設定が新規インストールのデフォルトになる時の準備にも なります。 register_globals が off であることを前提にするなら、まずこの点をチェックする 必要があります(そうなっていなければ中断します)。そうすれば、プログラムを インストールする人が、問題があることにすぐ気付きます。 サードパーティの PHP アプリケーションでこの設定で動作するものは少数です。 したがって現状では、Web サイトで完全に off にするのは困難であることを忘れない でください。 また、「register_globals」を無効にすると、サードパーティによるホスティング も難しくなります。 プログラムのいくつかだけが「register_globals」を「off」にしていることは可能 です。 たとえば Apache なら、下記の行を PHP のディレクトリの .htaccess ファイルに 加えてください(もしくは Directory 命令を使ってさらに制御をかけてください)。
php_flag register_globals Off php_flag track_vars On |
register_globals が on になって動いていると思われるところで、ソフトウェアを 開発しなければならないなら(たとえば、あちこちに存在する PHP アプリケーション)、 ユーザが設定していない値を常に設定するようにしてください。 PHP のデフォルト値を前提にしないでください。また自分で確かに設定した変数で なければ、信用してはいけません。 入口になるどの部分でも、これを行わなければいけません (たとえば PHP プログラムと PHP を使った HTML ファイルすべて)。 最善の解決策は、PHP プログラムそれぞれに対して、使用する変数すべてに値を設定 することです。ただそれらに普通のデフォルト値("" や 0)を再設定するとしてもです。 これには、実行に必要な include されるファイルに入っているグローバル変数や ライブラリすべてにも当てはまります。 あいにくこの点が、提案を実行することを困難にしています。それは、開発者の中で すべてのグローバル変数がすべての関数から利用されるかもしれないという点を本当に 知っていて、理解している人が少ないからです。 それより劣る方法ですが、HTTP_GET_VARS や HTTP_POST_VARS、HTTP_COOKIE_VARS、 HTTP_POST_FILES を捜し出し、ユーザがデータを用意しているが、プログラマが その情報すべてをチェックし忘れていないかを見つけます。また PHP が新しいデータ 源を追加すると何が起こるかも見つけます(たとえば、HTTP_POST_FILES は古い バージョンにはありません)。
エラー報告のレベルを E_ALL に設定して、すべてのエラーがテスト中に報告される ようにしてください。 何よりこの報告には、初期化していない変数についての警告があります。これが PHP では重要な問題になります。 とにかく PHP を使いはじめるなら、これは良い考えです。なぜならプログラムを デバッグするのにも役立つからです。 エラー報告のレベルを設定するのには、いろいろな方法があります。 「php.ini」ファイル(全体)や「.htttpd.conf」ファイル(ホスト 1 台)、 「.htaccess」ファイル(複数ホスト)、もしくはスクリプトのトップレベルで、エラー 報告関数を通して行なう等があります。 推奨するエラー報告レベルの設定方法は、php.ini ファイルとトップレベルの スクリプト両方で設定する方法です。そうすれば、(1)トップレベルのスクリプトに コマンドを入れ忘れる(2)プログラムが別のマシンに移動して php.ini ファイルを 変更し忘れる、ということが防げます。 つまり PHP プログラムそれぞれが、下記のようにはじまるべきです。
<?php error_reporting(E_ALL);?> |
ファイル名を作成するのに使われるユーザ情報は、どれも注意深くフィルタして ください。とりわけリモートファイルへのアクセスは防いでください。 PHP はデフォルトで「リモートファイル」機能がついてきます。つまり、fopen() のようなファイルをオープンするコマンドが存在しています。他の言語がローカル ファイルだけをオープンできるのに対して、他のサイトからの Web や ftp の要求 を呼び出すのに実際よく使われます。
PHP の古い形式でファイルのアップロードをしないでください。HTTP_POST_FILES 配列とそれに付随した関数を使ってください。 PHP はファイルをアップロードするのに、そのファイルをどこかのテンポラリの ディレクトリに特定の名前で置いています。 PHP はそもそも、変数の寄せ集めに対して設定を行い、ファイル名がどこにあるか を示します。しかし、攻撃者は変数名やその値をコントロールするのが可能なので、 その機能を使って、とんでもないことを起こせます。 そのかわりに、HTTP_POST_FILES とそれに付随した関数を使って、アップロード されたファイルにアクセスしてください。 この解決方法を取ったとしても、 攻撃者が勝手な内容のファイルをテンポラリで アップロードできてしまいます。これはそれ自体危険です。
ドキュメントツリーには、保護済みの入口部分だけを入れてください。他のコード (大部分がそうであるべきですが)は、ドキュメントツリーの外に置いてください。 PHP はこのトピックスについて、残念な報告が過去いろいろありました。 元々は、PHP ユーザは「.inc」(include)拡張子を使って、「含まれる」ファイルを サポートしてきました。しかし、このファイルにはパスワード他の情報も入ります。 また Apache は「.inc」ファイルがドキュメントツリーにあり、要求がありさえ すれば、要求者にその内容を与えてしまっていました。 これまで開発者は、すべてのファイルに「.php」という拡張子を付けていました。 これはこのファイルが見られないことを意味します。しかし、入口部分では なかったファイルが入口になってしまえば、そこが時として悪用されてしまいます。 先程述べたように、セキュリティ上のアドバイスで一番大切な点は、いつも同じです。 ドキュメントツリーには、保護済みの入口部分(ファイル)だけを入れてください。 他のコード(たとえば、ライブラリ)は、ドキュメントツリーの外に置いてください。 どんな「.inc」ファイルもドキュメントツリーには入れないでください。
セッション機構を避けてください。 「セッション」機構は、便利に永続的なデータを保存できますが、現状の実装には 問題点がたくさんあります。 まず、デフォルトではセッションはテンポラリファイルに情報を保存します。 したがって、マルチホストなシステムであると、数ある攻撃や情報流出に対して隙を 見せることになります。 今はマルチホストなシステムではなくても、いつかそうなるとも限りません。 この情報をファイルシステムではなく、データベースと「紐づける」ことも可能 です。しかし、別の人間がマルチホストなデータベース上で同じパーミッションで データベースにアクセスできるなら、問題は一緒です。 気をつけていないと曖昧になりますし(「セッション値なのか攻撃者が設定した値 なのかわからない)、攻撃者が選んだ内容のファイルや鍵をサーバに無理やりおいて しまうことも可能です。これは物騒な状況です。また、攻撃者はある程度、ファイル 名や鍵名をどこに置くかをコントロールすることさえ可能です。
入力がアクセスしてかまわないのか、パタンに照らし合わせてすべてチェック してください(言語と同じように)。そして型変換を使って、文字列ではないデータを 強制的にあるべき型にしてしまってください。 「ヘルパー」関数を開発すればチェックが簡単になり、(予想範囲内の)入力が 選ばれたリストから取り込めます。 PHP は型の制約が緩く、これが問題を起こします。 たとえば、入力データの値が「000」なら、「0」とは等しくなりませんし、empty() も等しくなりません。 これは連想配列にとって、とりわけ重要です。それはインデックスが文字列だからです。 $data["000"]は $data["0"]とは違うことを意味します。 たとえば、$bar が double 型であると確認しなければいけません(確認した後は、 double 型にとってのみ、正しいフォーマットとなります)。
$bar = (double) $bar; |
危険をはらんでいる関数には特に注意を払ってください。 この関数には PHP コードの実行(たとえば、exec()や passthru()、backtick 演算子、system()、popen())やファイルのオープン(たとえば、fopen()や readfile()、file())等があります。 これは完璧なリストではありません。
適切な場合には、magic_quotes_gpc()を使ってください。いろいろな攻撃を排除 します。
【訳註:原著では、オブジェクト指向スクリプト言語 Ruby にふれていません。 訳註として簡単に Ruby のことにふれたいと思います。 Ruby にはセキュリティモデルが存在しています。 大きくわけて 2 つの危険なケースを想定しています。 1 つは、信頼できないデータを扱う場合、もう 1 つは、信頼できないプログラムを 扱う場合です。 このケースに対応して、レベル 0 から 4 までのセキュリティ・レベルを $SAFE というグローバル変数を用いてスレッド単位に設定できます。 ただし注意していただきたいのは、C で書かれた組み込みライブラリ、拡張ライブラリ いずれとも、すべての汚染をチェックしている保証がない点です。 汚染をチェックするのは、作成者にまかされています。 セキュリティモデルの詳細については、 「オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル」の「セキュリティモデル」 を参照してください】