PHP
セキュリティ
小ネタ
xss

「何故htmlspecialcharsを通すのか?」を一言でどうぞ

More than 3 years have passed since last update.


結論

Content-Type: text/plain な入力を Content-Type: text/html な環境の中に正しく出力させるため。

もう議論され尽くした内容だが、このような言い回しの人は見たことがなかったので敢えて記事にしてみる。


解説


  • ユーザがテキストボックスを通じて入力するのは text/plain、つまりただのテキスト。ただのテキストであるから装飾する機能は無い。

  • プログラマがユーザ入力を元にPHPで出力するのは text/html、つまりHTML。HTMLには見出しを示したり、太字にしたり、下線を引いたり、CSSやJavaScriptを呼び出す機能もある。


  • htmlspecialcharstext/plaintext/html に変換する。< >&lt; &gt;に変換するというのはあくまで一例に過ぎない、と考えるべきである。

通常、ユーザ入力は text/plain であるべきだ。但し、一部のタグや一部の属性のみを許可するといった処理を厳重なコーディングのもとに行っている場合、ユーザ入力がtext/htmlであると見なすことも許されるであろう。実績のある代表的なライブラリとしては HTML Purifier が挙げられる。


余談


  • 上記では簡略化のため説明を割愛したが、htmlspecialcharsは事実上第3引数の文字コード指定までは必須と言われる。以下のコードは text/plain; charset=UTF-8 から text/html; charset=UTF-8 への変換を行う。

$out = htmlspecialchars($in, ENT_QUOTES, 'UTF-8');


  • Content-Typeのうちcharsetのみを変換する場合も考えられる。以下のコードは text/plain; charset=Shift_JIS から text/plain; charset=UTF-8、あるいは text/html; charset=Shift_JIS から text/html; charset=UTF-8 への変換を行う。

$out = mb_convert_encoding($in, 'UTF-8', 'SJIS');


  • SQL文にユーザ入力を埋め込む際にhtmlspecialcharsを用いるのは不適切である。求められる変換は text/plain から text/html ではなく、text/plain から application/sql(における文字列リテラル) だ。この処理はプリペアドステートメントとそれに付随するプレースホルダが役割として担うものである。

$stmt = $pdo->prepare('SELECT * FROM table WHERE name = :name');

$stmt->bindValue(':name', $name);
$stmt->execute();


  • PHPで作るWebページは必ずtext/htmlになるのか?と言われればそうではなく、text/plainとして出力することも可能である。この場合htmlspecialcharsは不要になる。

<?php

header('Content-Type: text/plain; charset=UTF-8');
echo '<script>alert(1)</script>'; // IE以外ではJavaScriptは実行されない (コメント欄参照)