Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

Commit

Permalink
[bz5590319] Unicode and/or HTML escaping for config data.
Browse files Browse the repository at this point in the history
  • Loading branch information
mojit0 committed Jun 14, 2012
1 parent 6e29b0e commit 83a68da
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 8 deletions.
18 changes: 14 additions & 4 deletions source/lib/app/addons/ac/deploy.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
}
},


/**
* Builds up the browser Mojito runtime.
* @method constructMojitoClientRuntime
Expand All @@ -86,6 +85,8 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
contextClient,
appConfigClient,
yuiConfig = {},
yuiConfigEscaped,
yuiConfigStr,
yuiModules,
loader,
yuiCombo,
Expand All @@ -98,6 +99,8 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
id,
instances = {},
clientConfig = {},
clientConfigEscaped,
clientConfigStr,
usePrecomputed,
useOnDemand,
initialModuleList,
Expand Down Expand Up @@ -292,11 +295,18 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
initialModuleList = "'mojito-client'";
}

initializer = '<script type=\"text/javascript\" >\n' +
' YUI_config = ' + Y.JSON.stringify(yuiConfig) + ';\n' +
// Unicode escape the various strings in the config data to help
// fight against possible script injection attacks.
yuiConfigEscaped = Y.mojito.util.cleanse(yuiConfig);
yuiConfigStr = Y.JSON.stringify(yuiConfigEscaped);
clientConfigEscaped = Y.mojito.util.cleanse(clientConfig);
clientConfigStr = Y.JSON.stringify(clientConfigEscaped);

initializer = '<script type="text/javascript">\n' +
' YUI_config = ' + yuiConfigStr + ';\n' +
' YUI().use(' + initialModuleList + ', function(Y) {\n' +
' window.YMojito = { client: new Y.mojito.Client(' +
Y.JSON.stringify(clientConfig, null, 2) + ') };\n' +
clientConfigStr + ') };\n' +
' });\n' +
'</script>\n';

Expand Down
80 changes: 80 additions & 0 deletions source/lib/app/autoload/util.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,86 @@ YUI.add('mojito-util', function(Y) {
},


/**
* Unicode escapes the "Big 5" HTML characters (<, >, ', ", and &). Note
* that only strings are escaped by this routine. If you want to ensure
* that an entire object or array is escaped use the util.cleanse() call.
* @param {Object} obj The object to encode/escape.
*/
unicodeEscape: function(obj) {

if (Y.Lang.isString(obj)) {
return obj.replace(/</g, '\\u003C').
replace(/>/g, '\\u003E').
replace(/&/g, '\\u0026').
replace(/'/g, '\\u0027').
replace(/"/g, '\\u0022');
}

return obj;
},


/**
* Cleanses string keys and values in an object, returning a new object
* whose strings are escaped using the escape function provided. The
* default escape function is the util.unicodeEscape function.
* @param {Object} obj The object to cleanse.
* @param {Function} escape The escape function to run. Default is
* util.unicodeEscape.
* @return {Object} The cleansed object.
*/
cleanse: function(obj, escape) {
var func,
clean,
len,
i;

// Confirm we got a valid escape function, or default properly.
if (escape) {
if (typeof escape === 'function') {
func = escape;
} else {
throw new Error('Invalid escape function: ' + escape);
}
}
func = func || this.unicodeEscape;

// How we proceed depends on what type of object we received. If we
// got a String or RegExp they're not strictly mutable, but we can
// quickly escape them and return. If we got an Object or Array
// we'll need to iterate, but in different ways since their content
// is found via different indexing models. If we got anything else
// we can just return it.

if (Y.Lang.isString(obj)) {
return func(obj);
}

if (Y.Lang.isArray(obj)) {
clean = [];
len = obj.length;
for (i = 0; i < len; i += 1) {
clean.push(this.cleanse(obj[i], func));
}
return clean;
}

if (Y.Lang.isObject(obj)) {
clean = {};
for (i in obj) {
if (obj.hasOwnProperty(i)) {
clean[this.cleanse(i, func)] =
this.cleanse(obj[i], func);
}
}
return clean;
}

return obj;
},


copy: function(obj) {
var temp = null,
key = '';
Expand Down
21 changes: 17 additions & 4 deletions source/lib/app/mojits/HTMLFrameMojit/controller.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,32 @@ YUI.add('HTMLFrameMojit', function(Y, NAME) {

var renderListAsHtmlAssets = function(list, type) {
var i,
data = '';
data = '',
url;

if ('js' === type) {
for (i = 0; i < list.length; i += 1) {
// Escape any HTML chars in the URL to avoid trivial attribute
// injection attacks.
url = Y.Escape.html(list[i]);
data += '<script type="text/javascript" src="' +
list[i] + '"></script>\n';
url + '"></script>\n';
}
} else if ('css' === type) {
for (i = 0; i < list.length; i += 1) {
// Escape any HTML chars in the URL to avoid trivial attribute
// injection attacks.
url = Y.Escape.html(list[i]);
data += '<link rel="stylesheet" type="text/css" href="' +
list[i] + '"/>\n';
url + '"/>\n';
}
} else if ('blob' === type) {
for (i = 0; i < list.length; i += 1) {
// NOTE: Giant security hole...but used by everyone who uses
// Mojito so there's not much we can do except tell authors of
// Mojito applications to _never_ use user input to generate
// blob content or populate config data. Whatever goes in here
// can't be easily encoded without the likelihood of corruption.
data += list[i] + '\n';
}
} else {
Expand Down Expand Up @@ -112,5 +124,6 @@ YUI.add('HTMLFrameMojit', function(Y, NAME) {
}, '0.1.0', {requires: [
'mojito-assets-addon',
'mojito-deploy-addon',
'mojito-config-addon'
'mojito-config-addon',
'escape'
]});
83 changes: 83 additions & 0 deletions source/lib/tests/autoload/util-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,89 @@ YUI.add('mojito-util-tests', function(Y, NAME) {

name: 'others',

'unicodeEscape cleanses a string': function() {
A.areSame(
'\\u003Cscript\\u003Ealert(\\u0022hi, i\\u0027m a squid \\u0026 a happy one!\\u0022)\\u003C/script\\u003E',
Y.mojito.util.cleanse(
'<script>alert("hi, i\'m a squid & a happy one!")</script>'));
},

'cleanse cleanses a string': function() {
A.areSame(
'\\u003Cscript\\u003Ealert(\\u0022hi, i\\u0027m a squid \\u0026 a happy one!\\u0022)\\u003C/script\\u003E',
Y.mojito.util.cleanse(
'<script>alert("hi, i\'m a squid & a happy one!")</script>'));
},

'cleanse cleanses an empty array': function() {
var a = [];
AA.itemsAreEqual(a, Y.mojito.util.cleanse(a),
'Empty array should cleanse properly as empty array.');
},

'cleanse cleanses an array with single array child': function() {
var a = [[]];
// AA.itemsAreEqual is brain-damaged and doesn't maintain Array
// semantics for content checks so we hack around it with JSON.
A.areSame(Y.JSON.stringify(a),
Y.JSON.stringify(Y.mojito.util.cleanse(a)),
'Array with single (empty) array child should cleanse properly.');
},

'cleanse cleanses an array': function() {
var a1,
a2;

a1 = ['<script>I\'m a hack attempt</script>'];
a2 = ['\\u003Cscript\\u003EI\\u0027m a hack attempt\\u003C/script\\u003E'];
AA.itemsAreEqual(a2, Y.mojito.util.cleanse(a1),
'array cleanse should work');
},

'cleanse cleanses an object': function() {
var o1,
o2;

o1 = {'key': '<script>I\'m a hack attempt</script>'};
o2 = {'key':
'\\u003Cscript\\u003EI\\u0027m a hack attempt\\u003C/script\\u003E'};

OA.areEqual(o2, Y.mojito.util.cleanse(o1),
'object cleanse should work');
},

'cleanse cleanses a nested array': function() {
var a1,
a2;

a1 = [['<script>I\'m a hack attempt</script>']];
a2 = [['\\u003Cscript\\u003EI\\u0027m a hack attempt\\u003C/script\\u003E']];
AA.itemsAreEqual(a2[0], Y.mojito.util.cleanse(a1)[0],
'nested array cleanse should work');
},

'cleanse cleanses a nested object': function() {
var a1,
a2;

a1 = [{'key': '<script>I\'m a hack attempt</script>'}];
a2 = [{'key':
'\\u003Cscript\\u003EI\\u0027m a hack attempt\\u003C/script\\u003E'}];

OA.areEqual(a2[0], Y.mojito.util.cleanse(a1)[0],
'object cleanse should work');
},

'cleanse ignores numbers, booleans, etc.': function() {
var a1,
a2;

a1 = [1, true, 'blah'];
a2 = [1, true, 'blah'];

AA.itemsAreEqual(a2, Y.mojito.util.cleanse(a1));
},

'copy() deep copies an object': function() {
var obj = {
inner: {
Expand Down

0 comments on commit 83a68da

Please sign in to comment.