XSS対策:JavaScriptのエスケープ(その2)

5/11の日記XSS対策:JavaScriptなどのエスケープ - ockeghem(徳丸浩)の日記に対する金床さんのコメントに触発されて、JavaScriptエスケープについて検討してみよう。ただし、現実のアプリケーション開発においては、私はJavaScriptの動的生成を推奨していないが、これはエスケープ処理をどのように考えるかと言うレッスンのつもりで検討することにする。

金床さんのコメントで紹介されたリンクには、以下のようなガイドライン案が提案されている。

JavaScriptの文字列でのエスケープ手順としては、以下が今のところ正解っぽい感じです。

1. 「\」を「\\」に置換する
2. 「"」を「\"」に置換する
3. 「'」を「\'」に置換する
4. 「/」を「\/」に置換する
5. 「<」を「\x3c」に置換する
6. 「>」を「\x3e」に置換する
7. 「0x0D(CR)」を「\r」に置換する
8. 「0x0A(LF)」を「\n」に置換する

私がこれを見た印象は、JavaScriptおよびHTMLにて問題になりそうな特殊文字や制御文字をJavaScriptの書式を利用してエスケープしたのだろうな、ということであった。

しかし、前にも書いたように、HTML中のJavaScriptということであれば、以下のように考えなければならない。
下図は、HTMLデータ中のスクリプトをブラウザが処理する手順を模式的に示したものである。

この図のように、ブラウザはHTMLデータ中からまずは(JavaScriptなどの)スクリプトを切り出し、その後言語処理の過程で文字列リテラルをアンエスケープ処理する。
したがって、元のJavaScriptソースは、上記の逆順のエスケープ処理を施す必要がある。

JavaScriptとしてのエスケープ

JavaScriptの文字列は、

"●●●●●●●●●●●●●●"
あるいは
'●●●●●●●●●●●●●●'

であるので、引用符(「'」および「"」)と、エスケープに使用する文字「\」の三種類が必要最低限のエスケープ対象となる。
金床さんは、改行文字(0x0dと0x0a)もエスケープ対象としているが、これはちと議論の分かれるところである。なぜなら、

  • 改行文字が現れることの妥当性
  • HTML中では改行は無視されるので、エスケープしてもあまり意味がない

改行を含む文字列の場合、
などのレンダリングを施すべきではないか。レンダリングしない場合は、すなわち改行が入るはずがないということで、改行を含むコントロールコードは、エラーにしたいところだ(ただし要求仕様に依存する)。
金床さんは、これら以外に「/」「<」「>」のエスケープを正解としているが、これは明らかにJavaScriptの要求するエスケープではないので、次節にて検討する。

HTMLとしてのエスケープ

次に、HTMLとしてのエスケープを施す必要がある。これは、スクリプトの置かれる場所によって、以下の二種類の処理に分かれる。

以下順に検討しよう。

イベントハンドラの場合

イベントハンドラ内におかれる場合は、属性値に対するエスケープ(「"」、「&」などに対する文字参照)を実施する。

<SCRIPT>タグ内の場合

<SCRIPT> タグの内部では、文字参照によるエスケープはできないので、前回説明したように、「</[a-zA-Z]」を別の文字に置き換えるという代替処理を施す。
T.Teradaさんの指摘にあったように、IEがヌル文字を無視するという仕様なので、安全サイドで考えて「/」(スラッシュ)を「\/」にエスケープする(JavaScriptとして)のが良いだろう。このエスケープ自体には副作用はないので、スクリプトの置き場所に関わらず、「/」のエスケープを行うことにして問題ない。
金床さんは、「<」と「>」のエスケープも推奨されているが、JavaScriptには元々不等号としてこれらの記号が使えるし、書こうと思えば、

<SCRIPT>
var SCRIPT=1;
if (1 <SCRIPT> -1) {
...
</SCRIPT>

なんてコードも合法的に書けるので、「<」と「>」を無条件にエスケープしても仕方ないかなと思う。

まとめ

JavaScriptを動的生成する場合における文字列リテラルエスケープについて検討した。HTMLとJavaScriptは異なる文法を持っているので、両者を意識して二段階のエスケープが必要となる。まず、JavaScriptについては(最低)引用符(「'」および「"」)と、「\」、「/」の四種類に対するエスケープを行う。加えて、イベントハンドラにおかれる場合はHTMLとしてのエスケープ(文字参照)を実施する。
ただ、冒頭に書いたように、上記はエスケープに対するモデルとして検討したのであって、現実の開発においてはJavaScriptの動的生成をせず、hidden field値を参照するべきである。

追記

 JavaScriptの文法で、「/」に対するエスケープが認められていたっけと思い色々調べた範囲では、「/」に対するエスケープ法の記述は見当たらなかった。現実のブラウザ(IE, Firefox, Opera)では、期待通り動作するもののちと気持ちが悪い。W3CのHTML4.01仕様でも「/」のJavaScrpitエスケープを「\/」とするように書いているが、博雅の士のご教授を請いたい。