顧客情報など機密情報の流出やアカウントの不正使用など、Webサービスの欠陥が問題となって発生する事件が世間を賑わせています。どのようにしたら、安全なプログラムを作ることができるでしょうか。それは、セキュリティに関する知識を身につけることです。
以前から「PHPアプリにはセキュリティホールが多い」と言われています。本当でしょうか。実際、どんなプログラミング言語を使っても、正しいプログラムを作らなければ問題が発生します。PHPは、他のプログラミング言語よりも手軽に作ることができるため、その分、セキュリティの問題が取りだたされる機会が多くなってしまいます。これは、PHPがWebアプリ製作に多く使われていることの裏返しと言えるでしょう。
誰しも「自分の作るプログラムに限ってセキュリティに問題はない」と思いがちですが、本当に問題がないかどうか、今一度、以下の項目をチェックしてみましょう。
クロスサイトスクリプティングとは、外部からの入力にJavaScriptなどが含まれていた場合、その文字列の出力時にエスケープ処理を行っていないことが原因となって何かしらのトラブルが起きる問題です。XSS脆弱性と略したり、JavaScriptインジェクションと言うこともあります。悪意のあるユーザにより、ページ内にJavaScriptなどが埋め込まれ、それを表示した他のユーザのWebブラウザでスクリプトが実行されてしまいます。
これを簡単にテストするためには、掲示板やチャットなどのプログラムで、ユーザーが入力した文字が、JavaScriptとして実行されないかどうかを確かめてみることができます。
例えば、自分の作ったプログラムで名前やタイトル・本文など、あらゆる入力欄に以下のような入力を入れて確かめてみましょう。
<script>alert('Hello')</script>
正しく処理されるプログラムでは、以下のように、入力した内容がそのままHTMLとして出力されます。
しかし、クロスサイトスクリプティングの脆弱性を抱えるアプリケーションでは、入力したJavaScriptがそのまま実行されてしまいます。
脆弱性のあるプログラムと正しいプログラムと問題のあるプログラムを見比べてみましょう。まずは、脆弱性を抱えるプログラムの方です。
このプログラムでは、load_data()メソッドで、ユーザーが書き込んだデータを読み込み、foreach構文でデータの内容をHTMLとして出力するという仕組みの掲示板です。
file: bbs-ng.php より抜粋
// データの読込み $log = load_data(); // ログを表示 echo "<ul>"; foreach ($log as $i) { $name = $i["name"]; $body = $i["body"]; echo "<li><b style='color:red;'>$name</b>: $body</li>\n"; } echo "</ul>";
これをどのように修正したら良いでしょうか?読み込んだデータを、htmlspecialchars()関数を使って、HTMLに変換しているのです。これは、とっても、単純なことなのですが、非常に重要な処理と言えます。
file: bbs-ok.php より抜粋
// データの読込み $log = load_data(); // ログを表示 echo "<ul>"; foreach ($log as $i) { $name = htmlspecialchars($i["name"], ENT_QUOTES); $body = htmlspecialchars($i["body"], ENT_QUOTES); echo "<li><b style='color:red;'>$name</b>: $body</li>\n"; } echo "</ul>";
SQLインジェクションは、セキュリティ上の不備を意図的に利用して、アプリケーションが想定しないSQL文を実行させる攻撃方法です。意図しないSQLが実行されることにより、データベースの中にある意図しないデータを表示させたり、内容を不正に改変したり、テーブルの内容を全部を削除してしまうといった問題を引き起こします。
例えば、以下のようなSQLを実行したいとします。
SELECT * FROM users WHERE name='(ユーザーからの入力)'
このユーザーからの入力の部分に「' OR 'a'='a」という値が入っていたらどうなるでしょうか。
SELECT * FROM users WHERE name='' OR 'a'='a'
本来、特定の名前のユーザだけを検索するつもりのようですが、「'a'='a'」という条件が加わったため、WHERE句が常に真となり、結果全てのユーザーを検索して意図しない値を出力してしまいます。
これを防ぐためには、SQL内に値を埋め込む際には、プリペアードステートメントを使ったり、quote()メソッドを使って、値をエスケープする必要があります。詳しくは5章のデータベースの項をご覧ください。
本来、公開されていないURLにアクセスすることを「デイレクトリ・トラバース」と言います。これは、相対パス記法などを使うことで、本来はアクセスが許可されていないWebページやファイルへのアクセスを許してしまう脆弱性です。
例えば、「/home/data/」以下にあるファイルの内容を表示するプログラムを作ったとします。
// ディレクトリトラバースを考慮しない誤ったプログラム $data_dir = "/home/data"; $fname = $_GET["filename"]; $data_file = $data_dir . '/' . $fname; echo htmlentities(file_get_contents($data_file));
このときに、ファイル名として、「../../etc/passwd」というパラメータを与えたらどうなるでしょうか。このパスの中にある「..」というのは、1つ上の階層のディレクトリを表わす記号です。そのため、「/home/data/../../etc/passwd」というパスは、「/etc/passwd」にアクセスするのと同じ意味になってしまいます。このパスは、Unixシステムではユーザアカウントの一覧を保持しています。プログラムを記述する時にパスやURLの検証を十分に行わないと、思わぬ攻撃を受ける可能性があります。そのため、ファイル名を入力してもらう場合にはパス記号を除去するなど工夫が必要となります。
外部から得たパラメータを利用して、インクルードするファイルを決定する場合、意図しないファイルをインクルードしないよう注意する必要があります。includeやrequire文を実行する際には、注意が必要です。
また、気をつけたいのが、外部からアップロードされたファイルを実行することにならないよう気をつける必要があります。アップロードされた画像ファイルと見せかけて、実はPHPのスクリプトだったということもあり得るからです。ファイルのアップロードを許す際には、ファイル名ではなく、ファイルそのものの形式をチェックするなど、念入りな確認が必要です。
セッションを管理するセッションIDを盗むことにより、悪意のあるユーザが別のユーザになりすますことを「セッションハイジャック」と呼びます。これにより、不正に他人のアカウントを操作して、個人情報が盗まれる可能性があります。
同じセッションIDを使い続けていると盗まれる可能性が高くなるため、たとえば一定時間が経過したり、特定の動作を行ったら、セッションIDを変更するなどの対策を取る必要があります。
PHPの関数session_cache_expire()を使うことで、一定時間ごとにセッションIDを変更するように指定できますし、特定の動作でセッションIDを更新したい場合には、session_regenerate_id()関数を使うことで対応できます。また、通信自体を暗号化する(SSLを利用する)ことで、セッションIDの漏洩リスクを減らすことができます。
以上、PHPで問題となる代表的なセキュリティ問題について紹介しましたが、悪意を持つ攻撃者は、さまざまな手段を用いて脆弱性を突いてきます。そこで定期的にセキュリティ対策に関する資料に目を通しておくと良いでしょう。
IPA(独立行政法人 情報処理推進機構)は情報セキュリティに関する情報を公開しています。以下の「安全なウェブサイトの作り方」も、2006年に公開してから、定期的に改訂されています。
IPAによる「安全なウェブサイトの作り方」
http://www.ipa.go.jp/security/vuln/websecurity.html