jQuery.ajaxを使って静的なJSファイルを取得しようとしたのですが、
取得するだけのつもりが勝手に実行されてしまいましたというお話。
しかも、こちらのコードのようにJSファイルが自らのJSファイルを読み込む機構にしてしまったため、意図せず無限ループを生み出してしまいました。
http://jsdo.it/butchi/GMl6
もっとわかりやすいコードで説明すると、main.jsがAjaxでtest.jsを取得したときに、
コールバックのalert(res)
が実行される前に
test.jsに記述したalert("hoge")
が実行されてしまいます。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Ajaxの罠</title>
</head>
<body>
<script src="jquery.js"></script>
<script src="main.js"></script>
</body>
</html>
main.js
$.ajax({
url: 'test.js',
success: function(res) {
alert(res); // => 'alert("hoge")'
}
});
test.js
alert('hoge');
種明かし
jQuery v2.1.4のコードをざっと見てみると、このような記述が見つかりました。
jquery.js
// Install script dataType
jQuery.ajaxSetup({
accepts: {
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
},
contents: {
script: /(?:java|ecma)script/
},
converters: {
"text script": function( text ) {
jQuery.globalEval( text );
return text;
}
}
});
(8651行あたりから抜粋)
たぶんレスポンスがスクリプトの形式だったときにdataTypeが'script'とみなされ、
evalしてしまう仕様のようです。
evalを避けるためには、dataTypeオプションを'text'とかに設定するとよいみたいでした。
main.js
$.ajax({
url: 'test.js',
dataType: 'text',
success: function(res) {
alert(res);
}
});
もっと古いコード
ちなみにv1.2.2について記述しているこちらの記事では
jQuery.httpData()
というメソッドが
httpData: function( r, type ) {
var ct = r.getResponseHeader("content-type");
var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
var data = xml ? r.responseXML : r.responseText;
if ( xml && data.documentElement.tagName == "parsererror" )
throw "parsererror";
// If the type is "script", eval it in global context
if ( type == "script" )
jQuery.globalEval( data );
// Get the JavaScript object, if JSON is used.
if ( type == "json" )
data = eval("(" + data + ")");
return data;
},
となっており、JSONに対してevalを使っていたみたいなので、
もしかしたらJSONファイルに任意のスクリプトを書いて実行できる脆弱性があったのかもしれません。
まとめ
jQueryはいろいろいい感じにやってくれる分、謎挙動も汲みながら扱っていく気概が必要ですね。