問題発生の経緯
- HTML, PHP, MySQL の文字コード設定がEUC-JPであったため(だと思うが)、絵文字や一部記号が数値文字参照("&#〜〜")でDBに登録されていた。
- 動作テスト中に、DB内にXSSが仕込める事に気づく。
- こちらを参考に、
htmlspecialchars
をecho
にかませる事でXSSを回避した。 - 数値文字参照用の
&
までエスケープされたために今度は絵文字が表示されなくなった
何を持って解決と呼ぶか
今回は営利目的でなく、実行速度の最適化も検討せず、またスパゲッティの生産に対して寛容な状況であったため、「XSSを回避しつつ、顔文字が表示される」という事のみをとりあえず満たせば達成ということにした。
対策
htmlspecialchars
でエスケープされる文字<
、>
、"
、&
のうち、&
以外がエスケープされていれば上記解決に至る、ということで、str_replace
で再置換をかけることにした。
下記実装。
function h($str) { return str_replace('&', '&', htmlspecialchars($str, ENT_QUOTES, 'EUCJP')); }
おわり
まだまだ浅学非才の身ですので、皆様からの心温まるマサカリをお待ちしております。
コメント
@mpyw 2015-07-20 13:57
個人的にh3の実装が一番しっくりきます。
<?php function h1($str) { return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); } function h2($str) { return str_replace('&', '&', htmlspecialchars($str, ENT_QUOTES, 'UTF-8')); } function h3($str) { return preg_replace('/&(?=#[\d;])/', '&', htmlspecialchars($str, ENT_QUOTES, 'UTF-8')); } header('Content-Type: text/html; charset=UTF-8'); $raw = '& is converted into & or & in <html></html> context. I like 🍛 & 🍣.'; $h1 = h1($raw); $h2 = h2($raw); $h3 = h3($raw); echo nl2br(print_r(compact('raw', 'h1', 'h2', 'h3'), true), false);
…これでいいかなぁと思ったんですがこれでもまだ厳密には不十分ですね。なぜなら本当にユーザが置換される範囲を求めたところで、置換される文字を表すHTMLエンティティを直接入力されると完全に区別がつかなくなるのでどうしようもないですね…&#...;
と入力する可能性も考えられるからです。100%正しい動作にするにはMySQLが勝手にHTMLエンティティ形式に置換する文字の範囲を求めなければなりません(そこまでしなくても運用上問題ないとは思いますがw)