Skip to content

Commit d06de72

Browse files
committed
Merge pull request #100 from plotly/href-xss
Improve XSS check for <a href>
2 parents 3bc9df8 + 0be8fc1 commit d06de72

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

src/lib/svg_text_utils.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ var TAG_STYLES = {
221221
em: 'font-style:italic;font-weight:bold'
222222
};
223223

224+
var PROTOCOLS = ['http:', 'https:', 'mailto:'];
225+
224226
var STRIP_TAGS = new RegExp('</?(' + Object.keys(TAG_STYLES).join('|') + ')( [^>]*)?/?>', 'g');
225227

226228
util.plainText = function(_str){
@@ -252,7 +254,14 @@ function convertToSVG(_str){
252254
if(tag === 'a'){
253255
if(close) return '</a>';
254256
else if(extra.substr(0,4).toLowerCase() !== 'href') return '<a>';
255-
else return '<a xlink:show="new" xlink:href' + extra.substr(4) + '>';
257+
else {
258+
var dummyAnchor = document.createElement('a');
259+
dummyAnchor.href = extra.substr(4).replace(/["'=]/g, '');
260+
261+
if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '<a>';
262+
263+
return '<a xlink:show="new" xlink:href' + extra.substr(4) + '>';
264+
}
256265
}
257266
else if(tag === 'br') return '<br>';
258267
else if(close) {
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
var d3 = require('d3');
2+
3+
var util = require('@src/lib/svg_text_utils');
4+
5+
6+
describe('svg+text utils', function() {
7+
'use strict';
8+
9+
describe('convertToTspans', function() {
10+
11+
function mockTextSVGElement(txt) {
12+
return d3.select('body')
13+
.append('svg')
14+
.attr('id', 'text')
15+
.append('text')
16+
.text(txt)
17+
.call(util.convertToTspans);
18+
}
19+
20+
afterEach(function() {
21+
d3.select('#text').remove();
22+
});
23+
24+
it('checks for XSS attack in href', function() {
25+
var node = mockTextSVGElement(
26+
'<a href="javascript:alert(\'attack\')">XSS</a>'
27+
)
28+
29+
expect(node.text()).toEqual('XSS');
30+
expect(node.select('a').attr('xlink:href')).toBe(null);
31+
});
32+
33+
it('checks for XSS attack in href (with plenty of white spaces)', function() {
34+
var node = mockTextSVGElement(
35+
'<a href = " javascript:alert(\'attack\')">XSS</a>'
36+
)
37+
38+
expect(node.text()).toEqual('XSS');
39+
expect(node.select('a').attr('xlink:href')).toBe(null);
40+
});
41+
42+
it('whitelists http hrefs', function() {
43+
var node = mockTextSVGElement(
44+
'<a href="http://bl.ocks.org/">bl.ocks.org</a>'
45+
)
46+
47+
expect(node.text()).toEqual('bl.ocks.org');
48+
expect(node.select('a').attr('xlink:href')).toEqual('http://bl.ocks.org/');
49+
});
50+
51+
it('whitelists https hrefs', function() {
52+
var node = mockTextSVGElement(
53+
'<a href="https://plot.ly">plot.ly</a>'
54+
)
55+
56+
expect(node.text()).toEqual('plot.ly');
57+
expect(node.select('a').attr('xlink:href')).toEqual('https://plot.ly');
58+
});
59+
60+
it('whitelists mailto hrefs', function() {
61+
var node = mockTextSVGElement(
62+
'<a href="mailto:[email protected]">support</a>'
63+
)
64+
65+
expect(node.text()).toEqual('support');
66+
expect(node.select('a').attr('xlink:href')).toEqual('mailto:[email protected]');
67+
});
68+
});
69+
});

0 commit comments

Comments
 (0)