CVE-2026-45693: FacturaScripts Unauthenticated Path Traversal in Static File Controllers
Any unauthenticated visitor can read private invoices, database backups, and other protected files from a FacturaScripts ERP installation by slipping a ../ segment into a static-file URL.
The problem
Two static-file controllers, Core/Controller/Files.php and Core/Controller/Myfiles.php, decide whether a request is authorized by checking the raw URL string with substr() or strpos() instead of the resolved filesystem path.
Because PHP's is_file() and readfile() resolve ../ segments before touching the disk, a URL that starts with /Plugins/ or /MyFiles/Public/ passes the prefix check but delivers a completely different file. No authentication is required, and any file whose extension is on the allow-list (pdf, xlsx, docx, csv, sql, zip, xml, json, xsig, etc.) can be read.
In practice this leaks the documents the application is specifically designed to protect: customer invoices, supplier invoices, document attachments, and .sql database backups under MyFiles/Private/.
Proof of concept
A working proof-of-concept for CVE-2026-45693 in facturascripts/facturascripts, with the exact payload below.
# Path 1: via /Plugins/* (no precondition, works on every install)
GET /Plugins/../MyFiles/Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: target.example.com
# Path 2: /MyFiles/Public/ prefix bypass (skips myft token check)
GET /MyFiles/Public/../Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: target.example.com
# Encoding variants that also work (bypass is in the prefix check, not Apache normalization)
# %2e%2e, %2E%2E, .%2e, ///../The root cause (CWE-22) is a classic prefix-only path check. isFolderSafe() in Files.php compares substr($url, 0, 1+strlen($folder)) against a list of safe prefixes. A URL like /Plugins/../MyFiles/Private/secret.pdf passes because substr returns /Plugins, but the OS later resolves the ../ when the file is actually opened.
Myfiles.php has the same flaw in a second gate: it calls strpos($url, '/MyFiles/Public/') === 0 to decide whether to skip the per-file myft token check. A URL starting with /MyFiles/Public/../Private/... satisfies that strpos test, so token validation is skipped entirely, and readfile() serves the private file.
The fix replaces both prefix checks with realpath() on the constructed filesystem path, then verifies the canonical result starts with realpath(Tools::folder() . '/AllowedDir'). This makes ../ traversal impossible because realpath() collapses the segments before any comparison is made.
The fix
Upgrade to FacturaScripts 2026.3 or later. The release notes confirm the path traversal in file serving was patched in that version. If an immediate upgrade is not possible, add a WAF rule that rejects any request URI containing ../, %2e%2e, or similar dot-segment sequences targeting the affected route prefixes (/Plugins/, /Core/Assets/, /Dinamic/Assets/, /MyFiles/).
Related research
- critical · 9.9CVE-2026-45262CVE-2026-45262: FacturaScripts REST API SQL Injection via Parenthesis Bypass in Where::sqlColumn
- high · 8CVE-2026-45263CVE-2026-45263: FacturaScripts CSV Formula Injection in CSVExport
- criticalCVE-2026-47677CVE-2026-47677: FacturaScripts 2FA Endpoint Authentication Bypass and Account Takeover
- high · 8.7CVE-2026-54065CVE-2026-54065: NukeViet Path Traversal to Arbitrary File Deletion