From 25b2bdd315b66c0f0ac43d9a6cabb6d3b3709e4a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 17 Jul 2017 15:54:16 -0400 Subject: [PATCH] safer construction of popup click handler --- src/lib/svg_text_utils.js | 8 +++--- test/jasmine/tests/svg_text_utils_test.js | 33 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 6c9c2d53d07..cdabebdf8c3 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -258,7 +258,7 @@ var BR_TAG = //i; var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i; var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i; var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i; -var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i; +var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i; // dedicated matcher for these quoted regexes, that can return their results // in two different places @@ -360,7 +360,9 @@ function buildSVGText(containerNode, str) { 'xlink:xlink:href': href }; if(popup) { - nodeAttrs.onclick = 'window.open("' + href + '","' + target + '","' + + // security: href and target are not inserted as code but + // as attributes. popup is, but limited to /[A-Za-z0-9_=,]/ + nodeAttrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' + popup + '");return false;'; } } @@ -459,7 +461,7 @@ function buildSVGText(containerNode, str) { var dummyAnchor = document.createElement('a'); dummyAnchor.href = href; if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) { - nodeSpec.href = href; + nodeSpec.href = encodeURI(href); nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank'; nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH); } diff --git a/test/jasmine/tests/svg_text_utils_test.js b/test/jasmine/tests/svg_text_utils_test.js index 374d50b9e8e..f0350389cf3 100644 --- a/test/jasmine/tests/svg_text_utils_test.js +++ b/test/jasmine/tests/svg_text_utils_test.js @@ -190,7 +190,38 @@ describe('svg+text utils', function() { it('attaches onclick if popup is specified', function() { var node = mockTextSVGElement('link'); assertAnchorLink(node, 'x', 'fred', 'new'); - assertAnchorAttrs(node, {onclick: 'window.open("x","fred","width=500,height=400");return false;'}); + assertAnchorAttrs(node, {onclick: 'window.open(this.href.baseVal,this.target.baseVal,"width=500,height=400");return false;'}); + }); + + it('drops XSS attacks via popup script', function() { + var textCases = [ + [ + 'XSS', + '#', 'b', null + ], + [ + 'XSS', + '#', 'b");alert(document.cookie);//', '1' + ], + [ + 'XSS', + '#%22);alert(document.cookie);//', 'b', '1' + ] + ]; + + textCases.forEach(function(textCase) { + var node = mockTextSVGElement(textCase[0]); + + var attrs = {}; + if(textCase[3]) { + attrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' + + textCase[3] + '");return false;'; + } + + expect(node.text()).toEqual('XSS'); + assertAnchorAttrs(node, attrs, textCase[0]); + assertAnchorLink(node, textCase[1], textCase[2], 'new', textCase[0]); + }); }); it('keeps query parameters in href', function() {