XSS対策は「エスケープの順序」で決まる
「エスケープしているのにXSSが起きる」コードには共通点があります。エスケープと整形の順序が壊れているのです。
だめな順序
// 悪い例: 整形してからエスケープ
$html = makeLinks($userText); // ここで <a href="..."> を生成
$html = htmlspecialchars($html); // 生成した <a> ごと潰れる…
これでは整形が無意味になるので、実務ではしばしば逆転され、
// もっと悪い例: エスケープしてから…と見せかけて生の値を混ぜる
$html = '<a href="' . $url . '">' . htmlspecialchars($text) . '</a>';
$url が素通りします。javascript:alert(1) を渡せば発火します。
安全な順序
- 本文全体を最初にエスケープする
- エスケープ済みの文字列に対して整形(リンク化・強調など)を行う
- 整形が挿入するタグは、プログラムが組み立てた安全な断片のみ。属性値(URL等)は許可スキームを検査してから入れる
この順序なら「本文由来の文字がタグとして解釈される経路」が存在しなくなります。
URLの検査を忘れない
href/src に入る値は、エスケープとは別にスキーム検査が必要です。
- 許可:
https:http:mailto:および相対パス - 拒否: それ以外すべて(
javascript:だけを名指しで弾く方式は、javascript:のような表記揺れに抜かれます)
「何を弾くか」ではなく「何を通すか」で書く。拒否リストではなく許可リスト。
自作のMarkdown変換器でも、この2点(順序と許可リスト)を先に固めてから記法の実装に入りました。順序は後から直すのが最も難しい設計判断だからです。