diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index e9549eab98..543ae9ff00 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -604,14 +604,9 @@ function buildSVGText(containerNode, str) { var href = getQuotedMatch(extra, HREFMATCH); if(href) { - // check safe protocols - var dummyAnchor = document.createElement('a'); - dummyAnchor.href = href; - if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) { - // Decode href to allow both already encoded and not encoded - // URIs. Without decoding prior encoding, an already encoded - // URI would be encoded twice producing a semantically different URI. - nodeSpec.href = encodeURI(decodeURI(href)); + var safeHref = sanitizeHref(href); + if(safeHref) { + nodeSpec.href = safeHref; nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank'; nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH); } @@ -626,6 +621,27 @@ function buildSVGText(containerNode, str) { return hasLink; } +function sanitizeHref(href) { + var decodedHref = encodeURI(decodeURI(href)); + var dummyAnchor1 = document.createElement('a'); + var dummyAnchor2 = document.createElement('a'); + dummyAnchor1.href = href; + dummyAnchor2.href = decodedHref; + + var p1 = dummyAnchor1.protocol; + var p2 = dummyAnchor2.protocol; + + // check safe protocols + if( + PROTOCOLS.indexOf(p1) !== -1 && + PROTOCOLS.indexOf(p2) !== -1 + ) { + return decodedHref; + } else { + return ''; + } +} + /* * sanitizeHTML: port of buildSVGText aimed at providing a clean subset of HTML * @param {string} str: the html string to clean @@ -660,10 +676,9 @@ exports.sanitizeHTML = function sanitizeHTML(str) { var href = getQuotedMatch(extra, HREFMATCH); if(href) { - var dummyAnchor = document.createElement('a'); - dummyAnchor.href = href; - if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) { - nodeAttrs.href = encodeURI(decodeURI(href)); + var safeHref = sanitizeHref(href); + if(safeHref) { + nodeAttrs.href = safeHref; var target = getQuotedMatch(extra, TARGETMATCH); if(target) { nodeAttrs.target = target; diff --git a/test/jasmine/tests/svg_text_utils_test.js b/test/jasmine/tests/svg_text_utils_test.js index 5b1ed7b026..34060aba24 100644 --- a/test/jasmine/tests/svg_text_utils_test.js +++ b/test/jasmine/tests/svg_text_utils_test.js @@ -85,6 +85,16 @@ describe('svg+text utils', function() { d3.selectAll('.text-tester').remove(); }); + it('checks for XSS attack in href protocol', function() { + var node = mockTextSVGElement( + 'XSS' + ); + + expect(node.text()).toEqual('XSS'); + assertAnchorAttrs(node); + assertAnchorLink(node, null); + }); + it('checks for XSS attack in href', function() { var node = mockTextSVGElement( 'XSS' @@ -534,6 +544,14 @@ describe('sanitizeHTML', function() { d3.selectAll('.text-tester').remove(); }); + it('checks for XSS attack in href protocol', function() { + var innerHTML = mockHTML( + 'XSS' + ); + + expect(innerHTML).toEqual('XSS'); + }); + it('checks for XSS attack in href', function() { var innerHTML = mockHTML( 'XSS'