Skip to content

Commit 8c44135

Browse files
committed
Merge branch 'master-sec'
2 parents 4574a79 + 7146dc9 commit 8c44135

File tree

6 files changed

+60
-53
lines changed

6 files changed

+60
-53
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## v1.0.4
2+
3+
* Fix CSP bypass vulnerability.
4+
5+
CVE-2015-1840.
6+
7+
*Rafael Mendonça França*
8+
19
## v1.0.3
210

311
* Replace deprecated `deferred.error()` with `fail()`.

src/rails.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,14 @@
9494

9595
// Default way to get an element's href. May be overridden at $.rails.href.
9696
href: function(element) {
97-
return element.attr('href');
97+
return element[0].href;
9898
},
9999

100100
// Submits "remote" forms and links with ajax
101101
handleRemote: function(element) {
102-
var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;
102+
var method, url, data, withCredentials, dataType, options;
103103

104104
if (rails.fire(element, 'ajax:before')) {
105-
elCrossDomain = element.data('cross-domain');
106-
crossDomain = elCrossDomain === undefined ? null : elCrossDomain;
107105
withCredentials = element.data('with-credentials') || null;
108106
dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
109107

@@ -155,7 +153,7 @@
155153
error: function(xhr, status, error) {
156154
element.trigger('ajax:error', [xhr, status, error]);
157155
},
158-
crossDomain: crossDomain
156+
crossDomain: rails.isCrossDomain(url)
159157
};
160158

161159
// There is no withCredentials for IE6-8 when
@@ -175,6 +173,27 @@
175173
}
176174
},
177175

176+
// Determines if the request is a cross domain request.
177+
isCrossDomain: function(url) {
178+
var originAnchor = document.createElement("a");
179+
originAnchor.href = location.href;
180+
var urlAnchor = document.createElement("a");
181+
182+
try {
183+
urlAnchor.href = url;
184+
// This is a workaround to a IE bug.
185+
urlAnchor.href = urlAnchor.href;
186+
187+
// Make sure that the browser parses the URL and that the protocols and hosts match.
188+
return !urlAnchor.protocol || !urlAnchor.host ||
189+
(originAnchor.protocol + "//" + originAnchor.host !==
190+
urlAnchor.protocol + "//" + urlAnchor.host);
191+
} catch (e) {
192+
// If there is an error parsing the URL, assume it is crossDomain.
193+
return true;
194+
}
195+
},
196+
178197
// Handles "data-method" on links such as:
179198
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
180199
handleMethod: function(link) {
@@ -186,7 +205,7 @@
186205
form = $('<form method="post" action="' + href + '"></form>'),
187206
metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
188207

189-
if (csrfParam !== undefined && csrfToken !== undefined) {
208+
if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
190209
metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
191210
}
192211

test/public/test/call-remote-callbacks.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,6 @@ asyncTest('modifying data("type") with "ajax:before" requests new dataType in re
6464
});
6565
});
6666

67-
asyncTest('setting data("cross-domain",true) with "ajax:before" uses new setting in request', 2, function(){
68-
$('form[data-remote]').data('cross-domain',false)
69-
.bind('ajax:before', function() {
70-
var form = $(this);
71-
form.data('cross-domain',true);
72-
});
73-
74-
submit(function(form) {
75-
form.bind('ajax:beforeSend', function(e, xhr, settings) {
76-
equal(settings.crossDomain, true, 'setting modified in ajax:before should have forced cross-domain request');
77-
});
78-
});
79-
});
80-
8167
asyncTest('setting data("with-credentials",true) with "ajax:before" uses new setting in request', 2, function(){
8268
$('form[data-remote]').data('with-credentials',false)
8369
.bind('ajax:before', function() {

test/public/test/call-remote.js

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,6 @@ asyncTest('sends CSRF token in custom header', 1, function() {
122122
});
123123
});
124124

125-
asyncTest('does not send CSRF token in custom header if crossDomain', 1, function() {
126-
buildForm({ 'data-cross-domain': 'true' });
127-
$('#qunit-fixture').append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />');
128-
129-
// Manually set request header to be XHR, since setting crossDomain: true in .ajax()
130-
// causes jQuery to skip setting the request header, to prevent our test/server.rb from
131-
// raising an an error (when request.xhr? is false).
132-
$('#qunit-fixture').find('form').bind('ajax:beforeSend', function(e, xhr) {
133-
xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
134-
});
135-
136-
submit(function(e, data, status, xhr) {
137-
equal(data.HTTP_X_CSRF_TOKEN, undefined, 'X-CSRF-Token header should NOT be sent');
138-
});
139-
});
140-
141125
asyncTest('intelligently guesses crossDomain behavior when target URL is a different domain', 1, function(e, xhr) {
142126

143127
// Don't set data-cross-domain here, just set action to be a different domain than localhost
@@ -156,20 +140,4 @@ asyncTest('intelligently guesses crossDomain behavior when target URL is a diffe
156140

157141
setTimeout(function() { start(); }, 13);
158142
});
159-
160-
asyncTest('does not set crossDomain if explicitly set to false on element', 1, function() {
161-
buildForm({ action: 'http://www.alfajango.com', 'data-cross-domain': false });
162-
$('#qunit-fixture').append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />');
163-
164-
$('#qunit-fixture').find('form')
165-
.bind('ajax:beforeSend', function(e, xhr, settings) {
166-
equal(settings.crossDomain, false, 'crossDomain should be set to false');
167-
// prevent request from actually getting sent off-domain
168-
return false;
169-
})
170-
.trigger('submit');
171-
172-
setTimeout(function() { start(); }, 13);
173-
});
174-
175143
})();

test/public/test/data-method.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,30 @@ asyncTest('link "target" should be carried over to generated form', 1, function(
4646
});
4747
});
4848

49+
asyncTest('link with "data-method" and cross origin', 1, function() {
50+
var data = {};
51+
52+
$('#qunit-fixture')
53+
.append('<meta name="csrf-param" content="authenticity_token"/>')
54+
.append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae"/>');
55+
56+
$(document).on('submit', 'form', function(e) {
57+
$(e.currentTarget).serializeArray().map(function(item) {
58+
data[item.name] = item.value;
59+
});
60+
61+
return false;
62+
});
63+
64+
var link = $('#qunit-fixture').find('a');
65+
66+
link.attr('href', 'http://www.alfajango.com');
67+
68+
link.trigger('click');
69+
70+
start();
71+
72+
notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae');
73+
});
74+
4975
})();

test/public/test/override.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ asyncTest("the getter for an element's href is overridable", 1, function() {
3232

3333
asyncTest("the getter for an element's href works normally if not overridden", 1, function() {
3434
$.rails.ajax = function(options) {
35-
equal('/real/href', options.url);
35+
equal(location.protocol + '//' + location.host + '/real/href', options.url);
3636
}
3737
$.rails.handleRemote($('#qunit-fixture').find('a'));
3838
start();

0 commit comments

Comments
 (0)