Skip to main content

Threat Intelligence Database

Comprehensive database of the latest cyber threats affecting organizations worldwide. Filter and search to find specific threat intelligence relevant to your organization.

Pro Console Lifetime

Stop chasing alerts. Route them.

Start free, then upgrade once to turn Radar into an automated delivery engine for your security stack.

Custom feeds / Automations: email, Slack, webhooks, SIEM/MISP / API access (baseline limits)

View Plans & Pricing

API access activates after upgrading in Console -> Billing.

Breach by OffSeqOFFSEQFRIENDS — 25% OFF

Check if your credentials are on the dark web

Instant breach scanning across billions of leaked records. Free tier available.

Scan now

Filter Threats

Narrow down the results by type, severity, or affected countries

Search threats by title, CVE ID, or description. Maximum 100 characters.
Active filters (1):Package: pkg:golang/github.com/qax-os/excelize

Threat Intelligence

Click on any threat for detailed analysis and mitigation recommendations

### 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:** 1) Verified source at HEAD (commit e81f0500, 2026-09-27, freshly cloned/up to date; `git status` clean except the new PoC files I added): read pivotTable.go:1425-1454 and confirmed verbatim: `order := pc.getPivotCacheFieldsName()` (1428), loop `for fieldIdx, field := range pt.PivotFields.PivotField` indexing `order[fieldIdx]` at axisRow/axisCol/axisPage (1431/1434/1437), and `Data: order[field.Fld]` (1443) inside the `pt.DataFields.DataField` loop -- zero bounds checks anywhere. Confirmed xmlPivotTable.go:271 `Fld int `xml:"fld,attr"`` on xlsxDataField (raw attacker-controlled int, no validation) and the sibling Fld field at line 252. Ran `go build ./...` -- compiles clean. Both citations are accurate. 2) 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. 3) 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) and `run` (victim: a fresh OS process that does exactly what any consumer service does -- `excelize.OpenFile(path)` then `f.GetPivotTables("Sheet1")` -- with zero recover() anywhere in the file or the call chain). Executed as four separate `go run` process 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

Join the discussion

Opening a file whose first eight bytes are the OLE magic number sends excelize down the decryption path whether or not the caller set a password, or supports encrypted workbooks at all. `openReaderAt` (excelize.go:198-215) branches on the header alone, and `agileDecrypt` calls `convertPasswdToKey` before the verifier hash is checked, so the key-derivation loop runs `spinCount` times regardless. `spinCount` (crypt.go:98) is a plain `int` filled by a bare `xml.Unmarshal` of the file's own EncryptionInfo stream. Nothing bounds it. A 3072-byte file with spinCount 100000000 makes `OpenFile` take 58.65s on v2.11.0 with default options, then return `zip: not a valid zip file`. It is linear at about 0.6 microseconds per iteration and the attacker picks the number, so 1e9 is roughly ten minutes. Nothing on the path takes a `context.Context`, so the caller cannot cancel it; in an HTTP handler the write timeout returns a response while the goroutine keeps spinning. Memory stays flat at 24 MB, so nothing reclaims it either. ``` v2.5.0 spinCount=10000000 5.307s v2.9.1 spinCount=10000000 5.444s v2.11.0 spinCount=10000000 7.529s v2.11.0 spinCount=100000000 58.654s ``` The loop arrived with `crypt.go` in v2.3.1 and is unchanged through v2.11.0. Excel and LibreOffice write spinCount 100000, which costs 61ms here, so a ceiling well above the legitimate value would cost real files nothing. This is availability only, and it is not a vulnerability for a program that only opens files its own operator produced.

Join the discussion

### Summary A worksheet containing `<mergeCell ref=""/>` makes `mergeCellsParser` leave the cached rectangle empty, and `cellInRange` then indexes that empty slice without a length check. Every non-streaming cell API panics with `index out of range [0] with length 0` on the first cell read after opening the file. ### Where it is `cell.go`, `cellInRange` func cellInRange(cell, ref []int) bool { return cell[0] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= ref[3] } `ref` is indexed at four positions with no bounds check. The caller that can hand it an empty slice, `mergeCellsParser` if ref := ws.MergeCells.Cells[i].Ref; len(ws.MergeCells.Cells[i].rect) == 0 && ref != "" { if strings.Count(ref, ":") != 1 { ref += ":" + ref } rect, err := rangeRefToCoordinates(ref) if err != nil { return cell, err } _ = sortCoordinates(rect) ws.MergeCells.Cells[i].rect = rect } if cellInRange([]int{col, row}, ws.MergeCells.Cells[i].rect) { The `ref != ""` condition means an empty `ref` skips the block that populates `rect`, so `rect` stays nil. The `cellInRange` call on the next line is unconditional and receives that nil slice. `Ref` comes straight from `xl/worksheets/sheetN.xml` through `encoding/xml`, so an empty string is entirely attacker-controlled. ### Impact Any consumer that opens an untrusted workbook and reads a cell panics. The loop scans every merged cell, so any cell reference triggers it, not a specific one. Affected entry points include `GetCellValue`, `GetCellType`, `GetCellFormula`, `SetCellValue` and the in-cell branch of `GetPictures`. The streaming `Rows` and `GetRows` use the SAX path and do not go through this parser, and `GetMergeCells` routes through `Rect()` which errors cleanly, which is probably why this has not surfaced before. There is no option or flag involved; opening the file succeeds and the panic fires on the first cell read. Unless the caller wraps the call in `recover()` it takes the process down. This is a regression. Commit a34c81e (PR #1500, 2023-03-20) replaced `checkCellInRangeRef`, whose `len(rng) != 2` guard returned cleanly for an empty ref, with the cached-rect fast path above, and the guard did not come along. `git merge-base --is-ancestor` confirms that commit is an ancestor of v2.11.0, so released versions are affected as well as HEAD. ### Proof of concept Executed at HEAD. Build a minimal xlsx whose `xl/worksheets/sheet1.xml` contains: <mergeCells count="1"><mergeCell ref=""></mergeCell></mergeCells> Then: f, err := excelize.OpenReader(bytes.NewReader(data)) if err != nil { t.Fatal(err) } _, _ = f.GetCellValue("Sheet1", "A1") Observed, running against the repository at HEAD: panic: runtime error: index out of range [0] with length 0 excelize.cellInRange cell.go:1691 excelize.(*xlsxWorksheet).mergeCellsParser cell.go:1660 excelize.(*File).getCellStringFunc cell.go:1512 excelize.(*File).GetCellValue cell.go:72 `OpenReader` itself returns no error; the file is 1656 bytes. A1, B1 and A2 all reproduce it. For context on how targeted this is, I ran a battery of 38 crafted files covering data validation, conditional formatting, cell and row references, number formats, cols, hyperlinks, dimension, shared strings and tables. Only the empty-ref merge cell panicked; everything else returned a clean error. The other parsing paths look well guarded. ### Suggested fix Skip the entry when the rectangle is empty, before the range test: if len(ws.MergeCells.Cells[i].rect) == 0 { continue } Credit goes to arpitjain099.

Join the discussion

## Affected versions and vulnerable location - Confirmed at `ae2113b` (current HEAD). - Sink: `rows.go:967` `ws.SheetData.Row[rowIdx].C[colNum-1] = *colData` inside `checkRow`. - `checkRow` computes `lastCol` from the column of the last cell in document order (`rows.go:940`), allocates `targetList` of that length, then re-scatters every source cell into `C[colNum-1]`. ## Root cause The slice is sized from the last cell's column, but cells are not required to be column-sorted in the XML. A cell that appears earlier in the row but references a higher column than the last cell has `colNum-1 >= len(targetList)`, so the assignment writes out of range. `MaxColumns`/`TotalRows` do not help, every individual column is valid; the bug is the ordering assumption, not magnitude. ## Attacker model and reachability Any service that opens an untrusted spreadsheet and calls a worksheet API that goes through `workSheetReader -> checkRow` (`excelize.go:332`): `GetCellValue`, `GetCellFormula`, `CalcCellValue`, `GetMergeCells`, `SetCellValue`, and essentially every non-streaming worksheet call. (The streaming `GetRows`/`Rows()` SAX path does not trigger it.) Unauthenticated, deterministic, unrecovered panic -> process crash. ## Proof of concept (executed) Crafted `xl/worksheets/sheet1.xml` with a row whose cells are out of column order and whose earlier cell exceeds the last cell's column: ```xml <row r="1"><c r="D1"><v>4</v></c><c r="C1"><v>3</v></c></row> ``` `GetCellValue("Sheet1","A1")` (via `getCellStringFunc -> workSheetReader -> checkRow`) panicked `index out of range [3] with length 3` at `rows.go:967`. Confirming grep: ```bash rg -n "func checkRow|lastCol|Row\[rowIdx\].C\[colNum-1\]" rows.go ``` ## Suggested fix Size `targetList` from the maximum cell column in the row (not the last cell in document order), or bounds-check `colNum-1` against `len(targetList)` and grow the slice as needed before the assignment.

Join the discussion

### Summary A Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap) A Zip64 uncompressed-size in the range [2^63, 2^64) casts to a negative `int64` in `zip.File.FileInfo().Size()`, and `ReadZipReader` does signed arithmetic on that value, so the total-decompression guard is bypassed and the negative size reaches `make([]byte, 0, size)` in `readFile`. A 159-byte crafted file panics `OpenFile` and `OpenReader` with `runtime error: makeslice: cap out of range`. The guard, at `lib.go:44-46` on HEAD `ae2113b`: ```go fileSize := v.FileInfo().Size() unzipSize += fileSize if unzipSize > f.options.UnzipSizeLimit { return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit) } ``` `FileInfo().Size()` returns `int64(UncompressedSize64)`. `UncompressedSize64` is read from the Zip64 extended-information extra record in the central directory and is fully attacker controlled, so any value with the top bit set arrives as a negative `int64`. `unzipSize` then goes negative and the comparison against `UnzipSizeLimit` (default `1000 << 24`, `templates.go:193`) is false no matter how many such entries the archive contains. The same negative value also fails the two stream-to-temp checks at `lib.go:53` and `lib.go:64` (`fileSize > f.options.UnzipXMLSizeLimit`), so the entry is not diverted to a temp file and control reaches `readFile`: ```go // lib.go:150 dat := make([]byte, 0, file.FileInfo().Size()) ``` `make` with a negative capacity panics. There is no `recover()` in any non-test file in the library, so the panic leaves the public API and takes down the calling goroutine. The asymmetry inside this same function is the clearest way to see it: `unzipToTemp` (`lib.go:83`) handles an entry with a plain `io.Copy` and never pre-allocates from the declared size, so it is indifferent to whatever the header claims. Only the pre-allocating branch trusts that number, and it trusts it after a signed comparison that a wrapped value walks straight through. ## Reproduction, executed against HEAD `ae2113b` (go1.26.5) A 159-byte archive was built with one stored entry named `xl/worksheets/sheet1.xml`: truthful 1-byte local header, central-directory 32-bit uncompressed size set to the `0xFFFFFFFF` Zip64 sentinel, and a Zip64 extra record (tag `0x0001`, data size 8) declaring `UncompressedSize64 = 0x8000000000000000`. ``` entry "xl/worksheets/sheet1.xml" FileInfo().Size() = -9223372036854775808 excelize.OpenReader(bytes.NewReader(raw)) -> panic: runtime error: makeslice: cap out of range ``` Control, the identical archive with `UncompressedSize64 = 0x7FFFFFFFFFFFFFFF`: ``` entry "xl/worksheets/sheet1.xml" FileInfo().Size() = 9223372036854775807 excelize.OpenReader(bytes.NewReader(raw)) -> err = "unzip size exceeds the 16777216000 bytes limit" ``` The control isolates the defect to the sign flip rather than to size magnitude: the guard behaves correctly for any positive declared size and fails only once the value wraps. `archive/zip` itself accepts the archive without complaint, so nothing upstream of excelize rejects it. ## What I verified and what I did not I ran both cases above myself and confirmed each cited line at HEAD. I caught the panic with a deliberate `recover` in my harness so I could print it; nothing in excelize recovers it, which I checked by grepping every non-test `.go` file. I did not separately run the `OpenFile` variant, since `OpenFile` reaches the same `ReadZipReader` loop, and I did not attempt to turn the bypassed size cap into a separate decompression-bomb primitive. ## Attacker model Any service that opens a spreadsheet it did not produce. The payload is 159 bytes, needs no authentication, and is deterministic. ## Suggested fix Compare against the limit in unsigned space, and bound the allocation independently rather than trusting the header: ```go if v.UncompressedSize64 > uint64(f.options.UnzipSizeLimit) { return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit) } ``` and in `readFile`, reject or clamp a declared size that is negative or larger than the limit before calling `make`. The same unsigned treatment applies to the two `UnzipXMLSizeLimit` comparisons, which currently route a wrapped size away from the safe streaming path and into the pre-allocating one.

Join the discussion

## Summary `File.GetStyle` indexes the fill, border and font tables with values taken straight out of `xl/styles.xml`, and the conditions gating those lookups check only the upper bound. A workbook whose `cellXfs` entry carries `fillId="-1"`, `borderId="-1"` or `fontId="-1"` reaches a negative slice index and panics. excelize has no `recover()`, so the panic leaves `GetStyle` and takes the calling process with it. Same defect class as GHSA-48hm-4h8j-58fg, the negative shared-string index, in a different file. That one was fixed by adding the missing lower bound; these three sites still lack it. ## Where it is `styles.go`, in `GetStyle`, lines 1683, 1686 and 1689: ```go xf := s.CellXfs.Xf[idx] if extractStyleCondFuncs["fill"](xf, s) { f.extractFills(s.Fills.Fill[*xf.FillID], s, style) } if extractStyleCondFuncs["border"](xf, s) { f.extractBorders(s.Borders.Border[*xf.BorderID], s, style) } if extractStyleCondFuncs["font"](xf, s) { style.Font = extractFont(s.Fonts.Font[*xf.FontID]) } ``` The conditions, at lines 1171 to 1185, bound only the top: ```go "fill": func(xf xlsxXf, s *xlsxStyleSheet) bool { return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) && xf.FillID != nil && s.Fills != nil && *xf.FillID < len(s.Fills.Fill) }, ``` `*xf.FillID < len(...)` is satisfied by any negative value. `FillID`, `BorderID` and `FontID` are `*int` unmarshalled directly from the `fillId`, `borderId` and `fontId` attributes, so the value is whatever the file says. The border and font conditions have the same shape. The correct pattern is already in the same function, six lines above at 1677: ```go if idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx { return style, newInvalidStyleID(idx) } ``` and again at 1783. So the style index itself is guarded on both sides while the three ids it leads to are not. ## Proof of concept Executed against `master` at `f98df08`, which is the merge of #2366, so this is current rather than historical. The harness builds a normal styled workbook with excelize, rewrites one attribute of the `cellXfs` entry inside the zip to `-1`, reopens it and calls `GetCellStyle` then `GetStyle`: ``` control (all >= 0) ok style=true err=<nil> fillId=-1 PANIC: runtime error: index out of range [-1] borderId=-1 PANIC: runtime error: index out of range [-1] fontId=-1 PANIC: runtime error: index out of range [-1] ``` The control confirms the harness reads a valid file correctly, so the three panics are the negative ids rather than a broken fixture. Worth mentioning because my first attempt at this harness patched only `fillId` and appeared to show the other two were fine; they are not, the replacement had simply missed them. ## Impact Any application that opens an untrusted `.xlsx` and reads cell styling crashes. `GetStyle` is reached through `GetCellStyle` and from the style-copying and rendering paths, so it sits on the ordinary read path. With no `recover()` anywhere in excelize, a CLI or worker exits and a service returns 500 per request unless the caller installed its own recovery middleware. No memory corruption and no information disclosure. Availability only. ## Suggested fix Add the lower bound to each of the three conditions, matching what line 1677 already does for the style index: ```go *xf.FillID >= 0 && *xf.FillID < len(s.Fills.Fill) ``` and the same for `BorderID` and `FontID`. Three one-line changes in `extractStyleCondFuncs`.

Join the discussion

Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, flatCols expands file-loaded column ranges without validating Min and Max against the worksheet column limit. SetColWidth reaches flatCols, which expands xlsxCol.Min through xlsxCol.Max without enforcing MaxColumns. When a crafted worksheet supplies an oversized col max attribute and the application invokes a column mutator, flatCols performs a deep copy and append for every attacker-selected column number, allowing an attacker to consume excessive CPU and memory or trigger OOM. No fixed version is available as of this review.

Join the discussion

Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.7.0 to 2.11.0, conditional-format extraction indexes required child slices or dereferences an optional colorScale child without validating malformed rule structure. GetConditionalFormats reaches extractCondFmtCellIs and also indexes ColorScale.Cfvo, DataBar.Cfvo, and DataBar.Color without complete structural checks. When a crafted worksheet supplies a cellIs, dataBar, or colorScale rule missing expected children and the application calls GetConditionalFormats, missing formula, color, value-object, or colorScale data reaches an out-of-range index or nil dereference, allowing an attacker to panic and terminate an unprotected process. No fixed version is available as of this review.

Join the discussion

Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.10.1 to 2.11.0, RIGHT validates the requested length with UTF-16 code-unit counts but slices a rune array using Unicode code-point counts. RIGHT reaches leftRight through CalcCellValue, where countUTF16String validates one unit but utf8.RuneCountInString supplies the slice index in another. When RIGHT evaluates supplementary-plane text with a requested character count between the rune count and UTF-16 code-unit count, the inconsistent units produce a negative rune-slice index, allowing an attacker to panic during formula evaluation. No fixed version is available as of this review.

Join the discussion

Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.0.0 to 2.11.0 in github.com/xuri/excelize/v2 and from 1.1.0 to 1.4.1 in github.com/xuri/excelize, ColumnNameToNumber accumulates a bijective base-26 value in int64 without detecting overflow, allowing an invalid long column name to wrap to zero with no error. ColumnNameToNumber accepts the overflowing name VGWQHXLSDVIKWV, after which checkSheetR0 and xlsxWorksheet.checkRow use the wrapped column value as an index. When a crafted worksheet uses an overflowing column name in a row normalized by checkSheetR0 or checkRow, the wrapped zero column becomes a negative slice index during worksheet normalization, allowing an attacker to panic and terminate the calling process. No fixed version is available as of this review.

Join the discussion

Showing 1 to 10 of 17 results

Filters:Package: pkg:golang/github.com/qax-os/excelize
Page 1 of 2
OffSeq TrainingCredly Certified

Lead Pen Test Professional

Technical5-day eLearningPECB Accredited
View courses