Lion Cipher Lab
Exploit and fix CSV formula injection (spreadsheet formula injection) in your own app
- AppSec / DevSecOps
- Intermediate
- ~50 min
- Free · no account required
Why it matters
CSV formula injection is one of the most under-taught web bugs precisely because it breaks the mental model everyone brings to injection. Almost every application collects data from users — feedback, contact-form messages, support tickets, a display name — and almost every application lets a staff member EXPORT that data to a spreadsheet later. The developer writes each user's text into a cell and thinks the job is done, because on the server that text is inert: it is just stored and copied, and no server-side code runs. But a spreadsheet is an interpreter. When Excel, LibreOffice Calc, or…
What you'll learn
- defining, from zero, what a CSV file is, what a 'formula' is to a spreadsheet, and the exact formula-trigger characters that start one — = + - @ and a leading tab (0x09) or carriage return (0x0D)
- explaining the key twist: the vulnerable SINK is the code that EXPORTS the CSV, but the formula EXECUTES in the consumer's spreadsheet app (Excel / LibreOffice / Google Sheets), not on your server — so the victim is whoever opens the file, usually an admin
- building a tiny vulnerable app on your own machine (pure Python stdlib: http.server + csv, no installs) that accepts a user field and exports it straight to CSV with no sanitization
- exploiting it with the benign proof value =1+1, exporting, and INSPECTING THE RAW CSV to see the cell literally begins with = — then (optionally) opening it in a spreadsheet and watching it evaluate to 2
- reading the real payloads for what they do: =HYPERLINK(...) and =WEBSERVICE(...) for data exfiltration, and the legacy =cmd|'/c calc'!A0 DDE form for historical code execution — understood, not detonated
- fixing it at the export boundary: prefix any cell that starts with a trigger character so the spreadsheet treats it as text, and CSV-quote correctly — then re-exporting the identical input and confirming the raw cell now reads '=1+1 and does not evaluate
- stating the honest negatives out loud: it is a consumer-side bug, modern Excel disables DDE by default and gates WEBSERVICE, naive quote-prefixing corrupts legitimate values like phone numbers '+1...', and CSV's lack of a type system is the root cause a typed .xlsx export avoids
- placing it in the injection family: the durable rule is output-context-aware encoding — the same principle as XSS output encoding and SQL parameterization, applied to the spreadsheet output context
Steps in this lab
- What a CSV, a spreadsheet formula, and the trigger characters actually are
- Set up: confirm Python, make a folder (no installs at all)
- Build the VULNERABLE app: accept a field, store it, export it raw
- Exploit: submit =1+1, export, and inspect the RAW cell (before measurement)
- The real payloads: HYPERLINK / WEBSERVICE exfil and the historical DDE RCE
- Fix it: neutralize formula triggers on export (after measurement)
- Honest negatives: it's a consumer-side bug, and the fix has sharp edges
- Do it better: output-context-aware encoding and the injection family