ささきしき

チラシ

【化石】htmlspecialcharsで文字参照まで無効化したときの対策

問題発生の経緯

  • HTML, PHP, MySQL文字コード設定がEUC-JPであったため(だと思うが)、絵文字や一部記号が数値文字参照("&#〜〜")でDBに登録されていた。
  • 動作テスト中に、DB内にXSSが仕込める事に気づく。
  • こちらを参考に、htmlspecialcharsechoにかませる事でXSSを回避した。
  • 数値文字参照用の&までエスケープされたために今度は絵文字が表示されなくなった

何を持って解決と呼ぶか

今回は営利目的でなく、実行速度の最適化も検討せず、またスパゲッティの生産に対して寛容な状況であったため、「XSSを回避しつつ、顔文字が表示される」という事のみをとりあえず満たせば達成ということにした。

対策

htmlspecialcharsエスケープされる文字<>"&のうち、&以外がエスケープされていれば上記解決に至る、ということで、str_replaceで再置換をかけることにした。
下記実装。

function h($str) {
    return str_replace('&amp;', '&', 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('&amp;', '&', htmlspecialchars($str, ENT_QUOTES, 'UTF-8'));
}
function h3($str) {
    return preg_replace('/&amp;(?=#[\d;])/', '&', htmlspecialchars($str, ENT_QUOTES, 'UTF-8'));
}

header('Content-Type: text/html; charset=UTF-8');

$raw = '& is converted into &amp; or &amp in <html></html> context. I like &#127835; & &#127843;.';
$h1  = h1($raw);
$h2  = h2($raw);
$h3  = h3($raw);

echo nl2br(print_r(compact('raw', 'h1', 'h2', 'h3'), true), false);

…これでいいかなぁと思ったんですがこれでもまだ厳密には不十分ですね。なぜなら本当にユーザが&#...;と入力する可能性も考えられるからです。100%正しい動作にするにはMySQLが勝手にHTMLエンティティ形式に置換する文字の範囲を求めなければなりません(そこまでしなくても運用上問題ないとは思いますがw)置換される範囲を求めたところで、置換される文字を表すHTMLエンティティを直接入力されると完全に区別がつかなくなるのでどうしようもないですね…