CVE-2018-20801 (published 2019-03-14T16:29Z)

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.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 7.5, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-185
cwe_name: Incorrect Regular Expression
description: The product specifies a regular expression in a way that causes data to be improperly matched or compared.
extended_description: When the regular expression is used in protection mechanisms such as filtering or validation, this may allow an attacker to bypass the intended restrictions on the incoming data.
         

Fix information:
repo_url: https://github.com/highcharts/highcharts
commit_details: repo_url: https://github.com/highcharts/highcharts
author: Torstein Hønsi
author_date: 2018-03-01 10:37:21+01:00
msg: Refactored SVGRenderer text to not use backtracking regexes to replace attributes.

Adds support for single quoted class, href and style attributes in strings.
repository: repo_url: https://github.com/highcharts/highcharts
repo_name: highcharts/highcharts
description: Highcharts JS, the JavaScript charting framework
repo_language: TypeScript
owner: highcharts
file_changes: filename: SvgRenderer.js
change_type: ModificationType.MODIFY
diff: @@ -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);
 										}

programming_language: JavaScript
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -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"
   }
 }

programming_language: JavaScript
filename: demo.js
change_type: ModificationType.MODIFY
diff: @@ -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'
+    );
+});

programming_language: JavaScript