CSV Formula Injection: The Spreadsheet That Opens a Command Prompt
Your export endpoint writes user-controlled strings into CSV cells. When a cell starts with =, @, +, -, or a tab, Excel and friends treat it as code — and a crafted cell can chain DDE into a command prompt on the analyst's machine. Here's the two-sided detection: planted formula cells already present, and the reflection vector that lets attackers plant them — proven without sending a single formula.
Of all the injection families, CSV formula injection has the most cinematic payload: a spreadsheet cell that opens a command prompt on the machine of whoever opened the file. The cell looks like data. The spreadsheet application — Excel, LibreOffice, Google Sheets on import — sees a leading = and evaluates it as a formula. And formulas can do more than math: with DDE (Dynamic Data Exchange) syntax like =cmd|'/C calc'!A0, a formula can ask the operating system to run a program.
The attack needs one thing from your application: an export endpoint that writes strings you don't fully control — usernames, profile fields, ticket titles, product names — into CSV cells without neutralizing formula prefixes. The attacker sets their display name to =cmd|'/C powershell ...' and waits for your staff member to export the user list and open it. Your server stored the string; their spreadsheet executed it.
Two Different Risk Questions
Most write-ups conflate two separable findings, and the distinction drives both severity and the fix:
Risk A — planted cells already present (HIGH). The export file, as served right now, contains cells beginning with formula-meaningful characters: = followed by function syntax; @ (legacy XLS function syntax); a tab or CR (prefix smuggling across cell boundaries); or +/- followed by DDE chain indicators (|, =, !). This means someone has already injected formula-shaped content into your data, and any spreadsheet that opens the export will evaluate it. This is not a theoretical vector — the payload is in production data, in a file you serve.
Risk B — the reflection vector (MEDIUM). The export reflects request input into cell position without RFC 4180 quoting. If ?q=2mnyCHECK comes back as a bare 2mnyCHECK cell (unquoted), then ?q==HYPERLINK(...) would come back as a bare formula too. The vector is proven; the payload is up to the attacker's imagination.
Detecting It Without Ever Sending a Formula
The safety line for this class is absolute: a scanner must never store a payload on the target and never send an actual formula. Our csv_formula_injection scanner honors both with a two-phase design:
Phase 1 — export discovery and cell audit. The scanner crawls the site surface for export-shaped endpoints — paths and parameters matching csv/export/download/report/format/excel/spreadsheet, and .csv/.tsv/.xls/.xlsx extensions — fetches each (capped at 5,000 cells and 512KB per file), parses the rows, and flags any existing cell whose value starts with a formula prefix. Planted cells are reported as evidence, never executed — the report shows the cell position and a redacted prefix, and the finding quotes the path.
Phase 2 — reflection check with a harmless marker. The export is requested with a purely alphanumeric parameter: ?q=2mny plus a random nonce. Alphanumerics can't be a formula, can't escape a cell, can't alter a spreadsheet's behavior — they can only prove one thing: whether the value lands in a cell unquoted. If the nonce appears bare in a cell, the export reflects request input without quoting (the exact precondition an attacker needs), and the medium-severity reflection finding fires. If the export quotes it ("2mny...") or ignores unknown parameters, there's no vector — nothing was sent that could have been one.
No =, +, -, @, tab, or CR is ever transmitted by the scanner. The proof of the vector is the reflection of a string that is provably inert.
The Fix: Quote Like RFC 4180 Means It
The cure is unglamorous and absolute: neutralize formula prefixes at write time. When emitting a CSV cell whose value begins with =, +, -, @, tab, or CR, prefix the value with a single quote (') or wrap and escape per RFC 4180 with the leading apostrophe guard — Excel and LibreOffice render the result as text, not a formula. Apply this to every field in every export, not just the ones that "face users," because data migrates: today's product-name column is tomorrow's finance-team export.
Then harden the consumption side as defense in depth: LibreOffice and recent Excel builds prompt (or refuse) on DDE and external content — don't disable those prompts, and teach staff that a spreadsheet asking to "run an external program" is an incident, not an inconvenience.
Finally, verify from outside: the free 2MNY scan discovers export endpoints on your site, audits served files for planted formula cells, and proves (or disproves) the reflection vector with an inert marker — the full two-sided check, GET-only, zero payloads stored, in every report.
A CSV export is a promise that the file contains data. Formula injection breaks that promise on the analyst's desk. Quote your cells, and keep the command prompts where they belong.