VYPR
High severityOSV Advisory· Published Mar 14, 2019· Updated Aug 5, 2024

CVE-2018-20801

CVE-2018-20801

Description

In js/parts/SvgRenderer.js in Highcharts JS before 6.1.0, the use of backtracking regular expressions permitted an attacker to conduct a denial of service attack against the SVGRenderer component, aka ReDoS.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
highchartsnpm
< 6.1.06.1.0

Affected products

1

Patches

1
7c547e1e0f5e

Refactored SVGRenderer text to not use backtracking regexes to replace attributes.

https://github.com/highcharts/highchartsTorstein HønsiMar 1, 2018via ghsa
3 files changed · +75 20
  • js/parts/SvgRenderer.js+38 20 modified
    @@ -2348,9 +2348,6 @@ extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
     			hasMarkup = textStr.indexOf('<') !== -1,
     			lines,
     			childNodes = textNode.childNodes,
    -			clsRegex,
    -			styleRegex,
    -			hrefRegex,
     			wasTooLong,
     			parentX = attr(textNode, 'x'),
     			textStyles = wrapper.styles,
    @@ -2390,6 +2387,23 @@ extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
     					}
     				});
     				return inputStr;
    +			},
    +			parseAttribute = function (s, attr) {
    +				var start,
    +					delimiter;
    +
    +				start = s.indexOf('<');
    +				s = s.substring(start, s.indexOf('>') - start);
    +
    +				start = s.indexOf(attr + '=');
    +				if (start !== -1) {
    +					start = start + attr.length + 1;
    +					delimiter = s.charAt(start);
    +					if (delimiter === '"' || delimiter === "'") { // eslint-disable-line quotes
    +						s = s.substring(start + 1);
    +						return s.substring(0, s.indexOf(delimiter));
    +					}
    +				}
     			};
     
     		// The buildText code is quite heavy, so if we're not changing something
    @@ -2427,10 +2441,6 @@ extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
     		// Complex strings, add more logic
     		} else {
     
    -			clsRegex = /<.*class="([^"]+)".*>/;
    -			styleRegex = /<.*style="([^"]+)".*>/;
    -			hrefRegex = /<.*href="([^"]+)".*>/;
    -
     			if (tempParent) {
     				// attach it to the DOM to read offset width
     				tempParent.appendChild(textNode);
    @@ -2485,27 +2495,31 @@ extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
     								renderer.SVG_NS,
     								'tspan'
     							),
    -							spanCls,
    -							spanStyle; // #390
    -						if (clsRegex.test(span)) {
    -							spanCls = span.match(clsRegex)[1];
    -							attr(tspan, 'class', spanCls);
    +							classAttribute,
    +							styleAttribute, // #390
    +							hrefAttribute;
    +						
    +						classAttribute = parseAttribute(span, 'class');
    +						if (classAttribute) {
    +							attr(tspan, 'class', classAttribute);
     						}
    -						if (styleRegex.test(span)) {
    -							spanStyle = span.match(styleRegex)[1].replace(
    +
    +						styleAttribute = parseAttribute(span, 'style');
    +						if (styleAttribute) {
    +							styleAttribute = styleAttribute.replace(
     								/(;| |^)color([ :])/,
     								'$1fill$2'
     							);
    -							attr(tspan, 'style', spanStyle);
    +							attr(tspan, 'style', styleAttribute);
     						}
     
     						// Not for export - #1529
    -						if (hrefRegex.test(span) && !forExport) {
    +						hrefAttribute = parseAttribute(span, 'href');
    +						if (hrefAttribute && !forExport) {
     							attr(
     								tspan,
     								'onclick',
    -								'location.href=\"' +
    -									span.match(hrefRegex)[1] + '\"'
    +								'location.href=\"' + hrefAttribute + '\"'
     							);
     							attr(tspan, 'class', 'highcharts-anchor');
     							/*= if (build.classic) { =*/
    @@ -2649,8 +2663,12 @@ extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
     												dy: dy,
     												x: parentX
     											});
    -											if (spanStyle) { // #390
    -												attr(tspan, 'style', spanStyle);
    +											if (styleAttribute) { // #390
    +												attr(
    +													tspan,
    +													'style',
    +													styleAttribute
    +												);
     											}
     											textNode.appendChild(tspan);
     										}
    
  • package.json+1 0 modified
    @@ -82,6 +82,7 @@
         "aws-sdk": "^2.94.0",
         "babel-runtime": "^6.20.0",
         "glob": "^7.1.2",
    +    "safe-regex": "^1.1.0",
         "taffydb": "^2.7.3"
       }
     }
    
  • samples/unit-tests/svgrenderer/text/demo.js+36 0 modified
    @@ -204,3 +204,39 @@ QUnit.test('Dir rtl (#3482)', function (assert) {
     
         document.getElementById('container').removeAttribute('dir');
     });
    +
    +QUnit.test('Attributes', function (assert) {
    +    var ren = new Highcharts.Renderer(
    +        document.getElementById('container'),
    +        600,
    +        400
    +    );
    +
    +    var text = ren
    +        .text(
    +            'The quick brown fox jumps <span class="red">over</span> the lazy dog',
    +            20,
    +            20
    +        )
    +        .add();
    +
    +    assert.strictEqual(
    +        text.element.childNodes[1].getAttribute('class'),
    +        'red',
    +        'Double quotes, red span should be picked up'
    +    );
    +
    +    text = ren
    +        .text(
    +            "The quick brown fox jumps <span class='red'>over</span> the lazy dog",
    +            20,
    +            20
    +        )
    +        .add();
    +
    +    assert.strictEqual(
    +        text.element.childNodes[1].getAttribute('class'),
    +        'red',
    +        'Single quotes, red span should be picked up'
    +    );
    +});
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.