クロスサイトスクリプティングについて
以前から多くのWebサイトの「クロスサイトスクリプティング脆弱性」が指摘されていますが、ここではWebサイトや書籍の情報を元に「PHPではどうすりゃいいの?」をまとめてみました。
間違いやご意見がありましたら、ご指摘下さい。また、一部クロスサイトスクリプティングとは違う話になってるかも知れません。。。
クロスサイトスクリプティングとは
Webページに入力データをおうむ返しに表示している部分があると,ページ内に悪意のスクリプトが埋め込まれ,それを見たユーザとサーバ自身の両方に被害を及ぼす「クロスサイトスクリプティング」という不正の手口に利用されてしまう(IPAセキュリティセンター(IPA/ISEC)のセキュア・プログラミング講座から引用)こと。
ざっくりな説明をすると、「入力・確認・トランザクションのような画面フローで入力された文字を確認画面でそのまま表示していたりすると、入力画面からJavaScriptなど実行可能なスクリプトを入力された時に確認画面でそれが実行され、悪意のあるサイトに誘導されたり、セッション情報やCookie情報などを入手される恐れがある」な感じです(これだけではないですが)。
詳細については以下のページにまとめて書かれていて、非常に参考になります。
PHPでの対策方法 その1(出力時のエスケープ)
HTML FORMからの入力内容をチェックする際、HTMLで危険とされる5つの文字(&,<,>,",')をエスケープすることでSCRPITタグなどを無効化します。基本的にHTMLに出力するときに一度だけ行います。
PHPの場合は、htmlspecialchars関数やmb_ereg_replace関数を使うことで、上記5つの文字を変換することができます。以下に入力された文字をエスケープした場合としない場合の違いを確認できます。特に「<script>alert("hello!");</script>」などと入力された場合です。
●PHPでのエスケープの例 xss01.php
<?php
function check($arr) {
$ret = $arr;
foreach($arr as $key => $val) {
$ret[$key] = htmlspecialchars($val);
}
return $ret;
}
$param = check($_POST);
if (isset($param["yourname"])) {
echo "あなたのお名前は、「" . $param["yourname"] . "」ですね?<br>";
}
if (isset($_POST["yourname"])) {
echo "あなたのお名前は、「" . $_POST["yourname"] . "」ですね?<br>";
}
?>
<form action="xss01.php" method="post">
お名前は?<input type="text" name="yourname">
<input type="submit">
</form>
ただし、上記の方法は「HTMLで危険とされる5つの文字(&,<,>,",')をエスケープする」だけですので、これらの文字以外で実行可能なJavascriptが入力された場合は対処できません(例:スタイルシート)。これの対策については、はてなダイアリーXSS対策を参照してください。
PHPでの対策方法 その2(セッション機能を使う)
PHP 4.3.1以下のバーションではセッションIDにHTMLタグを含めた場合にクロスサイトスクリプティングが可能になってしまう問題が報告されています。当該バージョンではsession.use_trans_sidを使用しないでください。
PHP4からセッション機能が標準で使用できるようになりましたが、このセッション機能を使い、入力された内容をHIDDENタグで受け渡ししないようにする(余計な情報自体をHTMLとして出力しないようにする)ことも有効だと思います。
以下のサンプルは、ありがちな「入力→確認→トランザクション」なフローですが、入力された文字はそのままセッション変数に格納し、HTMLとして出力する必要があるときにエスケープを行うようにします。また、セッションを使用しているので、HIDDENタグで情報を受け渡ししていません。
●セッションを使用した例 xss02_1.html
<?php
<form action="xss02_2.php" method="post">
お名前は?<input type="text" name="yourname">
<input type="submit">
</form>
?>
●セッションを使用した例 xss02_2.php
<?php
session_start();
if (isset($_POST["yourname"]) && $_POST["yourname"] != "") {
$_SESSION["yourname"] = $_POST["yourname"];
}
if (isset($_SESSION["yourname"])) {
echo "あなたのお名前は、「" . htmlspecialchars($_SESSION["yourname"]) . "」ですね?<br>";
}
?>
<form action="xss02_3.php" method="post">
<input type="submit" value="OK">
<input type="button" value="NG" onClick="history.back()">
</form>
●セッションを使用した例 xss02_3.php
<?php
session_start();
if (isset($_SESSION["yourname"])) {
$yourname = $_SESSION["yourname"];
else
die("名無しさんになってますよ<br>");
}
:
以下、何らかのトランザクション
:
?>
PHPでの対策方法 その3(php.iniの設定)
php.iniだけで防ぐことができるわけではないですが、何はともあれ、php.iniのregister_globalsを「Off」にしましょう(PHP4.2.xではデフォルトでOffです)。確かに、テキストボックスなどに指定した名前が受け取り側のPHPでの変数名として使用できる(global変数に登録される)のは便利ですが、全ての画面でGETパラメータを使って値を指定することができてしまいます。通常、WebサイトでのFORM Methodには「post」を使用すると思いますので、register_globalsをOffにすることで不要なGETパラメータを受け付けないようにすることができます。
次に、PHPのセッション機能を使用する場合は、php.iniのsession.cookie_lifetimeを「0(ゼロ)」にし、セッションIDを含むCookieの有効期限をブラウザ終了時にします。また、その他Cookieを使用する場合でも、特別な理由がない限り有効期限(setCookie関数のexpireで指定可能)を「0」にしましょう。
●setCookie関数の例
<?php
:
setCookie("key_name", "key_value", 0);
:
?>
その他(入力可能な文字・文字数の制限とサーバサイドでのチェック)
クロスサイトスクリプティング&PHPに限った話ではないですが、クライアントサイドでのチェックだけではなく、サーバーサイドでも基本的な入力文字・文字数チェックを行うようにしましょう。
特定の文字のみを受け付ける項目(ユーザーIDや郵便番号・日付など)があるとき、サーバ側でもereg関数などの正規表現関数を使ってチェックするようにしましょう。一度作成しておくと、いろいろと使い回せると思います。
|