■全てのユーザ入力は汚れていると思え
この位に考えてこそ、セキュアなアプリケーションが作れる。セッションを除く全てのスーパーグローバル変数はユーザ側から編集が可能であり、悪意のあるコードが挟まれる可能性がある。
■ホワイトリスト方式とブラックリスト方式
ホワイトリスト方式 | ブラックリスト方式 | |
---|---|---|
制限 | 高 | 低 |
攻撃耐性 | 高 | 低 |
ホワイトリスト方式は、プログラマーが用意したセキュアな形式の(リストの)データがアプリケーションで使われるので、ブラックリスト方式よりも安全であるといえる。ブラックリスト方式は未知のインプット形式が攻撃に使われる可能性があるので、フィルタリングの際には注意が必要である。
■フィルタリング
以下のようなフォームを考える。
<form method="post" action="register.php"> Name: <input type="text" name="name" /> Password: <input type="password" name="password" /> Gender: <select name="gender"> <option>male</option> <option>female</option> </select> <input type="submit" /> </form>
上述の場合、ユーザ入力のフィルタリングをするには以下のようにする。
<?php $genders = array('male', 'female'); $clean = array(); if(ctype_alpha($_POST['name'])){ $clean['name'] = $_POST['name']; } if(ctype_alnum($_POST['password'])){ $clean['password'] = $_POST['passwprd']; } if(in_array($_POST['gender'], $genders, true)){ $clean['gender'] = $_POST['gender']; } ?>
英語圏のアプリケーションの場合は上述のようなコードで構わないが、日本語の名前が入力される場合は不適当なコードとなる。また、パスワードは英数字を想定している。さらに、セレクトボックスから想定外のデータが飛んでくることも考えられるため、プログラム側で用意した配列と比較して、データの妥当性を調べる。
各メソッドについて
- bool ctype_alpha(string $text)
- 文字列が[a-zA-Z]の文字で構成されているかどうかをbool値で返す。
- bool ctype_alnum(string $text)
- 文字列が[a-zA-Z0-9]の文字で構成されているかどうかをbool値で返す。
■出力のエスケープ
ページ表示用変数のエスケープ
以下のように変数名にhtmlを用いてハッシュ構造とすれば、エスケープし忘れた変数を表示に使用してしまうような人為的ミスを回避できるかもしれない。
<?php $html = array(); $html['message'] = htmlentities($_POST['message'], ENT_QUOTES/*, 'utf-8'*/); ?>
データベース用のエスケープ
以下のように、*_escape_string関数を使っても良いが全ての変数を忘れずにエスケープしなくてはならない。また、セキュリティの観点とは異なるが、データベースを変更する際にコードを書き換える手間が増える。
<?php $dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS); mysql_select_db(DB_NAME, $dbh); $name = mysql_real_escape_string($_POST['name']); $age = mysql_real_escape_string($_POST['age']); $result = mysql_query("INSERT INTO `tbl`(`name`, `age`) VALUES('$name', '$age')"); ?>
但し、エスケープし忘れるリスクを減らすため、以下のようにプリペアドステートメントをできる限り使用する。プリペアドステートメントを用いれば常にエスケープされる。
<?php try { $dsn = 'mysql:host=localhost;dbname=db'; $dbh = new PDO($dsn, DB_USER, DB_PASS); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->setAttribute(PDO::EMULATE_PREPARE, true); } catch(PDOException $e){ print($e->getMessage()); } $stmt = $dbh->prepare("INSERT INTO `tbl`(`name`, `age`) VALUES(?, ?)"); $stmt->execute(array($_POST['name'], $_POST['age'])); ?>
OSコマンド用のエスケープ
escapeshellcmdなどの関数が存在するが、OSのコマンドを使うようなアプリケーションの設計を見直したほうが良いか、必ず確認すること。OSのコマンドを叩かれるのは非常に大きなリスクを伴う。
■レジスターグローバル
迷わずoffにするべきである。onでないと動かないアプリケーションは相当古いものであり、使用するべきではない可能性が非常に高い。
初期化されていない変数を使った場合に、プログラム実行時からその変数に値が格納されているという状態がアプリケーションの脆弱性になり得るために、PHP4.2.0以降デフォルトではoffにされた。PHP6ではこのディレクティブ自体が消滅する予定である。