JSONデータをクロスドメインアタックから守るためにwhile(1)を使うことをやめましょう

これはなに

既にご存知の方がいらっしゃるかどうかも知りませんが今さっき関連文献に行き当たって驚愕したので念のためにメモを書いておきます。私はメーリングリストなどに加入していませんので論議が済んでいるかどうかも知らないのです。もしもウェブ上に解説記事があるようでしたら逆に私に是非とも教えてください。

JSONデータの先頭に

JSONデータの先頭にwhile(1)を置いておくことで無限ループを発生させておいて、受動的攻撃のページの悪意あるscriptの実行を失敗させるというアイデアには欠点があるということを、先程とある文献から知りました。これはwhile(true)についても同様です。JavaScriptは柔軟で強力な言語ですから、ブラウザがJavaScriptエンジンをまじめに実装しているのであれば、アタックのチャンスを与えていることになります。しかしこれはブラウザの脆弱性とは捉えられません。そもそもJavaScriptがそのような設計なのですから。攻撃者は、たくみに作成されたページによって読み込まれた外部script(要するにターゲットとなるJSONデータがはいっているJSファイル)の先頭にあるwhile(1)を無効にしてあたかもwhile(0)であるかのようにしてしまうことが可能です。同様にwhile(true)はwhile(false)と変貌してしまいます。無限ループになりません。かくしてデータが盗まれることになります。NumberやBooleanの意味をコンストラクタ経由で書き換えるのだそうです。JavaScriptHijackingという名前で有名な先行事例のArrayの時と手法がほぼ同じですね。

現時点で特定ブラウザに適用可能なexploitが発見されているというわけではありません。しかしながらそのような蓋然性がJavaScriptの言語仕様とその実装によってはありうるということなのです。

簡単な解決策

while(1)に替えて、for(;;)を使います。 まともなJavaScriptエンジンはforの上書きを許しません。JavaScriptの言語の設計にはそのような発想はないからです。従って意図した無限ループを破壊できません。先程私が読んだ文献の著者の研究者によれば全てのブラウザで検査済みだそうです。

もっと強力な解決法

HTTPのGETを受けてのJSONデータを配信すべきではありません。サーバではPOSTのみを受け付けるべきです。ブラウザ側のXHRでもPOSTしてやればよいのです。この時、無限ループによる防衛は意味はないので取ってしまっても良いでしょう。

追記(3/30)

Kanatokoさんや徳丸さんに情報ソースを教えていただきました。大変ありがとうございました。Billy Hoffman (著), Bryan Sullivan (著), GIJOE (監修, 翻訳), 渡邉 了介 (翻訳) の、『Ajaxセキュリティ (単行本(ソフトカバー)) 』だそうです。hoshikuzu はとある会社の取引先経由で該当部分を含むいろいろな資料のPDFを頂いたのですが、さっそく上記の本を見てみたいと思います。ただし、徳丸さんによれば肝心のセキュリティ対策部分については弱い書籍なのだそうです。購入、どうしましょうか…経費で落ちないんですよね。JSON-Hijackについてはsetterやgetterを使わない手法が書いてあるというのでそこだけでも見たい気がしますが…

追記(3/31)

Billy Hoffman氏による、『配列型の』JSONデータのHIJACKINGに関するPoCを以下に。上記著書からの引用です。(数値コンストラクタに関するPoCは現存していないかと思います)


Here we have a literal array defined. Internally, the JavaScript interpreter calls the array constructor function Array() to create an array object from the supplied array literal. Next, the JavaScript interpreter checks to see if an operation is performed on this array. For example, [1, 2, 3].join(",") is perfectly valid JavaScript code. However, in this block of JavaScript no operations are performed on this array. Because the array object was never assigned to a variable, it is unreferenced, and the object will eventually be cleared by the JavaScript interpreter's garbage collection routines. Thus, by pointing a SCRIPT tag at an Ajax endpoint on a Web server that returns JSON, we can force the JavaScript interpreter to execute the array constructor function Array().
We know from the "Hijacking Ajax Frameworks" section earlier in the chapter that JavaScript code can clobber other functions, including internal functions. It turns out we can clobber the Array() function as well! An attacker can replace the array constructor with their own malicious version, which can capture all the contents of the array and send them to a third party and capture the method. Consider the following piece of code.

function Array() {
var foo = this;
var bar = function() {
var ret = "Captured array items are: [";
for(var x in foo) {
ret += foo[x] + ", ";
}
ret += "]";
//notify an attacker. Here we just display it
alert(ret);
};
setTimeout(bar, 100);
}

In our malicious array function, we set the variable foo equal to the current object (the array that is being created).We also create an anonymous function bar(), which will iterate all the properties of the variable foo (all the items stored in the array). All the data collected is simply displayed to the user, but it is trivial for an attacker to send this data to a third party using an Image object. The last thing the evil array constructor does is use the setTimeout() function to call our bar() function after 100 milliseconds. This ensures that by the time the bar() function is called, the elements in our array literal have been properly loaded into the array so that our bar() function can steal the contents.

hoshikuzu註::この間を大幅に省略しています。

Second, for(;;); consists of nothing but a JavaScript keyword and some symbols. There is no way an attacker can clobber or override the for keyword. Some people suggest using while(1);. This is not an ideal solution because 1 is a numeric literal, and it could be possible that some JavaScript interpreters would invoke the number constructor function Number () when a numeric literal is encountered. An attacker could conceivably use this=0; inside a malicious number constructor and literally redefine the value of 1, making the while conditional evaluate to false, which in turn causes the JavaScript interpreter to fall through to the JSON literal.
The same possibility could apply to using while (true); as an infinite loop and boolean literals. The authors currently know of no JavaScript interpreters that do this, but it certainly is possible that one might do so in the future. As a security-conscious developer, you must think not only about how to secure your application now, but how you can secure your application in such a way that minimizes the chance it will become insecure in the future due to technological changes.

…ところでこの本、電子ブックとしてネット上にあるのですが権利関係については調べられませんでした。すみません。宣伝になるから良しとしてください。(一方、本日記の上の方でなかばけなしてもいますが・・)

追記その2(3/31)

この本の日本語版ではArrayコンストラクタの改造によってJSONハイジャックが出きるのはFirefoxの2系列だというお話しなのですが、いやいや、IEではどうよ?という論議もネット上に出ているようです。Firefox2でもってgetterやsetterを使えなくともPoCが成立するのなら(最初のJSONハイジャックの手法はgetterやsetter関連を使っていたのでブラウザ独自の拡張に依存していましたからその制約がとれれば)という前提で、IEでも工夫次第では?という発想のようです。この論議についていけない私ではありますが…引用することだけでもしておきます。後学のために。


Calling the Array constructor in IE

I had a conversation a while ago on email with Billy Hoffman about how in IE the Array constructor wasn’t called when using [] to create arrays. The question is, was he right? Technically yes but actually no ;)

You see Arrays in JScript are actually objects and not arrays, so trying to overwrite the Array constructor will have no effect. However using the Object constructor does. I found this while hacking away in JSON to create my Twitter POC.

The is a strange quirk which although it technically is the same code it results in different behaviour. Take the following example:-


function Object() {
alert(arguments[0]);
}
([1,2,3]);

That doesn’t work but…look at this example:-


var Object = function() {
alert(arguments[0]);
}
([1,2,3]);

It works! Yay! Strange but true. Don’t ask how I found this but it was either by fuzzing, playing around in Hackvertor or pure luck ;)

どうやら、ビリー氏のPoCではfunction文を使っているのに対して、この論議では替わりにfunctionリテラルを使うと良いのでは?という提案のようですね。どうせならラムダ関数化すればいいのにとかいう妄想も吹き出てしまいますが。続くコメント欄の論議ではIEの他、Firefox3.02あたりでも通用する可能性を調べているようです。…さらに読み進めてみると結局Hijackが成立しないらしいと読解した私です。
ところでIt works! Yay!Strange but true.を読んだときに私の頭の中の翻訳ルーチンがバカボンのパパを呼び出してしまい、『不思議だが本当だ、本当だが不思議だ、これでいいのだ!』と叫んでいました。わろす。