Add XSS protection for redirectTo
* Add unit tests for Url
This commit is contained in:
Родитель
166118d787
Коммит
b7c33b0c62
|
@ -8,8 +8,8 @@
|
|||
define(['underscore'],
|
||||
function (_) {
|
||||
return {
|
||||
searchParam: function (name) {
|
||||
var search = window.location.search.replace(/^\?/, '');
|
||||
searchParam: function (name, str) {
|
||||
var search = (window.location.search || str).replace(/^\?/, '');
|
||||
if (! search) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Basic XSS protection
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'processed/constants'
|
||||
],
|
||||
function(_, Constants) {
|
||||
return {
|
||||
// only allow http or https URLs, encoding the URL.
|
||||
href: function(text) {
|
||||
if (! _.isString(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! /^https?:\/\//.test(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var encodedURI = encodeURI(text);
|
||||
|
||||
// All browsers have a max length of URI that they can handle.
|
||||
// IE8 has the shortest total length at 2083 bytes and 2048 characters
|
||||
// for GET requests.
|
||||
// See http://support.microsoft.com/kb/q208427
|
||||
|
||||
// Check the total encoded URI length
|
||||
if (encodedURI.length > Constants.URL_MAX_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
return encodedURI;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
define([], function () {
|
||||
return {
|
||||
FXA_ACCOUNT_SERVER: '/* @echo fxaccountUrl */'
|
||||
FXA_ACCOUNT_SERVER: '/* @echo fxaccountUrl */',
|
||||
|
||||
// All browsers have a max length of URI that they can handle.
|
||||
// IE8 has the shortest total length at 2083 bytes and 2048 characters
|
||||
// for GET requests.
|
||||
// See http://support.microsoft.com/kb/q208427
|
||||
URL_MAX_LENGTH: 2048
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -51,8 +51,10 @@ function (_, BaseView, Template, FxaClient, Session, Url) {
|
|||
},
|
||||
|
||||
_onResetCompleteSuccess: function () {
|
||||
// This information will be displayed on the
|
||||
// reset_password_complete screen.
|
||||
Session.service = Url.searchParam('service');
|
||||
Session.redirectTo = Url.searchParam('continue');
|
||||
Session.redirectTo = Url.searchParam('redirectTo');
|
||||
router.navigate('reset_password_complete', { trigger: true });
|
||||
},
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ define([
|
|||
'stache!templates/complete_sign_up',
|
||||
'lib/session',
|
||||
'lib/fxa-client',
|
||||
'lib/url'
|
||||
'lib/url',
|
||||
'lib/xss'
|
||||
],
|
||||
function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient, Url) {
|
||||
function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient, Url, Xss) {
|
||||
var CompleteSignUpView = BaseView.extend({
|
||||
template: CompleteSignUpTemplate,
|
||||
className: 'complete_sign_up',
|
||||
|
@ -20,8 +21,8 @@ function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient, Url) {
|
|||
context: function () {
|
||||
return {
|
||||
email: Session.email,
|
||||
siteName: Url.searchParam('service'),
|
||||
redirectTo: Url.searchParam('continue')
|
||||
service: Url.searchParam('service'),
|
||||
redirectTo: Xss.href(Url.searchParam('redirectTo'))
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ define([
|
|||
'underscore',
|
||||
'views/base',
|
||||
'stache!templates/reset_password_complete',
|
||||
'lib/session'
|
||||
'lib/session',
|
||||
'lib/xss'
|
||||
],
|
||||
function (_, BaseView, Template, Session) {
|
||||
function (_, BaseView, Template, Session, Xss) {
|
||||
var View = BaseView.extend({
|
||||
template: Template,
|
||||
className: 'reset_password_complete',
|
||||
|
@ -19,7 +20,7 @@ function (_, BaseView, Template, Session) {
|
|||
return {
|
||||
email: Session.email,
|
||||
service: Session.service,
|
||||
redirectTo: Session.redirectTo
|
||||
redirectTo: Xss.href(Session.redirectTo)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -48,7 +48,9 @@ require.config({
|
|||
require([
|
||||
'mocha',
|
||||
'../tests/setup',
|
||||
'../tests/spec/lib/channels/fx-desktop'
|
||||
'../tests/spec/lib/channels/fx-desktop',
|
||||
'../tests/spec/lib/xss',
|
||||
'../tests/spec/lib/url'
|
||||
],
|
||||
function (Mocha) {
|
||||
var runner = Mocha.run();
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
define([
|
||||
'mocha',
|
||||
'chai',
|
||||
'underscore',
|
||||
'lib/url',
|
||||
'processed/constants'
|
||||
],
|
||||
function (mocha, chai, _, Url, Constants) {
|
||||
var assert = chai.assert;
|
||||
var channel;
|
||||
|
||||
describe('lib/url', function () {
|
||||
describe('searchParam', function () {
|
||||
it('returns a parameter from window.location.serach, if it exists',
|
||||
function() {
|
||||
assert.equal(Url.searchParam('color', '?color=green'), 'green');
|
||||
});
|
||||
|
||||
it('returns undefined if parameter does not exist', function() {
|
||||
assert.isUndefined(Url.searchParam('animal', '?color=green'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
define([
|
||||
'mocha',
|
||||
'chai',
|
||||
'underscore',
|
||||
'lib/xss',
|
||||
'processed/constants'
|
||||
],
|
||||
function (mocha, chai, _, XSS, Constants) {
|
||||
var assert = chai.assert;
|
||||
var channel;
|
||||
|
||||
describe('lib/xss', function () {
|
||||
describe('href', function () {
|
||||
function expectEmpty(url) {
|
||||
assert.isUndefined(XSS.href(url));
|
||||
}
|
||||
|
||||
it('allows http href', function () {
|
||||
assert.equal(XSS.href('http://all.good'), 'http://all.good');
|
||||
});
|
||||
|
||||
it('allows https href', function () {
|
||||
assert.equal(XSS.href('https://all.good'), 'https://all.good');
|
||||
});
|
||||
|
||||
it('allows href with query parameters', function () {
|
||||
assert.equal(XSS.href('https://all.good?with_query'),
|
||||
'https://all.good?with_query');
|
||||
});
|
||||
|
||||
it('allows but escapes URLs that try to break out', function () {
|
||||
assert.equal(XSS.href('http://href.gone.bad" onclick="javascript(1)"'),
|
||||
'http://href.gone.bad%22%20onclick=%22javascript(1)%22');
|
||||
});
|
||||
|
||||
it('disallows javascript: href', function () {
|
||||
expectEmpty('javascript:alert(1)');
|
||||
});
|
||||
|
||||
it('disallows href without a scheme', function () {
|
||||
expectEmpty('no.scheme');
|
||||
});
|
||||
|
||||
it('disallows relative scheme', function () {
|
||||
expectEmpty('//relative.scheme');
|
||||
});
|
||||
|
||||
it('disallows data URI scheme', function () {
|
||||
expectEmpty('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D');
|
||||
});
|
||||
|
||||
it('only allows strings', function () {
|
||||
var disallowedItems = [
|
||||
1,
|
||||
true,
|
||||
new Date(),
|
||||
{},
|
||||
[]
|
||||
];
|
||||
|
||||
_.each(disallowedItems, expectEmpty);
|
||||
});
|
||||
|
||||
it('allows hrefs of the max length', function() {
|
||||
var maxLength = Constants.URL_MAX_LENGTH;
|
||||
var allowed = "http://";
|
||||
|
||||
while (allowed.length < maxLength) {
|
||||
allowed += 'a';
|
||||
}
|
||||
|
||||
assert.equal(XSS.href(allowed), allowed);
|
||||
});
|
||||
|
||||
it('disallowed hrefs that are too long', function() {
|
||||
var maxLength = Constants.URL_MAX_LENGTH;
|
||||
var tooLong = "http://";
|
||||
|
||||
while (tooLong.length < maxLength + 1) {
|
||||
tooLong += 'a';
|
||||
}
|
||||
|
||||
expectEmpty(tooLong);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Загрузка…
Ссылка в новой задаче