GHSA-2J4C-FFCH-9F23
Vulnerability from github – Published: 2026-10-08 16:31 – Updated: 2026-10-08 16:31Summary
Any file whose first 8 bytes are the OLE compound-file signature (D0 CF 11 E0 A1 B1 1A E1) is routed by OpenFile/OpenReader/OpenBytes → openReaderAt → Decrypt. The version dispatch only guarantees len(EncryptionInfo) >= 4 before handing attacker-controlled EncryptionInfo/EncryptedPackage buffers to standardDecrypt/agileDecrypt, and no callee validates structure. Malformed but version-valid content therefore fails as an unrecovered runtime panic instead of an error, terminating the calling process.
Details
All panic classes below were execution-confirmed against pristine master (ecd99d761fe0, 2026-09-08). excelize.go:211-213 maps Decrypt errors to ErrWorkbookFileFormat, but panics bypass that path and kill the process.
| # | Malformed input | Panic | Site |
|---|---|---|---|
| 1 | standard, len(EncryptionInfo) 4–11 |
slice bounds [:12] |
crypt.go:238 |
| 2 | standard, attacker-controlled headerSize uint32 |
slice bounds [12:12+headerSize] / fixed-offset header reads |
crypt.go:238-249 |
| 3 | standard, verifier remainder < 72 (AES) / 60 (RC4) bytes | slice bounds in standardEncryptionVerifier |
crypt.go:282-295 |
| 4 | standard, header.KeySize = 0xFFFFFFFF |
slice bounds [:536870911] with capacity 48 |
crypt.go:321 |
| 5 | standard, EncryptedPackage stream missing/short |
slice bounds [8:0] |
crypt.go:268 |
| 6 | agile, len(EncryptionInfo) 4–7 |
slice bounds [8:4] |
crypt.go:407 |
| 7 | agile, valid XML without <keyEncryptors> |
index out of range [0] with length 0 |
crypt.go:416, 433 |
| 8 | agile, saltValue decoded length ≠ AES block |
cipher.NewCBCDecrypter: IV length must equal block size |
crypt.go:425 → 512 |
| 9 | any, keyData blockSize="0" |
integer divide by zero | crypt.go:539 |
Note the asymmetry pinpointing the missing constraint: the agile path already checks len(EncryptedPackage) >= 8 (crypt.go:520-523) but the standard path does not (#5). Existing tests only cover the error paths (short <4 bytes → ErrUnknownEncryptMechanism, bad XML, base64 errors), never these panic paths.
PoC
Standalone programs (public API only, inputs built in memory) were provided to the maintainer by email: 1-decrypt-panic builds seven malformed CFB containers and shows each panic escaping the public Decrypt API plus one end-to-end OpenReader crash. All cases print PANIC on master and BLOCKED with the proposed patch. A regression guard proves legitimate decryption is unaffected: a workbook encrypted with the package's own Encrypt() still opens through the same code path.
(A separate advisory covers the unbounded/negative allocation in extractPart.)
Impact
Any service that calls OpenFile/OpenReader/OpenBytes on untrusted input (upload processing, mail scanning, spreadsheet conversion) can be killed remotely and without authentication by a file of ~100 bytes to ~3 KB. No password is required — panics occur during structural/parameter handling before successful decryption. Site variety means filtering one pattern does not help.
Proposed fix
A recover() boundary in Decrypt mapping any panic to ErrWorkbookFileFormat (restores the documented error-routing contract; legitimate standard/agile decryption unaffected). A complete patch has been provided to the maintainer; per-site length validation is recommended as defense in depth.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/xuri/excelize/v2"
},
"ranges": [
{
"events": [
{
"introduced": "2.3.1"
},
{
"fixed": "2.11.1-0.20260915055537-22f76f9acb94"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107214"
],
"database_specific": {
"cwe_ids": [
"CWE-248"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T16:31:32Z",
"nvd_published_at": "2026-10-07T18:17:19Z",
"severity": "HIGH"
},
"details": "### Summary\n\nAny file whose first 8 bytes are the OLE compound-file signature (`D0 CF 11 E0 A1 B1 1A E1`) is routed by `OpenFile`/`OpenReader`/`OpenBytes` \u2192 `openReaderAt` \u2192 `Decrypt`. The version dispatch only guarantees `len(EncryptionInfo) \u003e= 4` before handing attacker-controlled `EncryptionInfo`/`EncryptedPackage` buffers to `standardDecrypt`/`agileDecrypt`, and no callee validates structure. Malformed but version-valid content therefore fails as an **unrecovered runtime panic** instead of an error, terminating the calling process.\n\n### Details\n\nAll panic classes below were execution-confirmed against pristine master (`ecd99d761fe0`, 2026-09-08). `excelize.go:211-213` maps `Decrypt` *errors* to `ErrWorkbookFileFormat`, but panics bypass that path and kill the process.\n\n| # | Malformed input | Panic | Site |\n|---|---|---|---|\n| 1 | standard, `len(EncryptionInfo)` 4\u201311 | slice bounds `[:12]` | `crypt.go:238` |\n| 2 | standard, attacker-controlled `headerSize` uint32 | slice bounds `[12:12+headerSize]` / fixed-offset header reads | `crypt.go:238-249` |\n| 3 | standard, verifier remainder \u003c 72 (AES) / 60 (RC4) bytes | slice bounds in `standardEncryptionVerifier` | `crypt.go:282-295` |\n| 4 | standard, `header.KeySize` = 0xFFFFFFFF | slice bounds `[:536870911]` with capacity 48 | `crypt.go:321` |\n| 5 | standard, `EncryptedPackage` stream missing/short | slice bounds `[8:0]` | `crypt.go:268` |\n| 6 | agile, `len(EncryptionInfo)` 4\u20137 | slice bounds `[8:4]` | `crypt.go:407` |\n| 7 | agile, valid XML without `\u003ckeyEncryptors\u003e` | index out of range `[0]` with length 0 | `crypt.go:416`, `433` |\n| 8 | agile, `saltValue` decoded length \u2260 AES block | `cipher.NewCBCDecrypter: IV length must equal block size` | `crypt.go:425` \u2192 `512` |\n| 9 | any, `keyData blockSize=\"0\"` | integer divide by zero | `crypt.go:539` |\n\nNote the asymmetry pinpointing the missing constraint: the agile path already checks `len(EncryptedPackage) \u003e= 8` (`crypt.go:520-523`) but the standard path does not (#5). Existing tests only cover the error paths (short `\u003c4` bytes \u2192 `ErrUnknownEncryptMechanism`, bad XML, base64 errors), never these panic paths.\n\n### PoC\n\nStandalone programs (public API only, inputs built in memory) were provided to the maintainer by email: `1-decrypt-panic` builds seven malformed CFB containers and shows each panic escaping the public `Decrypt` API plus one end-to-end `OpenReader` crash. All cases print PANIC on master and BLOCKED with the proposed patch. A regression guard proves legitimate decryption is unaffected: a workbook encrypted with the package\u0027s own `Encrypt()` still opens through the same code path.\n\n(A separate advisory covers the unbounded/negative allocation in `extractPart`.)\n\n### Impact\n\nAny service that calls `OpenFile`/`OpenReader`/`OpenBytes` on untrusted input (upload processing, mail scanning, spreadsheet conversion) can be killed remotely and without authentication by a file of ~100 bytes to ~3 KB. No password is required \u2014 panics occur during structural/parameter handling before successful decryption. Site variety means filtering one pattern does not help.\n\n### Proposed fix\n\nA `recover()` boundary in `Decrypt` mapping any panic to `ErrWorkbookFileFormat` (restores the documented error-routing contract; legitimate standard/agile decryption unaffected). A complete patch has been provided to the maintainer; per-site length validation is recommended as defense in depth.",
"id": "GHSA-2j4c-ffch-9f23",
"modified": "2026-10-08T16:31:32Z",
"published": "2026-10-08T16:31:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/security/advisories/GHSA-2j4c-ffch-9f23"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107214"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/pull/2395"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/commit/22f76f9acb94b85b3eb9c4365ab4f750cebbc565"
},
{
"type": "PACKAGE",
"url": "https://github.com/qax-os/excelize"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Excelize Decrypt: unrecoverable panics on malformed OLE/CFB encrypted workbooks"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.