Skip to content

Commit e3eba8a

Browse files
authored
Merge pull request #1888 from plotly/popup-fix
safer construction of popup click handler
2 parents 81e33ae + 25b2bdd commit e3eba8a

File tree

2 files changed

+37
-4
lines changed

2 files changed

+37
-4
lines changed

src/lib/svg_text_utils.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ var BR_TAG = /<br(\s+.*)?>/i;
258258
var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i;
259259
var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i;
260260
var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
261-
var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
261+
var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;
262262

263263
// dedicated matcher for these quoted regexes, that can return their results
264264
// in two different places
@@ -360,7 +360,9 @@ function buildSVGText(containerNode, str) {
360360
'xlink:xlink:href': href
361361
};
362362
if(popup) {
363-
nodeAttrs.onclick = 'window.open("' + href + '","' + target + '","' +
363+
// security: href and target are not inserted as code but
364+
// as attributes. popup is, but limited to /[A-Za-z0-9_=,]/
365+
nodeAttrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' +
364366
popup + '");return false;';
365367
}
366368
}
@@ -459,7 +461,7 @@ function buildSVGText(containerNode, str) {
459461
var dummyAnchor = document.createElement('a');
460462
dummyAnchor.href = href;
461463
if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
462-
nodeSpec.href = href;
464+
nodeSpec.href = encodeURI(href);
463465
nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank';
464466
nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH);
465467
}

test/jasmine/tests/svg_text_utils_test.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,38 @@ describe('svg+text utils', function() {
190190
it('attaches onclick if popup is specified', function() {
191191
var node = mockTextSVGElement('<a href="x" target="fred" popup="width=500,height=400">link</a>');
192192
assertAnchorLink(node, 'x', 'fred', 'new');
193-
assertAnchorAttrs(node, {onclick: 'window.open("x","fred","width=500,height=400");return false;'});
193+
assertAnchorAttrs(node, {onclick: 'window.open(this.href.baseVal,this.target.baseVal,"width=500,height=400");return false;'});
194+
});
195+
196+
it('drops XSS attacks via popup script', function() {
197+
var textCases = [
198+
[
199+
'<a href=\'#\' target=\'b\' popup=\'1");alert(document.cookie);//\'>XSS</a>',
200+
'#', 'b', null
201+
],
202+
[
203+
'<a href=\'#\' target=\'b");alert(document.cookie);//\' popup=\'1\'>XSS</a>',
204+
'#', 'b");alert(document.cookie);//', '1'
205+
],
206+
[
207+
'<a href=\'#");alert(document.cookie);//\' target=\'b\' popup=\'1\'>XSS</a>',
208+
'#%22);alert(document.cookie);//', 'b', '1'
209+
]
210+
];
211+
212+
textCases.forEach(function(textCase) {
213+
var node = mockTextSVGElement(textCase[0]);
214+
215+
var attrs = {};
216+
if(textCase[3]) {
217+
attrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' +
218+
textCase[3] + '");return false;';
219+
}
220+
221+
expect(node.text()).toEqual('XSS');
222+
assertAnchorAttrs(node, attrs, textCase[0]);
223+
assertAnchorLink(node, textCase[1], textCase[2], 'new', textCase[0]);
224+
});
194225
});
195226

196227
it('keeps query parameters in href', function() {

0 commit comments

Comments
 (0)