CVE-2021-32796 (published 2021-07-27T22:15Z)

xmldom is an open source pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. xmldom versions 0.6.0 and older do not correctly escape special characters when serializing elements removed from their ancestor. This may lead to unexpected syntactic changes during XML processing in some downstream applications. This issue has been resolved in version 0.7.0. As a workaround downstream applications can validate the input and reject the maliciously crafted documents.

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

Weakness information:
cwe_id: CWE-116
cwe_name: Improper Encoding or Escaping of Output
description: The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.
extended_description: 
            Improper encoding or escaping can allow attackers to change the commands that are sent to another component, inserting malicious commands instead.
            Most products follow a certain protocol that uses structured messages for communication between components, such as queries or commands. These structured messages can contain raw data interspersed with metadata or control information. For example, "GET /index.html HTTP/1.1" is a structured message containing a command ("GET") with a single argument ("/index.html") and metadata about which protocol version is being used ("HTTP/1.1").
            If an application uses attacker-supplied inputs to construct a structured message without properly encoding or escaping, then the attacker could insert special characters that will cause the data to be interpreted as control information or metadata. Consequently, the component that receives the output will perform the wrong operations, or otherwise interpret the data incorrectly.
         
         
cwe_id: CWE-91
cwe_name: XML Injection (aka Blind XPath Injection)
description: The product does not properly neutralize special elements that are used in XML, allowing attackers to modify the syntax, content, or commands of the XML before it is processed by an end system.
extended_description: Within XML, special elements could include reserved words or characters such as "<", ">", """, and "&", which could then be used to add new data or modify XML syntax.
         

Fix information:
repo_url: https://github.com/xmldom/xmldom
commit_details: repo_url: https://github.com/xmldom/xmldom
author: Christian Bewernitz
author_date: 2021-07-27 22:32:03+02:00
msg: Merge pull request from GHSA-5fg8-2547-mr8q

resolves https://github.com/xmldom/xmldom/security/advisories/GHSA-5fg8-2547-mr8q
repository: repo_url: https://github.com/xmldom/xmldom
repo_name: xmldom/xmldom
description: A pure JavaScript W3C standard-based (XML DOM Level 2 Core) `DOMParser` and `XMLSerializer` module.
repo_language: HTML
owner: xmldom
file_changes: filename: dom.js
change_type: ModificationType.MODIFY
diff: @@ -1129,6 +1129,15 @@ function needNamespaceDefine(node, isHTML, visibleNamespaces) {
 	}
 	return true;
 }
+/**
+ * Well-formed constraint: No < in Attribute Values
+ * The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <.
+ * @see https://www.w3.org/TR/xml/#CleanAttrVals
+ * @see https://www.w3.org/TR/xml/#NT-AttValue
+ */
+function addSerializedAttribute(buf, qualifiedName, value) {
+	buf.push(' ', qualifiedName, '="', value.replace(/[<&"]/g,_xmlEncoder), '"')
+}
 
 function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
 	if (!visibleNamespaces) {
@@ -1196,8 +1205,7 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
 			if (needNamespaceDefine(attr,isHTML, visibleNamespaces)) {
 				var prefix = attr.prefix||'';
 				var uri = attr.namespaceURI;
-				var ns = prefix ? ' xmlns:' + prefix : " xmlns";
-				buf.push(ns, '="' , uri , '"');
+				addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
 				visibleNamespaces.push({ prefix: prefix, namespace:uri });
 			}
 			serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces);
@@ -1207,8 +1215,7 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
 		if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
 			var prefix = node.prefix||'';
 			var uri = node.namespaceURI;
-			var ns = prefix ? ' xmlns:' + prefix : " xmlns";
-			buf.push(ns, '="' , uri , '"');
+			addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
 			visibleNamespaces.push({ prefix: prefix, namespace:uri });
 		}
 		
@@ -1247,13 +1254,7 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
 		}
 		return;
 	case ATTRIBUTE_NODE:
-		/**
-		 * Well-formedness constraint: No < in Attribute Values
-		 * The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <.
-		 * @see https://www.w3.org/TR/xml/#CleanAttrVals
-		 * @see https://www.w3.org/TR/xml/#NT-AttValue
-		 */
-		return buf.push(' ', node.name, '="', node.value.replace(/[<&"]/g,_xmlEncoder), '"');
+		return addSerializedAttribute(buf, node.name, node.value);
 	case TEXT_NODE:
 		/**
 		 * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,

programming_language: JavaScript
filename: serializer.test.js
change_type: ModificationType.MODIFY
diff: @@ -120,4 +120,23 @@ describe('XML Serializer', () => {
 			)
 		})
 	})
+	describe('properly escapes attribute values', () => {
+		it('should escape special characters in namespace attributes', () => {
+			const input = `<xml xmlns='<&"' xmlns:attr='"&<'><test attr:test=""/></xml>`
+			const doc = new DOMParser().parseFromString(input, MIME_TYPE.XML_TEXT)
+
+			// in this case the explicit attribute nodes are serialized
+			expect(new XMLSerializer().serializeToString(doc)).toBe(
+				'<xml xmlns="&lt;&amp;&quot;" xmlns:attr="&quot;&amp;&lt;"><test attr:test=""/></xml>'
+			)
+
+			// in this case the namespace attributes are "inherited" from the parent,
+			// which is not serialized
+			expect(
+				new XMLSerializer().serializeToString(doc.documentElement.firstChild)
+			).toBe(
+				'<test xmlns:attr="&quot;&amp;&lt;" attr:test="" xmlns="&lt;&amp;&quot;"/>'
+			)
+		})
+	})
 })

programming_language: TypeScript