Skip to content
This repository has been archived by the owner on Jul 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #46 from yahoo/enhance-css-filters-for-IE
Browse files Browse the repository at this point in the history
Enhance css filters for IE
  • Loading branch information
neraliu committed Jul 29, 2015
2 parents a0e38b3 + 053ac64 commit 6d3d2b6
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 46 deletions.
59 changes: 33 additions & 26 deletions src/xss-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,23 @@ exports._getPrivFilters = function () {
var SENSITIVE_HTML_ENTITIES = /&(?:#([xX][0-9A-Fa-f]+|\d+);?|(Tab|NewLine|colon|semi|lpar|rpar|apos|sol|comma|excl|ast|midast|ensp|emsp|thinsp);|(nbsp|amp|AMP|lt|LT|gt|GT|quot|QUOT);?)/g,
SENSITIVE_NAMED_REF_MAP = {Tab: '\t', NewLine: '\n', colon: ':', semi: ';', lpar: '(', rpar: ')', apos: '\'', sol: '/', comma: ',', excl: '!', ast: '*', midast: '*', ensp: '\u2002', emsp: '\u2003', thinsp: '\u2009', nbsp: '\xA0', amp: '&', lt: '<', gt: '>', quot: '"', QUOT: '"'};

// TODO: CSS_DANGEROUS_FUNCTION_NAME = /(url\(|expression\()/ig;
var CSS_UNQUOTED_CHARS = /[^%#+\-\w\.]/g,
// \x7F and \x01-\x1F less \x09 are for Safari 5.0
CSS_DOUBLE_QUOTED_CHARS = /[\x01-\x1F\x7F\\"]/g,
CSS_SINGLE_QUOTED_CHARS = /[\x01-\x1F\x7F\\']/g,
// this assumes encodeURI() and encodeURIComponent() has escaped 1-32, 41, 127 for IE8
CSS_UNQUOTED_URL = /['\(\)]/g; // " \ treated by encodeURI()
// var CSS_VALID_VALUE =
// /^(?:
// (?!-*expression)#?[-\w]+
// |[+-]?(?:\d+|\d*\.\d+)(?:em|ex|ch|rem|px|mm|cm|in|pt|pc|%|vh|vw|vmin|vmax)?
// |!important
// | //empty
// )$/i;
var CSS_VALID_VALUE = /^(?:(?!-*expression)#?[-\w]+|[+-]?(?:\d+|\d*\.\d+)(?:r?em|ex|ch|cm|mm|in|px|pt|pc|%|vh|vw|vmin|vmax)?|!important|)$/i,
// TODO: prevent double css escaping by not encoding \ again, but this may require CSS decoding
// \x7F and \x01-\x1F less \x09 are for Safari 5.0, added []{}/* for unbalanced quote
CSS_DOUBLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\"]/g,
CSS_SINGLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\']/g,
// (, \u207D and \u208D can be used in background: 'url(...)' in IE, assumed all \ chars are encoded by QUOTED_CHARS, and null is already replaced with \uFFFD
// otherwise, use this CSS_BLACKLIST instead (enhance it with url matching): /(?:\\?\(|[\u207D\u208D]|\\0{0,4}28 ?|\\0{0,2}20[78][Dd] ?)+/g
CSS_BLACKLIST = /url[\(\u207D\u208D]+/g,
// this assumes encodeURI() and encodeURIComponent() has escaped 1-32, 127 for IE8
CSS_UNQUOTED_URL = /['\(\)]/g; // " \ treated by encodeURI()

// Given a full URI, need to support "[" ( IPv6address ) "]" in URI as per RFC3986
// Reference: https://tools.ietf.org/html/rfc3986
Expand Down Expand Up @@ -159,20 +169,16 @@ exports._getPrivFilters = function () {
// space after \\HEX is needed by spec
return '\\' + chr.charCodeAt(0).toString(16).toLowerCase() + ' ';
}
function css(s, reSensitiveChars) {
return htmlDecode(s).replace(reSensitiveChars, cssEncode);
function cssBlacklist(s) {
return s.replace(CSS_BLACKLIST, function(m){ return '-x-' + m; });
}
function cssUrl(s, reSensitiveChars) {
function cssUrl(s) {
// encodeURI() in yufull() will throw error for use of the CSS_UNSUPPORTED_CODE_POINT (i.e., [\uD800-\uDFFF])
s = x.yufull(htmlDecode(s));
var protocol = getProtocol(s);

// prefix ## for blacklisted protocols
if (protocol && URI_BLACKLIST_PROTOCOLS[protocol.toLowerCase()]) {
s = '##' + s;
}

return reSensitiveChars ? s.replace(reSensitiveChars, cssEncode) : s;
return (protocol && URI_BLACKLIST_PROTOCOLS[protocol.toLowerCase()]) ? '##' + s : s;
}

return (x = {
Expand Down Expand Up @@ -351,23 +357,22 @@ exports._getPrivFilters = function () {
// * http://www.w3.org/TR/CSS21/grammar.html
// * http://www.w3.org/TR/css-syntax-3/
//
// NOTE: delimitar in CSS - \ _ : ; ( ) " ' / , % # ! * @ . { }
// NOTE: delimiter in CSS - \ _ : ; ( ) " ' / , % # ! * @ . { }
// 2d 5c 5f 3a 3b 28 29 22 27 2f 2c 25 23 21 2a 40 2e 7b 7d

// CSS_UNQUOTED_CHARS = /[^%#+\-\w\.]/g,
yceu: function(s) {
return css(s, CSS_UNQUOTED_CHARS);
s = htmlDecode(s);
return CSS_VALID_VALUE.test(s) ? s : ";-x:'" + cssBlacklist(s.replace(CSS_SINGLE_QUOTED_CHARS, cssEncode)) + "';-v:";
},

// string1 = \"([^\n\r\f\\"]|\\{nl}|\\[^\n\r\f0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*\"
// CSS_DOUBLE_QUOTED_CHARS = /[\x01-\x1F\x7F\\"]/g,
yced: function(s) {
return css(s, CSS_DOUBLE_QUOTED_CHARS);
return cssBlacklist(htmlDecode(s).replace(CSS_DOUBLE_QUOTED_CHARS, cssEncode));
},

// string2 = \'([^\n\r\f\\']|\\{nl}|\\[^\n\r\f0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*\'
// CSS_SINGLE_QUOTED_CHARS = /[\x01-\x1F\x7F\\']/g,
yces: function(s) {
return css(s, CSS_SINGLE_QUOTED_CHARS);
return cssBlacklist(htmlDecode(s).replace(CSS_SINGLE_QUOTED_CHARS, cssEncode));
},

// for url({{{yceuu url}}}
Expand All @@ -376,19 +381,21 @@ exports._getPrivFilters = function () {
// The state machine in CSS 3.0 is more well defined - http://www.w3.org/TR/css-syntax-3/#consume-a-url-token0
// CSS_UNQUOTED_URL = /['\(\)]/g; // " \ treated by encodeURI()
yceuu: function(s) {
return cssUrl(s, CSS_UNQUOTED_URL);
return cssUrl(s).replace(CSS_UNQUOTED_URL, function (chr) {
return chr === '\'' ? '\\27 ' :
chr === '(' ? '%28' :
/* chr === ')' ? */ '%29';
});
},

// for url("{{{yceud url}}}
// CSS_DOUBLE_QUOTED_URL has nothing else to escape (optimized version by chaining with yufull)
yceud: function(s) {
return cssUrl(s);
},

// for url('{{{yceus url}}}
// CSS_SINGLE_QUOTED_URL = /'/g; (optimized version by chaining with yufull)
yceus: function(s) {
return cssUrl(s, SQUOT);
return cssUrl(s).replace(SQUOT, '\\27 ');
}
});
};
Expand Down
55 changes: 35 additions & 20 deletions tests/unit/private-xss-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,23 +316,29 @@ Authors: Nera Liu <[email protected]>
'\uD7FF', '\uD800', '\uDFFF', '\u1234567',
'\u0000', ' ', '\r\n\t\f\v', '\\', '\\n\\r\\f\\0\\9\\a\\f',
'-ide_nt', '"string"', "'string'",
'- \ _ : ; ( ) " \' / , % # ! * @ . { }',
'- \ _ : ; ( ) " \' / , % # ! * @ . { } []',
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'url(https://www.evil.com)',
'u\x00\x00rl(https://www.evil.com)',
'\\u\\r\x00\x00\\l\x00\\((evil.com))',
'expression(body.scrollTop + 50 + px)',
'(((()))) \\28 \\29'
];

it('filter yceu[uds] test', function() {
var expectedResults = [ 'undefined', 'null',
'\\26 ',
';-x:\'&\';-v:',
'1.1', '10%', '+10px', '-10px', '#fff',
'\\d7ff ', '\\d800 ', '\\dfff ', '\\1234 567',
'\\fffd ', '\\20 ', '\\d \\a \\9 \\c \\b ', '\\5c ', '\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f',
'-ide_nt', '\\22 string\\22 ', "\\27 string\\27 ",
'-\\20 \\20 _\\20 \\3a \\20 \\3b \\20 \\28 \\20 \\29 \\20 \\22 \\20 \\27 \\20 \\2f \\20 \\2c \\20 %\\20 #\\20 \\21 \\20 \\2a \\20 \\40 \\20 .\\20 \\7b \\20 \\7d ',
'http\\3a \\2f \\2f username\\3a password\\40 www.evil.com\\3a 8080\\2f \\3f k1\\3d v1\\26 k2\\3d v2#hash',
'url\\28 https\\3a \\2f \\2f www.evil.com\\29 ',
'expression\\28 body.scrollTop\\20 +\\20 50\\20 +\\20 px\\29 ',
';-x:\'\uD7FF\';-v:', ';-x:\'\uD800\';-v:', ';-x:\'\uDFFF\';-v:', ';-x:\'\u1234567\';-v:',
';-x:\'\uFFFD\';-v:', ';-x:\' \';-v:', ';-x:\'\\d \\a \\9 \\c \\b \';-v:', ';-x:\'\\5c \';-v:', ';-x:\'\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f\';-v:',
'-ide_nt', ';-x:\'"string"\';-v:', ';-x:\'\\27 string\\27 \';-v:',
';-x:\'- _ : ; ( ) " \\27 / , % # ! * @ . \\7b \\7d \\5b \\5d \';-v:',
';-x:\'http://username:[email protected]:8080/?k1=v1&k2=v2#hash\';-v:',
';-x:\'-x-url(https://www.evil.com)\';-v:',
';-x:\'u\ufffd\ufffdrl(https://www.evil.com)\';-v:',
';-x:\'\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))\';-v:',
';-x:\'expression(body.scrollTop + 50 + px)\';-v:',
';-x:\'(((()))) \\5c 28 \\5c 29\';-v:'
];
testutils.test_yce(filter.yceu, testPatterns, expectedResults);
});
Expand All @@ -343,10 +349,13 @@ Authors: Nera Liu <[email protected]>
'\uD7FF', '\uD800', '\uDFFF', '\u1234567',
'\uFFFD', ' ', '\\d \\a \\9 \\c \\b ', '\\5c ', '\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f',
'-ide_nt', '\\22 string\\22 ', "'string'",
'- _ : ; ( ) \\22 \' / , % # ! * @ . { }',
'- _ : ; ( ) \\22 \' / , % # ! * @ . \\7b \\7d \\5b \\5d ',
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'url(https://www.evil.com)',
'-x-url(https://www.evil.com)',
'u\ufffd\ufffdrl(https://www.evil.com)',
'\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))',
'expression(body.scrollTop + 50 + px)',
'(((()))) \\5c 28 \\5c 29',
];
testutils.test_yce(filter.yced, testPatterns, expectedResults);
});
Expand All @@ -357,16 +366,18 @@ Authors: Nera Liu <[email protected]>
'\uD7FF', '\uD800', '\uDFFF', '\u1234567',
'\uFFFD', ' ', '\\d \\a \\9 \\c \\b ', '\\5c ', '\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f',
'-ide_nt', '"string"', "\\27 string\\27 ",
'- _ : ; ( ) " \\27 / , % # ! * @ . { }',
'- _ : ; ( ) " \\27 / , % # ! * @ . \\7b \\7d \\5b \\5d ',
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'url(https://www.evil.com)',
'-x-url(https://www.evil.com)',
'u\ufffd\ufffdrl(https://www.evil.com)',
'\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))',
'expression(body.scrollTop + 50 + px)',
'(((()))) \\5c 28 \\5c 29',
];
testutils.test_yce(filter.yces, testPatterns, expectedResults);
});
});


describe("private-xss-filters: css url tests", function() {
var testPatterns = [ undefined, null,
'&',
Expand All @@ -378,7 +389,8 @@ Authors: Nera Liu <[email protected]>
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'\u0000\u0008\u000b\u007f\u000e-\u001f',
'&rpar;&#x00029;&#41;&lpar;&#x00028;&#40;&apos;&#x00027;&#39;&quot;&QUOT;&#x00022;&#34',
'javascript:alert(1)'
'javascript:alert(1)',
'(((()))) \\28 \\29',
];

it('filter yceuu[uds] attribute test', function() {
Expand All @@ -388,11 +400,12 @@ Authors: Nera Liu <[email protected]>
'%5Ca', '%ED%9F%BF', '%E1%88%B4567',
'%EF%BF%BD', '%20', '%0D%0A%09%0C%0B', '%5C', '%5Cn%5Cr%5Cf%5C0%5C9%5Ca%5Cf',
'-ide_nt', '%22string%22', "\\27 string\\27 ",
'-%20%20_%20:%20;%20\\28 %20\\29 %20%22%20\\27 %20/%20,%20%25%20#%20!%20*%20@%20.%20%7B%20%7D%20%5B%20%5D',
'-%20%20_%20:%20;%20%28%20%29%20%22%20\\27 %20/%20,%20%25%20#%20!%20*%20@%20.%20%7B%20%7D%20%5B%20%5D',
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'%EF%BF%BD%08%0B%7F%0E-%1F',
'\\29 \\29 \\29 \\28 \\28 \\28 \\27 \\27 \\27 %22%22%22%22',
'##javascript:alert\\28 1\\29 '
'%29%29%29%28%28%28\\27 \\27 \\27 %22%22%22%22',
'##javascript:alert%281%29',
'%28%28%28%28%29%29%29%29%20%5C28%20%5C29',
];
testutils.test_yce(filter.yceuu, testPatterns, expectedResults);
});
Expand All @@ -407,7 +420,8 @@ Authors: Nera Liu <[email protected]>
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'%EF%BF%BD%08%0B%7F%0E-%1F',
')))(((\'\'\'%22%22%22%22',
'##javascript:alert(1)'
'##javascript:alert(1)',
'(((())))%20%5C28%20%5C29'
];
testutils.test_yce(filter.yceud, testPatterns, expectedResults);
});
Expand All @@ -422,7 +436,8 @@ Authors: Nera Liu <[email protected]>
'http://username:[email protected]:8080/?k1=v1&k2=v2#hash',
'%EF%BF%BD%08%0B%7F%0E-%1F',
')))(((\\27 \\27 \\27 %22%22%22%22',
'##javascript:alert(1)'
'##javascript:alert(1)',
'(((())))%20%5C28%20%5C29'
];
testutils.test_yce(filter.yceus, testPatterns, expectedResults);
});
Expand Down

0 comments on commit 6d3d2b6

Please sign in to comment.