Add XSS protection for redirectTo

* Add unit tests for Url
This commit is contained in:
Shane Tomlinson 2014-01-14 12:05:20 +00:00
Родитель 166118d787
Коммит b7c33b0c62
9 изменённых файлов: 195 добавлений и 12 удалений

Просмотреть файл

@ -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;
}

42
app/scripts/lib/xss.js Normal file
Просмотреть файл

@ -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();

33
app/tests/spec/lib/url.js Normal file
Просмотреть файл

@ -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'));
});
});
});
});

96
app/tests/spec/lib/xss.js Normal file
Просмотреть файл

@ -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);
});
});
});
});