CVE-2023-34104: Inefficient Regular Expression Complexity (CWE-1333)
value: fast-xml-parser is an open source, pure javascript xml parser. fast-xml-parser allows special characters in entity names, which are not escaped or sanitized. Since the entity name is used for creating a regex for searching and replacing entities in the XML body, an attacker can abuse it for denial of service (DoS) attacks. By crafting an entity name that results in an intentionally bad performing regex and utilizing it in the entity replacement step of the parser, this can cause the parser to stall for an indefinite amount of time. This problem has been resolved in v4.2.4. Users are advised to upgrade. Users unable to upgrade should avoid using DOCTYPE parsing by setting the `processEntities: false` option.
CWE description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
CVSS v3 base score: 7.5
Published: 2023-06-06T18:15Z
Repository: https://github.com/NaturalIntelligence/fast-xml-parser
Language: JavaScript
Files changed: spec/entities_spec.js, src/xmlparser/DocTypeReader.js
Commit date: 2023-06-05 11:14:13 +0530
Commit message: fix security bug

Diff: diff --git a/spec/entities_spec.js b/spec/entities_spec.js
index 41c12f6..a4606d4 100644
--- a/spec/entities_spec.js
+++ b/spec/entities_spec.js
@@ -374,10 +374,28 @@ describe("XMLParser Entities", function() {
         let result = parser.parse(xmlData);
         // console.log(JSON.stringify(result,null,4));
 
         expect(result).toEqual(expected);
     });
+    it("should throw error if an entity name contains special char", function() {
+        const xmlData = `
+        <?xml version="1.0" encoding="UTF-8"?>
+
+        <!DOCTYPE note [
+        <!ENTITY nj$ "writer;">
+        <!ENTITY wr?er "Writer: Donald Duck.">
+        ]>`;
+
+        const options = {
+            processEntities: true,
+        };
+
+        expect(() =>{
+            const parser = new XMLParser(options);
+            parser.parse(xmlData);
+        }).toThrowError("Invalid character $ in entity name")
+    });
 });
 
 describe("XMLParser External Entites", function() {
     it("should throw error when an entity value has '&'", function() {
         const parser = new XMLParser();
diff --git a/src/xmlparser/DocTypeReader.js b/src/xmlparser/DocTypeReader.js
index 32d92f8..3b0fe05 100644
--- a/src/xmlparser/DocTypeReader.js
+++ b/src/xmlparser/DocTypeReader.js
@@ -17,11 +17,11 @@ function readDocType(xmlData, i){
             if (xmlData[i] === '<' && !comment) { //Determine the tag type
                 if( hasBody && isEntity(xmlData, i)){
                     i += 7; 
                     [entityName, val,i] = readEntityExp(xmlData,i+1);
                     if(val.indexOf("&") === -1) //Parameter entities are not supported
-                        entities[ entityName ] = {
+                        entities[ validateEntityName(entityName) ] = {
                             regx : RegExp( `&${entityName};`,"g"),
                             val: val
                         };
                 }
                 else if( hasBody && isElement(xmlData, i))  i += 8;//Not supported
@@ -138,6 +138,18 @@ function isNotation(xmlData, i){
     xmlData[i+8] === 'O' &&
     xmlData[i+9] === 'N') return true
     return false
 }
 
+//an entity name should not contains special characters that may be used in regex
+//Eg !?\\\/[]$%{}^&*()<>
+const specialChar = "!?\\\/[]$%{}^&*()<>";
+
+function validateEntityName(name){
+    for (let i = 0; i < specialChar.length; i++) {
+        const ch = specialChar[i];
+        if(name.indexOf(ch) !== -1) throw new Error(`Invalid character ${ch} in entity name`);
+    }
+    return name;
+}
+
 module.exports = readDocType;
\ No newline at end of file

Vulnerable code:                         entities[ entityName ] = {
Fixed code:     it("should throw error if an entity name contains special char", function() {
        const xmlData = `
        <?xml version="1.0" encoding="UTF-8"?>

        <!DOCTYPE note [
        <!ENTITY nj$ "writer;">
        <!ENTITY wr?er "Writer: Donald Duck.">
        ]>`;

        const options = {
            processEntities: true,
        };

        expect(() =>{
            const parser = new XMLParser(options);
            parser.parse(xmlData);
        }).toThrowError("Invalid character $ in entity name")
    });
                        entities[ validateEntityName(entityName) ] = {
//an entity name should not contains special characters that may be used in regex
//Eg !?\\\/[]$%{}^&*()<>
const specialChar = "!?\\\/[]$%{}^&*()<>";

function validateEntityName(name){
    for (let i = 0; i < specialChar.length; i++) {
        const ch = specialChar[i];
        if(name.indexOf(ch) !== -1) throw new Error(`Invalid character ${ch} in entity name`);
    }
    return name;
}