※以前別の場所で書いた文章を備忘的に書き記しておきます。
作成にあたっては「
PHP と Web アプリケーションのセキュリティについてのメモ」を参考にさせていただきました。この場をお借りして御礼申し上げます。
基本は「外部から受け取ったデータ(変数)について、不正な場合はエラー処理をするか無害化(サニタイズ)する」。これに尽きるようです。
主なセキュリティホールとしては、データベース利用時の「SQLインジェクション」と、HTML表示時の「クロスサイトスクリプティング(XSS)」が挙げられます。前者はデータベースに不正なデータを入力して操作することを、また、後者はHTML表示時に不正なJavaScriptなどを埋め込むことを、それぞれ意図しているものと自分なりに理解しています。
なお、受け取ったデータ(変数)ですが、整数でなければいけないものと、それ以外のものに分けて考えると理解しやすいと思います。
1.データ(変数)が整数の場合
(A)クロスサイトスクリプティング(XSS)対策・SQLインジェクション対策共通
if(is_numeric($_GET['page'])) $page = intval($_GET['page']);
if(!is_numeric($_GET['page'])) return;
if(!is_numeric($_GET['page'])) $page = 1;
$page = intval($_GET['page']);
$page = preg_replace('/^([0-9]+).*/' ,'\\1', $_GET['page'])
2.データ(変数)が整数以外の文字列の場合
(B)クロスサイトスクリプティング(XSS)対策(※HTML表示時)
$page = htmlspecialchars($_GET['page'], ENT_QUOTES);
(C)SQLインジェクション対策(※データベース利用時)
【ケース5】 ※strpos()やpreg_match()を使い特定の文字列(「'」や「"」)が存在するか否かを判定して処理
if(strpos($_GET['page'], "'") !== false) return;
if(strpos($_GET['page'], "'") === false) $page = addslashes($_GET['page']);
if(preg_match('/\'|"/', $_GET['page'])) return;
if(!preg_match('/\'|"/', $_GET['page'])) $page = addslashes($_GET['page']);
【ケース6】 ※addslashes()を使い文字列をスラッシュでクォート
$page = addslashes($_GET['page']);
$page = mysql_real_escape_string($_GET['page']);
3.共通
(D)受け取るデータの文字数を制限する場合
【ケース8】 ※substr()を使い受け取るデータ(変数)の文字数を制限
$page = substr($_GET['page'] ,0, 2);
最も気を付けなければいけないのは、$_GETや$_POST、$_SERVER['HTTP_REFERER']などのスーパーグローバル関数を使って外部からデータ(変数)を受け取る際の処理です。また、Nucleusでプラグインを作成する場合など、global変数($blogidや$catidなど)やグローバルオブジェクトを使う場合、念のため使用する変数を無害化しておいた方がよさそうです。外部からどのようなデータ(変数)が飛んでくるのか分からない分、受け取る際の処理には細心の注意が必要となってきます。
次に注意するのは、データベースからデータを呼び出す場合です。
例えば【ケース6】や【ケース7】のように、特殊文字をエスケープせずにデータベースに格納した場合、そのデータを呼び出してHTML表示する際には【ケース4】の処理が必要となります。
セキュリティホールのチェック方法は以下の通り。
- 「$_」で検索をかけて、外部から受け取ったデータ(変数)を確認し、その後どのように使われているかを追跡する。ソースのメンテナンスのことを考え、外部から受け取ったデータ(変数)については、直後にエスケープすることを心がける。
- データベース利用時にWHERE節などにおいて変数を直接使う場合や、HTML表示時に<a href></a>や<form></form>内などにおいて変数を直接使う場合、それぞれの変数がエスケープされているかどうかを確認。
整数の処理については、【ケース3】のように正規表現preg_replace()を使えば、思い通りの処理結果が得られますが、厳密性を求めずセキュリティ対策を重視する場合は、処理速度のことを考えるとis_numeric()やintval()を使った方がいいように思います。
以上のセキュリティ対策に関する記述についてですが、勘違いしている箇所や間違っている箇所など多々あると思います。その際は指摘してくださると喜びます。
また、以上のセキュリティ対策に関する記述を鵜呑みにせず、他の情報源(例:「
MySQL 4.1 リファレンスマニュアル :: 4.3.1 一般的なセキュリティガイドライン」「
サニタイズと言わない」など)と比較したり付き合わせた上でセキュリティ対策を実施してください。
【追記】
下記ページをエントリーしたことに伴い、内容を見直しました。あわせてご覧下さい。(2008-02-28)