# Excelize Decrypt: unrecoverable panics on malformed OLE/CFB encrypted workbooks

Excelize's Decrypt hands attacker-controlled OLE EncryptionInfo/EncryptedPackage buffers to standard/agile decryptors without length validation, so malformed workbooks trigger unrecovered panics that kill the process.

- Payout: Research
- Severity: high
- Program: Excelize
- Found by: lmonstergg
- Disclosed: 2026-10-08
- CVE: CVE-2026-107214
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2j4c-ffch-9f23
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2j4c-ffch-9f23
- Tags: area:oss domain:oss impact:dos kind:research platform:ghsa sev:high tech:go technique:parser-differential technique:source-review vuln:dos

**Target:** Go library Decrypt path for OLE/CFB encrypted spreadsheets (OpenFile/OpenReader/OpenBytes)
**Root cause:** Version dispatch only checks len(EncryptionInfo) >= 4 before passing attacker-controlled EncryptionInfo/EncryptedPackage to standardDecrypt/agileDecrypt; no callee validates structure, so slice bounds, index, IV-length and divide-by-zero panics escape as runtime panics rather than errors.
**Steps:**
1. Craft a CFB file starting with `D0 CF 11 E0 A1 B1 1A E1` so OpenFile/OpenReader/OpenBytes routes to Decrypt.
2. Standard path: set len(EncryptionInfo) to 4-11 bytes to trigger slice bounds `[:12]` at crypt.go:238.
3. Standard path: set attacker-controlled headerSize uint32 to force `[12:12+headerSize]` bounds panic at crypt.go:238-249.
4. Standard path: truncate verifier remainder below 72 (AES) / 60 (RC4) bytes to panic in standardEncryptionVerifier (crypt.go:282-295).
5. Standard path: set header.KeySize = 0xFFFFFFFF to panic on `[:536870911]` with capacity 48 (crypt.go:321).
6. Standard path: omit or shorten EncryptedPackage stream to panic on `[8:0]` (crypt.go:268).
7. Agile path: set len(EncryptionInfo) to 4-7 to panic on `[8:4]` (crypt.go:407).
8. Agile path: supply valid XML without `<keyEncryptors>` to panic index out of range at crypt.go:416/433.
9. Agile path: set saltValue decoded length != AES block to panic in cipher.NewCBCDecrypter (crypt.go:425/512).
10. Any path: set keyData blockSize="0" to trigger integer divide by zero (crypt.go:539).
**How found:** Noticed the agile path already checks len(EncryptedPackage) >= 8 (crypt.go:520-523) while the standard path does not, pinpointing the missing constraint; then fuzzed/constructed malformed CFB containers per parameter (headerSize, KeySize, verifier length, saltValue, blockSize) and confirmed each panic against pristine master.
**Exploitation:** Remote unauthenticated DoS: any service calling OpenFile/OpenReader/OpenBytes on untrusted uploads, mail attachments or conversion pipelines is killed by a ~100-byte to ~3 KB file; no password needed since panics occur during structural/parameter handling before decryption.
**Insight:** When a parser has two sibling implementations (standard vs agile), diff their validation checks — an asymmetry like one path checking stream length and the other not is a direct pointer to the missing guard; then vary every length/size field the unchecked path reads.

