htmlspecialchars/htmlentitiesの正しい使い方

(Last Updated On: )

追記:このエントリは古い情報です。今のHTMLエスケープの情報は以下の新しいエントリを参照してください。

PHPのHTMLエスケープ

PHP_SELFはそのまま出力できない

htmspecialchars($str, ENT_QUOTES);

じゃなくて、

htmspecialchars($str);

で終わらせてしまった場合の、
問題例が非常に欲しいです!!

とコメントを頂きました。

htmlspecialcharsとhtmlenties関数はENT_QUOTESを指定しないとENT_COMPAT(セキュリティ上問題があるが互換性を維持)が指定された状態と同じ動作をします。

ENT_QUOTESは”と’の両方をHTMLエンティティに変換するオプションです。ENT_COMPATは”のみHTMLエンティティに変換します。

JavaScript/HTMLの文字列は”と’の両方を利用できるので

<img src='foo.img' width='<?php echo htmlentities($width) ?>' />

と記述する事もできます。万が一、$widthに

300' style='xss:expression(bad_javascript_here)

の様な値が設定されてしまうと

<img src='foo.img' width='300' style='xss:expression(bad_javascript_here)' >

となりJavaScriptが実行されてしまいます。ENT_QUOTESを指定してればこのような場合でもJavaScriptは実行されません。

ENT_COMPATのオプション名からも分かるように、最初の実装では’をエンティティ変換しない危険性を知らなかったため”のみをエンティティ変換(エスケープ)するように実装してしまった、と思われます。本来はENT_QUOTESがデフォルトであるべきと思いますが、歴史的な経緯でENT_COMPATがデフォルトになっています。HTMLはXMLで定義されたXHTMLになっているので問題は少なくなってきていますが、いつかはENT_QUOTESをデフォルトにした方がよいかも知れません。

古いPHPでは、不正な文字エンコーディングを利用した攻撃を防ぐためにも3つ目の引数も指定する方が良いです。正しい(安全な)htmlspecialchars/htmlentiesの使い方は

htmlspecialchars($str, ENT_QUOTES, 'UTF-8');

等とするべきです。Webアプリセキュリティ対策入門にも同様の解説を書いていると思います。(書いていないかな?)

現在のPHP(PHP 5.6以降)はデフォルトの文字エンコーディングがUTF-8に設定されています。UTF-8を利用している場合、3つめの引数を指定する必要はありません。

一般論としてHTMLの属性値は”で囲むべきですが、過去の経緯もあってHTML5では”でも’でも使えるようになっています。不用意に’で属性を囲み、htmlspecialchars/htmlentitiesをENT_QUOTESオプション無しで使うとXSSに脆弱になります。

またXHTMLではクオートなしの属性値は規格外でしたが、HTML5ではクオートなしの属性値も許可されています。しかし、クオートなしの場合、エスケープしても意味がないのでプログラムから属性値を出力する場合、必ずクオートで囲むべきです。

HTMLの属性のみでなくPHPスクリプトからJavaScriptの変数に文字列を渡す場合に’を使い、htmlspecialcharsをオプション無しで使ってもXSSに脆弱になります。

<script>
var some_var = '<?php echo htmlspecialchars($str) ?>';
</script>

PHPのフレームワークの中にはENT_QUOTESオプションを付けていないフレームワークもあります。その場合、フレームワークの利用者は’を利用して文字列の定義するとXSSに
脆弱になる可能性があるので注意が必用です。

参考になれば幸いです。

追記:

出力には出力先のコンテクストに合わせたエスケープが必要です。例えば、JavaScript文字列にはJavaScript文字列用のエスケープが必要です。JavaScript文字列のエスケープ を参照してください。JSONの出力にもHTMLコンテクストで、誤ってHTMLの一部として解釈されないようなエスケープが必要です。JSONのエスケープを参照してください。

現在のPHPはhtmlspecialchars()でエスケープしても問題ありません。パフォーマンスを考慮するとhtmlentities()よりhtmlspecialchars()の利用をお勧めします。

現在のhtmlspecialchars()/htmlentities()は無効な文字エンコーディングの場合、空文字列を返す場合があります。このため、

  • 入力文字列は入力時点でバリデーションする
  • データベースやファイルの文字エンコーディングを統一する(統一できない場合は変換/バリデーションする)
  • Exif情報は無効な文字エンコーディングの文字列が入っている場合があるので、変換またはバリデーションする
  • その他、全ての文字列の文字エンコーディングが正しいことをバリデーションする

といった対策も必要になります。

投稿者: yohgaki

コメントは受け付けていません。