Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-mx22-3794-2vpv
Summary
extractPivotTableFields builds order := pc.getPivotCacheFieldsName() from xl/pivotCache/pivotCacheDefinitionN.xml's <cacheFields> list, then indexes it with values taken from a separately parsed xl/pivotTables/pivotTableN.xml part with zero cross-validation: order[field.Fld] where field.Fld is a raw attacker-controlled int from the <dataField fld="N"> attribute, and order[fieldIdx] where fieldIdx is a loop index over pt.PivotFields.PivotField that need not match the cache's field count. I executed two independent PoCs against the current HEAD: (1) trimmed the pivot cache's <cacheFields> to count=0 while leaving the pivot table's 2 pivotFields untouched -> runtime error: index out of range [0] with length 0 at pivotTable.go:1431; (2) left the 2 cache fields completely intact and only changed one attribute, fld="1" -> fld="99999", in xl/pivotTables/pivotTable1.xml -> runtime error: index out of range [99999] with length 2 at pivotTable.go:1443. Both stack traces confirmed via non-recovered go test runs: extractPivotTableFields -> getPivotTable -> GetPivotTables.
Reachability / who can trigger this
Unauthenticated: any application that opens an untrusted .xlsx containing a pivot table and calls the public File.GetPivotTables(sheet) API. excelize has no recover() anywhere, so this crashes the host process (CLI/worker) or surfaces as an unhandled 500 in a request-scoped-recover HTTP service. Deterministic, single small crafted file, no user interaction beyond the service's normal open-and-read flow.
Proof of Concept / Reproduction
Method:
- Verified source at HEAD (commit e81f0500, 2026-09-27, freshly cloned/up to date;
git statusclean except the new PoC files I added): read pivotTable.go:1425-1454 and confirmed verbatim:order := pc.getPivotCacheFieldsName()(1428), loopfor fieldIdx, field := range pt.PivotFields.PivotFieldindexingorder[fieldIdx]at axisRow/axisCol/axisPage (1431/1434/1437), andData: order[field.Fld](1443) inside thept.DataFields.DataFieldloop -- zero bounds checks anywhere. Confirmed xmlPivotTable.go:271Fld intxml:"fld,attr"`` on xlsxDataField (raw attacker-controlled int, no validation) and the sibling Fld field at line 252. Rango build ./...-- compiles clean. Both citations are accurate. - Found the repo already contained an untracked PoC test file from earlier work (pivot_fld_poc_test.go) implementing this exact finding via a byte-level zip-tamper technique (build a legitimate workbook with excelize's own public AddPivotTable API, then hex-edit one raw XML part inside the saved .xlsx's zip bytes -- not a hypothetical reimplementation, uses the real unmodified excelize package). Did not just trust it: ran it myself with
go test -run 'TestPivotTableDataFieldFldIndexPanic|TestPivotTableDataFieldFldAttributeOutOfRangePanic' -v .and observed real PASS output with exact matching panic strings. - For stronger, independent evidence beyond a recover-wrapped test, I wrote my own standalone non-test Go program from scratch, cmd/pivotcrash/main.go (module github.com/xuri/excelize/v2, go1.27.0 toolchain), that imports the real compiled excelize package (no reimplementation) with two subcommands:
gen/gen-trim(attacker: build a legit pivot-table workbook via the public API, then rezip with one XML part tampered -- either dataField fld="1"->fld="99999", or the pivot cache's <cacheFields count="2"> collapsed to count="0" -- writing the crafted .xlsx to disk) andrun(victim: a fresh OS process that does exactly what any consumer service does --excelize.OpenFile(path)thenf.GetPivotTables("Sheet1")-- with zero recover() anywhere in the file or the call chain). Executed as four separatego runprocess invocations: gen, run, gen-trim, run.
Evidence:
Test run (pre-existing PoC file, re-executed by me, both PASS):
--- PASS: TestPivotTableDataFieldFldIndexPanic (0.00s) logging CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [0] with length 0
--- PASS: TestPivotTableDataFieldFldAttributeOutOfRangePanic (0.00s) logging CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [99999] with length 2
ok github.com/xuri/excelize/v2 0.114s
Standalone process-crash run #1 (go run ./cmd/pivotcrash run pivotcrash_malicious.xlsx, fld="99999" tamper, cache untouched with 2 fields):
[victim] file opened successfully, workbook parsed with no errors
[victim] now calling f.GetPivotTables("Sheet1") on the untrusted workbook...
panic: runtime error: index out of range [99999] with length 2
goroutine 1 [running]:
github.com/xuri/excelize/v2.(*File).extractPivotTableFields(...)
.../pivotTable.go:1443 +0xb9b
github.com/xuri/excelize/v2.(*File).getPivotTable(...)
.../pivotTable.go:1379 +0x7f6
github.com/xuri/excelize/v2.(*File).GetPivotTables(...)
.../pivotTable.go:1278 +0x2f7
main.runVictim(...) / main.main() -> exit status 2
Standalone process-crash run #2 (go run ./cmd/pivotcrash run pivotcrash_malicious_trim.xlsx, cacheFields trimmed to count=0, pivot table's 2 pivotFields untouched):
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
github.com/xuri/excelize/v2.(*File).extractPivotTableFields(...)
.../pivotTable.go:1431 +0xc15
github.com/xuri/excelize/v2.(*File).getPivotTable(...)
.../pivotTable.go:1379 +0x7f6
github.com/xuri/excelize/v2.(*File).GetPivotTables(...)
.../pivotTable.go:1278 +0x2f7
main.runVictim(...) / main.main() -> exit status 2
In both standalone runs the "GetPivotTables RETURNED NORMALLY" print statement that follows the call was never reached -- the OS process itself terminated via an unrecovered Go panic (exit status 2), for a crafted .xlsx that excelize.OpenFile accepted without any error. Both stack traces terminate at the exact claimed sink lines (pivotTable.go:1443 and :1431) via the exact claimed call chain (extractPivotTableFields -> getPivotTable -> GetPivotTables). This is a real, unhandled crash of a real process running unmodified excelize code -- not a mock, not a simulated/hypothetical trace.
Novelty
Ran all 5 requested checks in full, plus extra verification. (1) Pulled the complete, paginated GHSA list via gh api (16 advisories, all state=published, no hidden drafts) and grepped full descriptions, not just summaries, for "pivot" -- only one incidental, non-overlapping hit (GHSA-wp2g-vpjj-g53r cites pivotTable.go:538 as an AddPivotTable call site for an unrelated formula-recursion stack-overflow bug). (2) Ran ~15 targeted GitHub issue searches (extractPivotTableFields, getPivotCacheFieldsName, GetPivotTables panic, dataField fld, BaseField, ShowValuesAs, "index out of range", cacheFields count mismatch, PivotFields panic, DataField, plus a broad "pivot" query returning 30 issues/PRs all individually triaged by title) and full-text-read the 5 most plausible (#2161, #1937, #2183, #1954, #1945) -- none match; the closest (#2161) is a nil-pointer bug in a different function/field, detailed above. (3) Checked PRs both by keyword (is:pr pivot = 10, is:pr "pivotTable.go" fld = 0, all reviewed; read PR #2168's full diff) and exhaustively (pulled all 31 currently-open PRs on the repo and reviewed every title) -- none touch pivot field indexing. (4) Ran 5 WebSearch queries combining the mechanism with "excelize"/"pivot table"/"CVE", and independently cross-checked OSV.dev's API (which mirrors NVD/GHSA/GO vuln DB) -- turned up only the already-ruled-out GHSA-fx5j (shared-string) and GHSA-h69g (row allocation) advisories, and a non-upstream automated "task"-tracking repo (windyswe/q ...[truncated for brevity]
Closest prior art considered: qax-os/excelize issue #2161 + merged PR #2168 ("This closes #2161, fix panic on read unsupported pivot table cache source types", merged 2025-07-05). That bug: pivotCacheDefinition.xml's <cacheSource type="external"/> has no <worksheetSource> child, so pc.CacheSource.WorksheetSource is a nil pointer, and getPivotTable() (then pivotTable.go:875) dereferenced .Sheet/.Ref on it -> nil-pointer-dereference panic, reached via GetPivotTables (then line 803). Fix was an early type-validation error return in getPivotTable, not any bounds check. SAME-FIX TEST: would that fix also close the candidate bug? No. The candidate's panic is index-out-of-range (not nil-deref) inside a different function, extractPivotTableFields, on a different data structure: order := pc.getPivotCacheFieldsName() (length = count of <cacheFields>) indexed by (a) a loop counter over pt.PivotFields.PivotField (a separate, unrelated count from pivotTableN.xml) at lines 1431/1434/1437, and (b) the raw unvalidated int field.
...[truncated for brevity]
Independent skeptical review
Tried hard to kill this and it holds. Independently re-read current HEAD (e81f05008b3, fetched and diffed against origin/master to confirm it's current) rather than trusting the report: pivotTable.go:1427-1454 (extractPivotTableFields) has zero bounds checks on order[fieldIdx] (axisRow/Col/Page branches, lines 1431/1434/1437) or order[field.Fld] (line 1443) — confirmed by direct Read, matching the claim's line citations exactly. This is a genuine oversight, not a designed invariant: the very next function in the same file, extractPivotTableShowValuesAs (lines 1476, 1486) and extractPivotTableField (line 1508), DO bounds-check structurally identical order/slice indices before using them — the developers clearly know this pattern needs guarding and simply missed it in extractPivotTableFields. Confirmed xmlPivotTable.go:271 Fld is a bare unchecked int XML attribute, and confirmed getPivotTable/pivotCacheReader/pivotTableReader perform no count-vs-actual-length cross-validation between the independently-parsed pivotTable and pivotCache XML parts. Grepped the entire repo for recover() — zero hits outside the researcher's own test file, confirming "no recover anywhere" is accurate. Re-ran both provided PoCs myself in a throwaway test file against this exact HEAD (go test -run TestPoC_PivotCache -v): both panic exactly as claimed, with byte-identical messages — "index out of range [0] with length 0" (truncated-cache-fields PoC, line 1431) and "index out of range [99999] with lengt
...[truncated for brevity]
Upload your own SBOM in CycloneDX 1.6 or higher (JSON) directly here to check your vulnerabilities.
Drag and drop some file here, or click to select
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights. No user interaction is needed for the attacker to exploit this vulnerability.
Exploitation attempts have been detected. Elevated vigilance and prompt remediation are advised.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard