配列データを一気にクロスサイトスクリプティング対策する関数(php)
プログラムを書く上でXSS(クロスサイトスクリプティング)対策はとても面倒であり、また抜けが多いし、確認するのに人を大量投入もしくはツールを導入出来れば良いが納期と予算的な兼ね合いでそうもいかない。
「XSS対策に関しては出力時に処理するのが最善の策。最初期にやるのが次善の策。」とXSS対策に詳しい方が述べている。
いろんなサイトを見ると一気にデータを処理するのではなく変数を出力時に個別にエンティティ化させている。
しかし、出力時に個別に処理するのは、人為的なミスが混入する割合が高いと個人的に考えている。
予算も人も期間も投入できない環境では、言い訳になるかもしれないが出力時に個別にやるのは抜けが多そうで怖い。
デバッグもデータを加工する部分が一箇所にまとまっていると楽である。
そこで一気にXSS対策を行う関数を作成してみた。
$_POSTや$_GET、$_COOKIE、$_SERVERなどの外部から与えられるパラメータをこの関数を使い一気にエスケープ処理し、表示はエスケープしたものを使うことによりXSS問題を防ぎます。(keyは共通して使える)
SQLインジェクション対策したデータも別に用意し、出力前に$_POSTなどをunsetしちゃうと良いかも?
keyも出力に使うことがあるのでkeyもエスケープ処理しています。
これで良いかと言うと駄目でちゃんと値をチェックしなくてはいけません。
追記
追記としてコード説明の前に注意点を先に書いておきます。
書いた後、様々なサイトを見ていると「XSS対策に関しては出力時に処理するのが最善の策。最初期にやるのが次善の策。」をようやく理解できた気がする。入力時処理するのをサニタイズ脳というらしい。。。この方法だと以下の欠点がある。
- そもそも無害化して通すのではなく、エラーとして弾くべきである
- エンティティ化した文字列同士を連結した時、本当に安全か?可変な出力文字列全体をhtmlspecialcharsしたほうが安全性は保証される。
- SQLインジェクション対策を行ったデータと、XSS対策を行ったデータを別々に用意することは逆に複雑化させる
- もしエンティティ化した文字列をDBに入れると処理がまた面倒
- 知らない人が二重にエンティティ化してしまう可能性がある
- 逆に注意散漫になる
こんなものでしょうか?自分の考えも含めています。DB-アプリ間データ受け渡し、アプリ-出力間データ受け渡し、文字列連結時のお約束をきちっとする。データのチェックや加工・保存はエンティティ化の前にやるといったことをしていれば良いのではないかと思います。MVCでいうVにデータを渡す前に処理するのが良さげ。出力時にまとめてエンティティ化することは否定されていない気がするので。。。
サニタイズ脳の方への参考URL
- 「サニタイズ言うなキャンペーン」とは何か
- 続・「サニタイズ言うなキャンペーン」とは
- WASF Times版「サニタイズ言うな!」
- サニタイズ言うなキャンペーンが理解できない人の考え方
- XSSの脆弱性を限りなくなくす方法(似た考えでブログを書いていました。)
パラメータ説明
- $data: XSS対策したい数値や文字列、配列を渡します。
- $charset: htmlentities関数のcharsetパラメータを参照してください。エンティティ化しない場合は気にする必要がありません。
- $quote_style: htmlentities関数のquote_styleパラメータを参照してください。
ソースコード
function rhe($data, $charset='UTF-8', $quote_style=ENT_QUOTES)
{
//配列だったら
if (is_array($data)) {
foreach ($data as $key => $val) {
//keyがエスケープされた場合、重複するため一旦データを削除
unset($data[$key]);
//文字列の文字コードを同じ文字コードへ変換し、有効ではない文字を取り除く
$key = mb_convert_encoding($key, $charset, $charset);
//エンティティ化
$key = htmlentities($key, $quote_style, $charset);
if (is_array($val)) {
//値が配列の場合、再帰的に配列を処理
$val = rhe($val, $charset, $quote_style);
} else {
//文字列の文字コードを同じ文字コードへ変換し、有効ではない文字を取り除く
$val = mb_convert_encoding($val, $charset, $charset);
//エンティティ化
$val = htmlentities($val, $quote_style, $charset);
}
//値を再設定
$data[$key] = $val;
}
//エンティティ化した配列を返す
return $data;
} else {
//文字列の文字コードを同じ文字コードへ変換し、有効ではない文字を取り除く
$data = mb_convert_encoding($data, $charset, $charset);
//エンティティ化して返す
return htmlentities($data, $quote_style, $charset);
}
}
使用例
//テストデータのセット
$_POST = array(
"key1" => '<a href="#" onclick=javascript:alert(\'XSS\')>example2</a>',
"key2" => '<A href="#" ONCLICK="javascript:alert(\'XSS\')">example2</a>',
"key3" => '<a href=\'#/hello.cgi?name=<script>alert("XSS");</script>\'>aaaa</a>',
"key4" => '<a href="#" onclick=\'javascript:alert(\'XSS\')\' onclick=\'javascript:alert(\'XSS\')\'>example2</a>',
"<b>key5</b>" => '<a href="#" onclick=\'javascript:alert(\'XSS\')\' onclick=\'javascript:alert(\'XSS\')\'>example2</a>',
"array_key" => array(
0 => '<i>あいうえお</i>',
1 => '<pre class="brush:php;">bbbbb</pre>',
2 => '<font size="7">aaaaa</font>',
3 => "<font size='7'>ccccc</font>",
),
);
//一気にエスケープ
$data = rhe($_POST,mb_internal_encoding());
//使われないように、元データを消去
unset($_POST);
//デバッグ出力
myPrint_r($data);
サーバー変数$_SERVERのHTTP_REFERER、HTTP_USER_AGENT、PHP_SELF、PATH_INFO、PATH_TRANSLATEDなども外部から値を与えることが出来るためXSS脆弱性が存在します。$_SERVERも全てエンティティ化するのが良いのかもしれません。
inputやselectなどで使われるname属性、これらもHTML書き換えによって簡単に書き換えることが出来ます。
注意
- htmlspecialchars関数やhtmlentities関数とは引数の順番が違います。(charsetを渡しやすくするため)
- SJIS環境ではまだ問題があるかもしれません。
関数名の由来
「リカーシブル(再帰的)・HTML・エンティティ」の略です。名前の由来を知ると関数名が覚えやすいので書いておきます。(汗
お勧めXSS対策ライブラリ
以下のライブラリはhtmlタグや属性を許可しながらもXSS対策してくれるという優れたライブラリです。
php5でGPLを組み込むことが許されているならば使うと幸せになれるかも?
基本、中のドキュメントやtestサンプルを参考。Webでの日本語ページで一番とっつきやすい解説は以下のページです。
フォーム要素でHTMLタグを許可したいときのXSS・JavaScript対策
こういうものはセキュリティの脆弱性を無くすためにGPLでは無く本当に自由なライセンスで配布して欲しいものです。。。
注意
値のチェックは必要です。
参考URL
- マルチバイト文字とXSS脆弱性
- HTMLエスケープ方法を見直そう htmlspecialcharsとhtmlentities
- SQLインジェクション対策をするから起こる、セカンドオーダーSQLインジェクションとその対策
- $_SERVER['PHP_SELF']とXSS脆弱性
- XSS (Cross Site Scripting) Cheat Sheet(英語)
- HTMLエスケープを簡単にする独自のPHP関数(元ネタ、公開されている関数を元に改良)
参考URLに抜けが多い気が。。。(汗
最後に
前置きが長くて申し訳ありません。記事を書いた後、再確認のためにサイトを見てまわった後、少し書き直しています。
コードに問題がある場合はメールでもコメントでもなんでも良いのでお教えください。またもっと効率が良いコードなども募集しています。ポインタでも良いのでお教えください。
ふっくんのブログっぽいサイトから記事の移行と共に、コードの見直し修正を行いました。
あわせて読む
コメント投稿
1件のトラックバック
PHPセキュリティ対策:正規化した文字列を取り出す
入力された文字をそのまま表示すると、タグ安来る婦トガ実行されてしまう場合がある。そこで、PHPで正規化した文字列を取り出すプログラムを作ることにする。