PHPとMySQLのセキュリティ対策-覚書

※以前別の場所で書いた文章を備忘的に書き記しておきます。

【投稿年月日】2007-01-15 【ジャンル】PHP/MySQL


 作成にあたっては「PHP と Web アプリケーションのセキュリティについてのメモ」を参考にさせていただきました。この場をお借りして御礼申し上げます。
 
 基本は「外部から受け取ったデータ(変数)について、不正な場合はエラー処理をするか無害化(サニタイズ)する」。これに尽きるようです。

 主なセキュリティホールとしては、データベース利用時の「SQLインジェクション」と、HTML表示時の「クロスサイトスクリプティング(XSS)」が挙げられます。前者はデータベースに不正なデータを入力して操作することを、また、後者はHTML表示時に不正なJavaScriptなどを埋め込むことを、それぞれ意図しているものと自分なりに理解しています。

 なお、受け取ったデータ(変数)ですが、整数でなければいけないものと、それ以外のものに分けて考えると理解しやすいと思います。




1.データ(変数)が整数の場合


(A)クロスサイトスクリプティング(XSS)対策・SQLインジェクション対策共通

【ケース1】 ※is_numeric()を使い整数かどうか判定して処理
if(is_numeric($_GET['page'])) $page = intval($_GET['page']);
if(!is_numeric($_GET['page'])) return;
if(!is_numeric($_GET['page'])) $page = 1;

【ケース2】 ※intval()を使い整数化
$page = intval($_GET['page']);

【ケース3】 ※preg_replace()を使い受け取るデータ(変数)を整数に制限
$page = preg_replace('/^([0-9]+).*/' ,'\\1', $_GET['page'])


2.データ(変数)が整数以外の文字列の場合


(B)クロスサイトスクリプティング(XSS)対策(※HTML表示時)

【ケース4】 ※htmlspecialchars()を使い特殊文字をエスケープ処理
$page = htmlspecialchars($_GET['page'], ENT_QUOTES);

strip_tags()を使えばタグを除去できるが不完全。詳しくは「クロスサイトスクリプティング対策に strip_tags() を使用するときの注意」を参照。
※ENT_QUOTESをつけないとエスケープできない文字があるので不完全です。必ずつけましょう。詳しくは「PHPエスケープ関数の比較一覧」を参照。面倒な場合は「PHPとMySQLのセキュリティー対策関数」の利用をお勧めします。(2008-02-28追記)


(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']);

【ケース7】 ※【ケース6】の代わりにmysql_real_escape_string()を使い特殊文字をエスケープ(注:PHP4.3以上)
$page = mysql_real_escape_string($_GET['page']);

※文字コードがSJISの場合、addslashes()やmysql_real_escape_string()では対策が不完全になる可能性があります。(参考:「addslashes() による SQL 文字列のエスケープ回避問題」)
※addslashes()ではエスケープできない文字があるので不完全です。詳しくは「PHPエスケープ関数の比較一覧」を参照。PHP4.3未満の場合は「PHPとMySQLのセキュリティー対策関数」の利用をお勧めします。(2008-02-28追記)


3.共通


(D)受け取るデータの文字数を制限する場合

【ケース8】 ※substr()を使い受け取るデータ(変数)の文字数を制限
$page = substr($_GET['page'] ,0, 2);





 最も気を付けなければいけないのは、$_GETや$_POST、$_SERVER['HTTP_REFERER']などのスーパーグローバル関数を使って外部からデータ(変数)を受け取る際の処理です。また、Nucleusでプラグインを作成する場合など、global変数($blogidや$catidなど)やグローバルオブジェクトを使う場合、念のため使用する変数を無害化しておいた方がよさそうです。外部からどのようなデータ(変数)が飛んでくるのか分からない分、受け取る際の処理には細心の注意が必要となってきます。

 次に注意するのは、データベースからデータを呼び出す場合です。
 例えば【ケース6】や【ケース7】のように、特殊文字をエスケープせずにデータベースに格納した場合、そのデータを呼び出してHTML表示する際には【ケース4】の処理が必要となります。

 セキュリティホールのチェック方法は以下の通り。
  1. 「$_」で検索をかけて、外部から受け取ったデータ(変数)を確認し、その後どのように使われているかを追跡する。ソースのメンテナンスのことを考え、外部から受け取ったデータ(変数)については、直後にエスケープすることを心がける。
  2. データベース利用時にWHERE節などにおいて変数を直接使う場合や、HTML表示時に<a href></a>や<form></form>内などにおいて変数を直接使う場合、それぞれの変数がエスケープされているかどうかを確認。

 整数の処理については、【ケース3】のように正規表現preg_replace()を使えば、思い通りの処理結果が得られますが、厳密性を求めずセキュリティ対策を重視する場合は、処理速度のことを考えるとis_numeric()やintval()を使った方がいいように思います。

 以上のセキュリティ対策に関する記述についてですが、勘違いしている箇所や間違っている箇所など多々あると思います。その際は指摘してくださると喜びます。
 また、以上のセキュリティ対策に関する記述を鵜呑みにせず、他の情報源(例:「MySQL 4.1 リファレンスマニュアル :: 4.3.1 一般的なセキュリティガイドライン」「サニタイズと言わない」など)と比較したり付き合わせた上でセキュリティ対策を実施してください。


【追記】

 下記ページをエントリーしたことに伴い、内容を見直しました。あわせてご覧下さい。(2008-02-28)

EDIUNET | PHP/MySQL | 独り言 | 提供サービス | JavaScript