JSONのエスケープをどこまでやるか問題
Ajaxなアプリケーションにおいて、サーバからJSONを返す場合に、JSON自体はvalidであるにも関わらず、(IEの都合で)エスケープが不足していて脆弱性につながってる場合があるので、書いておきます。
開発側でやるべきこと
文字列中のUnicode文字は "\uXXXX" な形式にエスケープするとともに、ASCIIな範囲であっても「/」「<」「>」「+」も同様にエスケープすることにより、前述の脆弱性を防ぐことができます。
Perlであれば、以下のような感じになります。JSON->ascii(1) に続けて、JSON文字列を正規表現で置換しているあたりがキモになります。
use utf8; use Encode; use JSON; my $user = { 'mail' => 'hasegawa@example.com', 'name' => 'はせがわようすけ', 'bio' => '<script>alert(1)</script>', }; my $json = new JSON; my $json_text = $json->ascii(1)->encode( $user ); $json_text =~ s/([<>\/\+])/sprintf("\\u%04x",ord($1))/eg; print "$json_text\n";
{"name":"\u306f\u305b\u304c\u308f\u3088\u3046\u3059\u3051","mail":"hasegawa@example.com","bio":"\u003cscript\u003ealert(1)\u003c\u002fscript\u003e"}
発生するかもしれない脆弱性その1: 機密情報の漏えい
例えばWebメールにおける新着一覧などのように、JSON内に第三者に知られてはいけない機密情報と攻撃者自身がコントロール可能な文字列の両方が含まれている場合(Webメールの新着情報であれば攻撃者はメールを送るだけでそういう状況を作り上げることができます)、攻撃者は罠ページに被害者を誘導することによりJSON内の機密情報にもアクセスすることが可能になります。
JSONが以下のようなものだったとします。
[ { "name" : "abc+MPv/fwAiAH0AXQA7-var t+AD0AWwB7ACIAIg-:+ACI-", "mail" : "hasegawa@example.jp" }, { "name" : "John Smith", "mail" : "john@example.com" } ]
JSON内にはふたつのユーザ名とメールアドレスが含まれており、hasegawa@example.jp のほうの name は、攻撃者自身がFrom:等に設定することにより挿入したものです。
攻撃者はこのようなJSONをJavaScript ソースとして指定した罠ページを用意し、IE6/7を使用している被害者を誘導します。
<script src="http://target.example.com/newmail.json" charset="utf-7"> <script> alert( t[ 1 ].name + t[ 1 ].mail ); </script>
IE6、IE7においてはJSONの応答においてレスポンスヘッダで Content-Type: application/json; charset=utf-8 のように文字エンコーディングを指定していたとしても、罠ページ内の <script> 要素の charset 属性が優先されてしまい、結果として上で示したJSONはIE6、IE7では以下のように解釈されます。
[ { "name" : "abc"}];var t=[{"":"", "mail" : "hasegawa@example.jp" }, { "name" : "John Smith", "mail" : "john@example.com" } ]
結果、変数 t にJSON内の後続文字列が格納され、JavaScriptから自由にアクセスできるため、攻撃者はJSON内に含まれる機密情報(攻撃者自身がコントロールできない範囲の内容)を知ることが可能になります。IE8以降においてはこの問題は解決されています。
発生するかもしれない脆弱性その2: XSS
よく知られているIEのContent-Type無視病を使うと、JSONをHTMLと誤認識させることによってXSSを発生させられることがあります。
例として、<script> のようなHTML文字列の断片を含むJSONを http://utf-8.jp/cgi-bin/json-xss.cgi に用意しました。
Content-Type: application/json; charset=utf-8 X-Content-Type-Options: nosniff; { "foo" : "<script>alert(document.location)</script>" }
Content-Type として application/json を応答していますが、IEにとってapplication/jsonというContent-Typeは未知のものであるため、URLに少し細工を施し http://utf-8.jp/cgi-bin/json-xss.cgi?a.html のようなかたちでIE6、IE7 でアクセスすると、URLからコンテンツがHTMLであると判断され*1XSSが発生します。
なおIE8では、X-Content-Type-Options: nosniff を指定することにより、非HTMLがHTMLに昇格することを抑えることができます。
その他の対策方法
- XHRからのリクエストヘッダに X-Requested-With: XMLHttpRequest などを付与し、サーバ側でそれを確認
- POSTのみ受け入れ、GET要求ではJSONを応答しない。ただし、XSS対策には使えない。
- JSONのフォーマットをinvalidなものにする。先頭に"while(1);"などを入れるなど。Google方式。ただし、XSS対策には使えない。
などの対策が知られています(下2つはあまり勧められない)。
それ以外の方法があれば教えてください。
まとめ
IE6だけ爆発しても解決しないので、IE7とついでにIE8も爆発すべき。IE8が抱える(「爆発すべき」に相当する)問題についてはもう少し影響度が下がってから(シェア減るのが先か、修正されるのが先か…)書きます。→ 書きました
*1:「拡張子ではなく、内容によってファイルを開く」を無効に設定していてもHTML扱いされます