JavaScript文字列エスケープの解説

11月 3, 2013 PHP Security
(Last Updated On: 2018年8月4日)

JavaScript文字列のエスケープの解説というか補足です。

サンプルとして書いたescape_javascript_string関数は文字コード256未満の英数字以外は全てJavaScriptのHEXエスケープでエスケープ処理しています。いくら何でもエスケープし過ぎでは?と感じた方も居るかも知れません。

HTMLページに埋め込まれたJavaScriptコードは、最初にHTMLパーサーでパースされ、その後にコード部分がJavaScriptで処理されます。JavaScriptに利用できる文字コードはUnicodeだけなのですが、HTMLは複数の文字エンコーディングをサポートしています。今まで存在してきたHTMLパーサー(つまりブラウザ)の多くにはおかしなバグや仕様がありました。

例えば、制御コードがサニタイズされる、128以上のコードがサニタイズされる、などです。このような歴史的な経緯から英数字以外の文字は危険とみなし、全てエスケープ処理しています。デフェンシブプログラミングの一例です。 エスケープ処理をすると1バイトから4バイトへと一文字のデータ量は4倍になりますが、英数字以外の文字コードが現れる事は多くありません。実際のデータ量増加は無視できるほど小さいはずです。

確かOWASPのESAPIはどのような文字エンコーディングでも動作するようにUnicodeエスケープをしていたような気がします。(詳しい仕様はESAPIのコードを読んで下さい)サンプルのescape_javascript_string()はUTF-8を前提条件としているのでUnicodeエスケープしていません。このためスペース、効率の両方で良くなっていると思います。

ブラウザで処理されないJavaScriptプログラムを書いている場合はここまでエスケープする必要はありません。\エスケープでエスケープ処理が必要な文字だけエスケープするだけで十分です。しかし、Webブラウザから実行する場合には、HTMLの特殊文字を追加でエスケープ処理するより防御的にエスケープする方が安全です。

特にWeb環境(HTMLの中)では\によるエスケープは行わない方が良いことに注意しなければなりません。\”や\’ではブラウザのパーサがクオートのペアとして認識する可能性があるからです。

本来、エスケープ処理は単純であるべき物ですが単純でない場合もよくあります。XPath 1.0のエスケープは仕様の瑕疵により複雑になっています。HTML文書中のJavaScriptなどのように、複数のパーサーが関係しているようなコンテクストでは複雑になりがちなので特に注意が必要です。

簡単なテストの為に

php -r "PHP_CODE_HERE;" 

と実行している方も多いと思います。

この場合、シェルのエスケープとPHPのエスケープの両方を考えないと正しく動作しません。複数のパーサーが関わるためエスケープ処理が複雑になってしまう身近な例です。

サンプル関数を見て、解りづらいと感じた方も多いと思います。マルチバイト文字対応版のchr()とord()があればもっと解りやすくなります。取り敢えずmb_chr()やmb_ord()は前から必要だと思っていたのでbugs.php.netに登録して自分にアサインしておきました。

PHPのセキュリティ入門書に記載するコンテンツのレビューも兼ねてPHP Securityカテゴリでブログを書いています。コメント、感想は大歓迎です。

追記:
Railsでどうなっているか調べてみたらこうなっていました

HTMLのソースやブラウザにバグが無ければこれでも大丈夫なのですが、新しいブラウザも出てきます。OWASPの推奨に従ったエスケープの方法は間違い無く動作するのでこちらの方が良いと思います。

Railsのjavascript_escapeメソッドはセパレータ文字を2つ追加でHTMLエンティティ変換していますが、ECMAScript 5.1の仕様書にはエスケープが必要な文字としては挙げられていません。これの理由が分かりません。HTMLエンティティ変換しているので、ブラウザでUnicodeに変換されます。しかもHTMLエンティティ変換なので素のJavaScriptソース(JavaScritpだけのソース)に出力したら正しく処理されません。JavaScript用にエスケープするならHTMLエンティティ変換でなく、JavaScriptのUnicodeエスケープで \u2028 とすべきです。バグのようですが、理由を知っている方は教えて下さい。

投稿者: yohgaki