Hatena::ブログ(Diary)

葉っぱ日記 このページをアンテナに追加

2011-07-06

[] JSONのエスケープをどこまでやるか問題  JSONのエスケープをどこまでやるか問題を含むブックマーク

Ajaxなアプリケーションにおいて、サーバからJSONを返す場合に、JSON自体はvalidであるにも関わらず、(IEの都合で)エスケープが不足していて脆弱性につながってる場合があるので、書いておきます。

発生するかもしれない脆弱性

JSONのエスケープが不足している場合に発生する可能性のある脆弱性は以下の通りです。

  • JSON内に含まれる機密情報の漏えい
  • XSS

それぞれの詳細については後述します。

開発側でやるべきこと

文字列中の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"}

欠点としては、デバッグなどにおいて目視でJSONの内容を確認しにくいこと、サイズが大きくなることがあげらます。

発生するかもしれない脆弱性その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に昇格することを抑えることができます。

その他の対策方法

JSONを過剰にエスケープする以外にも、例えば

  • XHRからのリクエストヘッダに X-Requested-With: XMLHttpRequest などを付与し、サーバ側でそれを確認
  • POSTのみ受け入れ、GET要求ではJSONを応答しない。ただし、XSS対策には使えない。
  • JSONのフォーマットをinvalidなものにする。先頭に"while(1);"などを入れるなど。Google方式。ただし、XSS対策には使えない。

などの対策が知られています(下2つはあまり勧められない)。

それ以外の方法があれば教えてください。

まとめ

IE6だけ爆発しても解決しないので、IE7とついでにIE8も爆発すべき。IE8が抱える(「爆発すべき」に相当する)問題についてはもう少し影響度が下がってから(シェア減るのが先か、修正されるのが先か…)書きます。→ 書きました

*1:「拡張子ではなく、内容によってファイルを開く」を無効に設定していてもHTML扱いされます

名無し名無し 2011/07/06 19:08 先日つぶやいていたContent-Dispositonは対策方法として下ですか?

hasegawayosukehasegawayosuke 2011/07/06 19:12 Content-Disposition はそれはそれで問題があるので、XSS低減の気休めくらいには…。

s_hskzs_hskz 2011/07/13 10:49 IEの都合でエスケープが不足しているので「/」をエスケープ…の趣旨に疑問を持ちました。JSONのエスケープ方法を説明をしているRFCによればJSON文字列内では、「/」は既に「\/」としてエスケープされまくりずみのはずです。さらにユニコードエスケープシーケンスの形でエスケープしてしまうと、「\\u005C」あたりになると思われますが、これでは原始データを正しく表現しません。
それではと、別に考えて見ます。JSON仕様の「/」→「\/」のエスケープはせずに代替で「/」→「\u005C」というのが長谷川さんの意図であるとすると、その効用がわかりません。ETAGOのための「/」は「\/」へのエスケープにて既に使用不可能だからです。 本記事を読んで以来ここ数日悩みこんでいます。ご教示頂きたくお願い申し上げます。

hasegawayosukehasegawayosuke 2011/07/13 15:05 RFC4627に / を \/ にエスケープ「しなければならない」という記述ありましたっけ?

s_hskzs_hskz 2011/07/13 18:01 少なくとも " を \" にエスケープするのと同様な扱いとして、/ を \/ にエスケープするように記述されています。<RFC4627

hasegawayosukehasegawayosuke 2011/07/13 21:33 「エスケープするよう」ではなく「されてるかも」ですよね。もちろん「</」の注入を防ぐのが目的ですので、「/」が「\/」にエスケープされている場合は \u002F への再度のエスケープは不要です。

s_hskzs_hskz 2011/07/14 11:26 ご教示ありがとうございます。
solidus, backspace, form feed, line feed, carriage return, tab については、エスケープはMUSTではなくMAYだったのですね。
恥を忍んでお聞きしてよかったです。理解が深まりました。