Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"schema_version": "1.4.0",
"id": "xlsx-cdata-recursion-cve-2026-38358",
"modified": "2026-05-07T00:00:00Z",
"published": "2026-05-07T00:00:00Z",
"aliases": [
"CVE-2026-38358"
],
"summary": "xlsx (SheetJS Community Edition): Unbounded recursion in unescapexml() leads to remote DoS via crafted XLSX files",
"details": "## Summary\n\nSheetJS xlsx Community Edition (all versions including 0.18.5, unmaintained since 2022) contains an unbounded recursion vulnerability in the XML parser's `unescapexml()` function. A crafted XLSX file containing many sequential `<![CDATA[...]]>` tokens in `xl/sharedStrings.xml` causes recursive calls to exhaust the JavaScript call stack, terminating the Node.js process.\n\n**The package is unmaintained.** Users should migrate to `xlsx-js-style` or `exceljs`.\n\n## Vulnerable code (`xlsx.js:3501-3506`)\n\n```javascript\nreturn function unescapexml(text) {\n var s = text + '';\n var i = s.indexOf(\"<![CDATA[\");\n if(i == -1) return s.replace(encregex, ...).replace(coderegex, ...);\n\n var j = s.indexOf(\"]]>\");\n return unescapexml(s.slice(0, i)) + // Recurse on prefix\n s.slice(i+9, j) + // Raw CDATA content\n unescapexml(s.slice(j+3)); // Recurse on suffix — UNBOUNDED\n};\n```\n\n## Recursion mechanics\n\n- Each `<![CDATA[...]]>` token in the string triggers one recursive call on the suffix\n- N sequential tokens = N stack frames\n- JavaScript does not tail-call optimize this pattern\n- No depth counter or iteration limit exists\n\n**Call chain:**\n```\nXLSX.read(buffer)\n → parse_zip()\n → parse_sst_xml() // Shared Strings Table\n → unescapexml(text)\n → unescapexml(suffix) → Recurses N times for N CDATA tokens\n```\n\n## Proof of Concept\n\nAn XLSX file (~126 KB) containing `xl/sharedStrings.xml` with approximately 9,000 sequential `<![CDATA[a]]>` tokens reliably triggers `RangeError: Maximum call stack size exceeded` on Node.js v18+. Crash threshold: ~8,320 tokens (varies by Node.js version).\n\n## Impact\n\nAny application that processes untrusted XLSX files using SheetJS Community Edition is vulnerable. Attack scenarios:\n\n- Web application file upload endpoints accepting `.xlsx`\n- Email gateway scanners auto-processing attachments\n- REST/GraphQL APIs with Excel import functionality\n- Cloud storage with automatic file processing pipelines\n- Batch ETL/data processing pipelines accepting partner-supplied XLSX files\n\nResult: Node.js process termination, denial of service for the affected service.\n\n## Mitigation\n\nNo patch will be released — SheetJS Community Edition is unmaintained.\n\n**Migrate to:**\n- `xlsx-js-style` (community fork of older sheetjs)\n- `exceljs` (actively maintained alternative)\n\nIf migration is not immediately possible:\n- Reject XLSX uploads larger than a strict size threshold (e.g. 1 MB)\n- Pre-scan `xl/sharedStrings.xml` for excessive CDATA token counts before calling `XLSX.read()`\n- Process XLSX parsing in a worker thread or sandboxed subprocess",
"severity": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H"
}
],
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "xlsx"
},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
}
]
}
],
"database_specific": {
"last_known_affected_version_range": "<= 0.18.5"
}
}
],
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-38358"
},
{
"type": "PACKAGE",
"url": "https://github.com/SheetJS/sheetjs"
},
{
"type": "WEB",
"url": "https://www.npmjs.com/package/xlsx"
},
{
"type": "WEB",
"url": "https://www.npmjs.com/package/xlsx-js-style"
},
{
"type": "WEB",
"url": "https://www.npmjs.com/package/exceljs"
},
{
"type": "WEB",
"url": "https://cwe.mitre.org/data/definitions/674.html"
}
],
"database_specific": {
"cwe_ids": [
"CWE-674"
]
}
}