From 02cc9992e915c024650ddc77a91064f7a4252914 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 23 Jan 2008 00:51:25 +0000 Subject: [PATCH] prototype: Prevent a potential security issue for cross-site ajax requests. --- CHANGELOG | 2 ++ src/ajax.js | 17 ++++++++++++++--- test/unit/ajax.html | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 37ac38e..cd5bdb2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Prevent a potential security issue for cross-site ajax requests. [Alexey Feldgendler, sam, Tobie Langel] + * Test for attribute existence before applying more complex CSS3 selectors. Closes #10870. [arty, Tobie Langel] * Fix "function $A" declaration inside of a conditional (confuses IE). Closes #10882. [Jacco, Andrew Dupont] diff --git a/src/ajax.js b/src/ajax.js index e576a1e..a1abed6 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -189,7 +189,7 @@ Ajax.Request = Class.create(Ajax.Base, { var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' - || (this.options.evalJS && contentType + || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } @@ -207,6 +207,15 @@ Ajax.Request = Class.create(Ajax.Base, { } }, + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; @@ -282,7 +291,8 @@ Ajax.Response = Class.create({ if (!json) return null; json = decodeURIComponent(escape(json)); try { - return json.evalJSON(this.request.options.sanitizeJSON); + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -295,7 +305,8 @@ Ajax.Response = Class.create({ this.responseText.blank()) return null; try { - return this.responseText.evalJSON(options.sanitizeJSON); + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } diff --git a/test/unit/ajax.html b/test/unit/ajax.html index 1d533ad..3744d34 100644 --- a/test/unit/ajax.html +++ b/test/unit/ajax.html @@ -410,6 +410,45 @@ } else { info(message); } + }}, + + testIsSameOriginMethod: function() {with(this) { + var isSameOrigin = Ajax.Request.prototype.isSameOrigin; + assert(isSameOrigin.call({ url: '/foo/bar.html' }), '/foo/bar.html'); + assert(isSameOrigin.call({ url: window.location.toString() }), window.location); + assert(!isSameOrigin.call({ url: 'http://example.com' }), 'http://example.com'); + + if (isRunningFromRake) { + Ajax.Request.prototype.isSameOrigin = function() { + return false + }; + + $("content").update('same origin policy'); + new Ajax.Request("/response", extendDefault({ + parameters: Fixtures.js, + onComplete: function(transport) { + assertEqual("same origin policy", $("content").innerHTML); + } + })); + + new Ajax.Request("/response", extendDefault({ + parameters: Fixtures.invalidJson, + onException: function(request, error) { + assert(error.message.include('Badly formed JSON string')); + } + })); + + new Ajax.Request("/response", extendDefault({ + parameters: { 'X-JSON': '{});window.attacked = true;({}' }, + onException: function(request, error) { + assert(error.message.include('Badly formed JSON string')); + } + })); + + Ajax.Request.prototype.isSameOrigin = isSameOrigin; + } else { + info(message); + } }} }); // ]]>