Skip to content

Commit 9b7c18f

Browse files
authored
Merge pull request #1307 from plotly/hover-fix-xss
Fix XSS in trace name on hover
2 parents 5e4bb1a + 0064954 commit 9b7c18f

File tree

4 files changed

+48
-7
lines changed

4 files changed

+48
-7
lines changed

src/lib/notifier.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ module.exports = function(text, displayLength) {
6363
note.transition().call(killNote);
6464
});
6565

66-
note.append('p').html(thisText);
66+
var p = note.append('p');
67+
var lines = thisText.split(/<br\s*\/?>/g);
68+
for(var i = 0; i < lines.length; i++) {
69+
if(i) p.append('br');
70+
p.append('span').text(lines[i]);
71+
}
6772

6873
note.transition()
6974
.duration(700)

src/lib/svg_text_utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ exports.html_entity_decode = function(s) {
4848
var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
4949
if(d === '&lt;') { return '&#60;'; } // special handling for brackets
5050
if(d === '&rt;') { return '&#62;'; }
51+
if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
5152
return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
5253
});
5354
hiddenDiv.remove();

src/plots/cartesian/graph_interact.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -952,12 +952,8 @@ function createHoverText(hoverData, opts) {
952952

953953

954954
if(d.name && d.zLabelVal === undefined) {
955-
// strip out any html elements from d.name (if it exists at all)
956-
// Note that this isn't an XSS vector, only because it never gets
957-
// attached to the DOM
958-
var tmp = document.createElement('p');
959-
tmp.innerHTML = d.name;
960-
name = tmp.textContent || '';
955+
// strip out our pseudo-html elements from d.name (if it exists at all)
956+
name = svgTextUtils.plainText(d.name || '');
961957

962958
if(name.length > 15) name = name.substr(0, 12) + '...';
963959
}

test/jasmine/tests/hover_label_test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,45 @@ describe('hover info', function() {
158158
});
159159
});
160160

161+
describe('hover info with bad name', function() {
162+
var mockCopy = Lib.extendDeep({}, mock);
163+
164+
mockCopy.data[0].text = [];
165+
mockCopy.data[0].text[17] = 'hover text';
166+
mockCopy.data[0].hoverinfo = 'all';
167+
mockCopy.data[0].name = '<img src=x onerror=y>';
168+
mockCopy.data.push({
169+
x: [0.002, 0.004],
170+
y: [12.5, 16.25],
171+
mode: 'lines+markers',
172+
name: 'another trace'
173+
});
174+
175+
beforeEach(function(done) {
176+
Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done);
177+
});
178+
179+
it('cleans the name', function() {
180+
var gd = document.getElementById('graph');
181+
Fx.hover('graph', evt, 'xy');
182+
183+
var hoverTrace = gd._hoverdata[0];
184+
185+
expect(hoverTrace.curveNumber).toEqual(0);
186+
expect(hoverTrace.pointNumber).toEqual(17);
187+
expect(hoverTrace.x).toEqual(0.388);
188+
expect(hoverTrace.y).toEqual(1);
189+
190+
expect(d3.selectAll('g.axistext').size()).toEqual(1);
191+
expect(d3.selectAll('g.hovertext').size()).toEqual(1);
192+
expect(d3.selectAll('g.axistext').select('text').html()).toEqual('0.388');
193+
expect(d3.selectAll('g.hovertext').select('text.nums').selectAll('tspan').size()).toEqual(2);
194+
expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][0].innerHTML).toEqual('1');
195+
expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][1].innerHTML).toEqual('hover text');
196+
expect(d3.selectAll('g.hovertext').selectAll('text.name').node().innerHTML).toEqual('&lt;img src=x o...');
197+
});
198+
});
199+
161200
describe('hover info y+text', function() {
162201
var mockCopy = Lib.extendDeep({}, mock);
163202

0 commit comments

Comments
 (0)