konekto Inc with PHP , MySQL コネクト株式会社 技術情報コンテンツ


Tips

はじめに

Santyワーム*1のターゲットとされてしまったphpBB。
この事件を、何も知らない人が見聞きした場合、「PHPって、リスクの高いスクリプト言語じゃないの?」と考えてしまうかもしれない。
この誤解を解く鍵は、如何にしてSantyワームがphpBBに侵入したかを理解する事にある。

ここでは、Santyワームを例にして、Webアプリケーションの脆弱性*2について考えたいと思う。

脆弱性とは?

脆弱性は、どんな言語で開発されたWebアプリケーションであっても発生する可能性がある。 PHPのようなスクリプト言語であろうが、コンパイルによって出力された中間コードを用いるJavaのような言語であろうが、可能性は全く変わらない。

脆弱性が言語に依存しないとすれば、どうして脆弱性が生まれるのだろうか。

動作環境の脆弱性

まず最初に思い浮かぶのは、動作環境に依存した脆弱性だろう。
ここで言う動作環境とは、利用者(クライアント)側の環境ではなく、Webサーバ側の環境を指す。

PHPに限らず、アプリケーションに脆弱性が発見された場合、ほぼ数日のうちに脆弱性を改善したバージョンが公開されている*3
自宅などでサーバを公開している場合は、迅速にバグフィックスされたバージョンをインストールするように心掛ける事で、環境に依存する脆弱性を回避する事が可能である。

もし、レンタルサーバ上でサービスを運用を行っている場合、契約者はPHPなどのアプリケーションを更新する権限を持っていないため、脆弱性に対する処置は管理会社に一任される事となる。 脆弱性の対応がなされない場合、メールなどで管理会社に問い合わせてみると良いかも知れない。

Webアプリケーション側の脆弱性

実行環境の脆弱性ばかりがクローズアップされがちだが、Webアプリケーション自体に脆弱性が存在する事も珍しくない。

代表的な例が、[[クロスサイトスクリプティング>セキュリティ指針/クロスサイトスクリプティング]]や[[SQLインジェクション>セキュリティ指針/SQLインジェクション]]である。 これらの脆弱性は、一定のルールに従ったコーディングを行うだけで回避する事が可能である。 裏を返せば、脆弱性に関する対策を施さない若しくは脆弱性について全くの無知であった場合、Webアプリケーションの安全性は全く保障されない事になる。

phpBBがSantyワームに狙われた理由は、phpBBに存在していた脆弱性を悪用する為である。
もし、phpBBに脆弱性が無かった場合、このようなワームは存在しなかったであろう。

なぜ脆弱性が生まれるのか

PHPから話題はずれてしまうが、“バッファ・オーバーフロー”というキーワードを1度は耳にした事があるだろう。
Linuxのカーネルやアプリケーションのセキュリティ・アップデート情報に度々現れるキーワードなのだが、これも脆弱性の1つである。
アプリケーションが想定外の要求を受け取った場合、本来用意してあったバッファを溢れてしまい、用意してあったバッファに配置されている全く別のバッファやプログラムが上書きされてしまう。 その結果、アプリケーションは、設計者が全く想定していなかった動作をしてしまう。
溢れて別のバッファが破壊されてしまうと、アプリケーションの動作が停止してしまう事もあるが、意図的な内容で上書きを行ってしまえば、本来は利用できなかったはずの機能も利用可能になってしまう。

脆弱性とは、「アプリケーション設計者が想定していない情報を受け取ったときの対処が甘かった事によって生じる“人災”である」と言っても過言ではないだろう。

第2のSantyワームに狙われないために

Santyワームが狙ったのは、phpBBという普及率の高いWebアプリケーションであった。
自分が開発したWebアプリケーションが、phpBBのように不特定多数が利用する形態ではないからといって、安心してはいけない。 何故なら、Webアプリケーションが個人情報を扱っていたり、金銭に直結するような仕組みであれば、例え普及率が低かった(若しくは、特定企業専用だった)としても、ハッカーやクラッカー達に狙われる要素は十分に満たしているからだ。

では、どのような事に気を付けなければならないのか、具体例を挙げて解説しよう。

想定外の値は受け付けない

phpBBの脆弱性で狙われたのは、スクリプトを読み込むinclude(require)文の使い方であった。
読み込みたいスクリプトの名前を構成する要素の1つとして、クエリストリングの値を用いる事は大変に危険な行為である。
何故なら、本来用意してあったスクリプトだけでなく、外部の別サーバに用意された任意のファイルをHTTPプロトコルで取得してスクリプトとして読み込む事も可能だからである*4

クエリストリングのactで要求されたベース名に拡張子".php"を加えたスクリプトを読み込む場合、次のようなスクリプトを記述する人も少なくないだろう。

$path = $_REQUEST['act'] . 'php'
include($path);

このスクリプトは、非常に危険である。
act=http://example.com/foo」と要求した場合、外部のサーバにHTTPプロトコルで要求したレスポンスをスクリプトとして読み込んでしまう。
また、「act=/etc/passwd%00」と要求した場合、なんとサーバ上のパスワードファイルを読み込んでしまう事も可能である。

actの値のバリエーションが決まっているのであれば、整合性を確認すべきである。

$valid_arr = array('list', 'edit', 'delete', 'commit');

$act = $_REQUEST['act'];
if (!in_array($act, $valid_arr)) {
    exit('bad act.');
}
include($act . '.php');

上記のスクリプトであれば、actは、list, edit, delete, commit だけが許可され、正しくない場合はexitをする。

このように、アプリケーション設計者が意図している値だけを受け付けるようにすべきである。

クエリストリングの精査は必ず行う

ありがちな脆弱性として、クエリストリングの精査の甘さが考えられる。

次のスクリプトは、クエリストリングの精査が不十分である。

$num = $_REQUEST['num'];
if ($num > 0 && $num < 10) {
    $sql = "DELETE FROM foo WHERE id=$num";
}

このスクリプトに対して、num=1%20OR%201というクエリストリングを要求してみるといい。 fooテーブルにある全ての行が削除されてしまうであろう。
これは、スクリプトの2行目の検査で、$numの値を比較する際に、数値型にキャスト*5されてしまうため、$numは数値型の1として扱われてしまうからである。

この問題を解決するには、次のような処理を行うべきである。

$num = (int)$_REQUEST['num'];
if ($num > 0 && $num < 10) {
    $sql = "DELETE FROM foo WHERE id=$num";
}

このスクリプトでは、変数$numに代入した際に、既に整数型にキャストされるので、前述のような要求があった場合でも、不正な値をSQL文に埋め込まれる心配は無い。
もし、不正な要求であった事を検出したいのであれば、次のようなスクリプトを記述すると良い。

$num = $_REQUEST['num'];
if (ctype_digit($num) && $num > 0 && $num < 10) {
    $sql = "DELETE FROM foo WHERE id=$num";
} else {
    exit('bad request');
}

最後に

今回取り上げた情報だけでは、セキュアなWebアプリケーションを作るのに十分とは言えない。
更に詳しく知りたい方は、当コンテンツ内の[[セキュリティ指針]]を参考にしていただけると幸いである。


*1 Googleの検索機能を悪用し、phpBBを使って運用しているサイトを見つけ出して攻撃を行うワーム。
*2 アプリケーションの仕様に無い操作を行われた結果、改竄などの行為を行われてしまう事。=セキュリティ・ホール。
*3 アプリケーションのライセンスにより異なる。オープンソースとして公開されているものの中には、開発者が一切の責務を負わない事をライセンスに明記している場合も少なくない。
*4 php.iniの設定によって制限する事は可能だが、アプリケーション側でも十分な配慮をすべきである。
*5 型変換。PHPは必要に応じて変数の型を自動的に変換してくれる。

最終更新のRSS