サーバサイドからクライアントサイドのJavaScriptを呼び出す際のベストプラクティス

JavaScript文字列のエスケープ – yohgaki's blog に対して、

最近だと id="hoge" data-foo="<% bar %>" しておいて $("#hoge").data('foo') でとりだすのが主流かと思います。

はてなブックマーク - JavaScript文字列のエスケープ – yohgaki's blog

のように、

  • そもそもJavaScriptコードを動的生成すべきでない
  • JavaScriptコードに渡す変数はHTMLノードを経由すべきだ

というような反論がついています。

が、はたしてそうでしょうか。

僕には、元の記事の手法も、HTMLノードを経由する手法もあまり好ましくない*1ように思えます。

そもそも、HTML生成時にXSS脆弱性が発生しがちなのは、

  • タグや静的な文字列と動的に生成される文字列が混在し
  • 埋め込まれる多数の文字列を正しくエスケープしなければならない

という事情ゆえです。これは、HTMLが人間が閲覧しやすいように生成されるものである以上、仕方がない制限です。

ですが、サーバサイドのプログラムからクライアントサイドのJavaScriptに情報を渡す場合には、この制限を気にする必要はありません。ですから、「データの受け渡し箇所を局所化する」ことで脆弱性の発生する可能性を低めるべきだと思います。

具体的には、どうすべきか。

ひとことでいうと、JSONPと同形式の呼出をサーバサイドで生成しSCRIPTタグとして埋め込む、という手法を採るべきだと思います。

実例をあげると、以下のような感じでしょうか。

<script>
function doit(argsAsJson) {
	...
}
</script>
<script>
doIt({ a: 123, b: "hello" }); // このステートメントを動的生成する
</script>

動的生成するコードはJSONPの場合とほぼ同じ。以下の手順で生成できます。

  1. データをJSONエンコード
  2. 呼び出す関数名と()をつけて
  3. 「<」「>」「&」をそれぞれ \uXXXX に全置換する*2

この方式のメリットは以下のとおりになります。

  • HTML文書内に、意味不明な情報が混ざらない
  • クライアントサイドのプログラムとデータが明確に分離されるので、テストがしやすい

デメリットとしては、エンコード手法が HTML の場合と異なるという点がありますが、先に述べたように JavaScript へデータを渡す場合は処理を局所化できるので、上記のメリットと比べると些細なことかと思います。

注: 全面的に書き換えました。元は https://gist.github.com/kazuho/7329584

*1:JavaScriptに渡すべき情報が増えるたびにHTMLテンプレートの修正が必要になるとか

*2:HTMLにおけるSCRIPTタグはCDATAであり、XHTMLの場合はPCDATAもしくはCDATAであるため