Shift_JIS では、htmlspecialchars() を使用しても XSS が可能な場合がある

以下のページに関連して、htmlspecialchars() を使用している場合でも XSS が可能かどうか少し調べてみました。

その結果、いくつかのブラウザで文字エンコーディングShift_JIS を使用していた場合、XSS が可能なことを確認しました。
テストコードは以下の通りです。リンクにマウスポインタを乗せると埋め込んだ Javascript が実行されます。

<?php

$_GET['a1'] = "\xf0";	// \xf0 - \xfc で可能
$_GET['a2'] = " href=dummy onmouseover=alert(document.title) dummy=dummy";

header( "Content-Type:text/html; charset=Shift_JIS" );
?>
<html>
<head><title>Shift_JIS test</title></head>
<body>
<p><a title="<?php echo htmlspecialchars( $_GET['a1'], ENT_QUOTES, 'SJIS' ) ?>" href="<?php echo htmlspecialchars( $_GET['a2'], ENT_QUOTES, 'SJIS' ) ?>">test</a></p>
</body>
</html>

上記のコードで XSS が発生するのは、以下が原因となるようです。

  1. PHP の htmlspecialchars() では、SJIS(Shift_JIS) の場合、\xf0 - \xfc を単独で指定しても排除しない(PHP 5.3.0 で確認)
  2. 一部のブラウザでは、\xf0 - \xfc に続く1バイトを合わせて Shift_JIS の1文字として認識する(Mozilla Firefox 3.5.3 と Internet Explorer 8(8.0.6001.18813) で確認)
  3. このため、上記の例では、\xf0 の後ろにある "(ダブルクォート) が有効にならず、その後ろに任意の属性が追加できる

このため、ブラウザによっては、XSS が可能な場合があります。
他のブラウザとして、Google Chrome 3.0.195.24 で確認してみましたが、\xf0 - \xfc の後ろに不正な文字が続く場合では、XSS は発生しませんでした。

追記(2009.10.06)

コメントで指摘いただいたように、Shift_JIS の先行バイトに関連する記述が正しくなかったため、表現を修正しました。