NocoDB 共享基础链接可能邀请真实基础成员并在共享撤销后依然存活
我在审查 NocoDB 时发现了这个问题,出发点是一个简单的安全问题:
公共共享基础链接能否跨越临时共享访问的边界,进入真实的认证基础成员身份?
在这个案例中,答案是肯定的。
一个仅由 xc-shared-base-id 认证的共享基础会话,在 ACL 层面被当作普通的基础查看者。由于查看者权限仍然覆盖了成员管理端点,拥有共享基础 UUID 的用户可以枚举现有的基础成员,并邀请任意邮箱地址成为该基础的真实成员。
被邀请的用户随后可以通过正常的注册流程兑换邀请,获得标准的认证账户,并且在所有者禁用共享基础链接后仍然保持基础访问权限。
该问题被分配为 CVE-2026-46552。
项目: NocoDB
已验证的受影响版本: 0.301.3
该问题影响了 NocoDB,在其官方网站上,NocoDB 宣称被 35,000+ 家组织信任,下载量超过 2000 万。 该网站还列出了包括 Accenture、Western Digital、Hyundai、Walmart、PwC、Bosch 和 American Express 在内的公司。
公共共享基础链接 -> xc-shared-base-id 被当作普通基础查看者 -> 查看者 ACL 可访问成员管理端点 -> 攻击者列出基础用户并邀请任意邮箱 -> 被邀请用户兑换正常注册令牌 -> 持久的认证基础访问在共享链接撤销后依然存活
NocoDB 是一个基于数据库的协作平台,提供浏览器端的基础访问、共享、元数据管理和用户成员工作流。
这意味着它的共享模型是一个真正的安全边界。
这里的重要问题并不是共享基础链接能否读取共享内容。
真正的问题是:
公共共享主体能否执行本应仅属于认证基础成员的操作?
在这个案例中,答案是肯定的。
公共共享功能很容易被低估。
这是一个错误。
一旦应用程序支持:
主要的风险不仅仅是数据暴露。
更严重的风险是 边界塌陷:
这才是真正的问题所在。
这不是登录验证的漏洞。 不是令牌伪造问题。 也不是密码重置缺陷。
这是一个经典的 授权边界失败:
我没有通过随机探测端点并希望有有趣响应的方法来接近这个问题。
更有效的方法是首先确定最高价值的信任边界。
对于 NocoDB,这个边界是:
这两种状态不应该是可互换的。
共享基础链接本应代表受限的、可撤销的、基于链接的访问。 它不应该能在基础内制造新的长期主体。
这正是这里失败的边界。
漏洞源于共享基础访问被集成到普通 ACL 路径中的方式。
在共享基础前端流程中,xc-shared-base-id 被注入,而普通认证头被移除。
然后,在后端,BaseViewStrategy 接受 xc-shared-base-id 并将共享链接直接转换为从共享基础配置派生的普通 roles / base_roles。
这是第一个问题。
第二个问题是 查看者级别的权限仍然包括成员管理操作。
在 ACL 层中,ProjectRoles.VIEWER 可以访问:
baseUserListuserInvite这些权限守卫着普通的元路由:
GET /api/v2/meta/bases/:baseId/usersPOST /api/v2/meta/bases/:baseId/users因此,公共共享会话实际上被允许访问本应属于真实基础参与者的成员端点。
最后一步是在邀请流程本身中。
BaseUsersService.userInvite() 检查角色权限,然后创建:
invite_token 的真实用户行而对于共享基础会话:
invited_by 变为 null因为请求背后没有真正的认证邀请者身份。
这就是整个漏洞链。
因为拥有共享基础链接就足够了。
攻击者不需要:
xc-auth利用链很简单:
这将可撤销的链接共享转化为持久成员身份。
重要的区别在于撤销后的持久性。
这不仅仅是:
“查看者可以调用查看者端点”
易受攻击的主体不是普通的认证查看者。
它是一个 公共共享会话。
这很重要,因为应用程序将一个临时的、链接范围的主体视为足够可信,能够:
真正的问题不是:
“共享用户能否读取共享数据?”
真正的问题是:
“公共共享访问能否转化为永久的认证访问,并且在共享撤销后仍然存在?”
答案是肯定的。
这就是为什么这是一个真正的授权漏洞,而不仅仅是令人惊讶的应用程序行为。
我在本地针对以下环境进行了验证:
0.301.3dac49b0122c5ee655fb8f46a1b6e42dfeec1f3adhttp://127.0.0.1:8080复现过程很直接。
首先,我以普通所有者账户登录,创建一个新基础,创建一个表,并以 viewer 身份启用共享基础访问:
PATCH /api/v2/meta/bases/<baseId>/shared
Content-Type: application/json
{
"roles": "viewer"
}
这返回了共享基础 UUID。
然后,在不发送任何 xc-auth 的情况下,我只使用了:
xc-shared-base-id: <sharedBaseUuid>
仅使用该头部,我调用了:
GET /api/v2/meta/bases/<baseId>/users
这返回了 200 OK 并暴露了真实基础成员,包括邮箱地址。
仅使用 xc-shared-base-id,我调用了:
POST /api/v2/meta/bases/<baseId>/users
Content-Type: application/json
{
"email": "[email protected]",
"roles": "viewer"
}
这也返回了 200 OK。
为了在不发送邮件的情况下进行本地实验室验证,我直接在 SQLite 元数据库中确认:
nc_users_v2 包含被邀请用户,且 invite_token 非空nc_base_users_v2 包含目标基础的一个真实成员行invited_by 为 NULL然后我通过正常注册流程兑换了邀请:
POST /api/v2/auth/user/signup
Content-Type: application/json
{
"email": "[email protected]",
"password": "Password123.",
"token": "<invite_token>"
}
使用返回的 xc-auth,我调用了:
GET /api/v2/meta/bases/<baseId>/tables
这返回了 200 OK。
最后,作为所有者,我禁用了共享基础链接:
DELETE /api/v2/meta/bases/<baseId>/shared
之后:
xc-shared-base-id 的共享链接访问失败,返回 401xc-auth 的被邀请账户仍然成功返回 200200200200200200401200这证实了核心安全主张:
一次成功的共享基础邀请本身就足以证明授权失败。
但完整的验证链很重要,原因有二。
它表明这不仅仅是端点暴露。
公共共享会话不仅到达了受限 API。 它完成了完整的权限转换链:
它证明这不是自撤销的。
更严重的影响出现在共享链接禁用之后:
这就是将临时链接访问转变为持久访问持续性的原因。
该漏洞允许任何拥有共享基础链接的人:
主要影响是 机密性,因为攻击者可以通过一个普通的认证账户维持对共享基础数据的长期读取访问。
还有 完整性影响,因为公共共享主体可以通过向基础添加新成员来修改访问控制状态。
这比普通数据泄露更严重。 这是在匿名共享和认证成员身份之间的特权边界突破。
该问题合理归类为跨作用域授权缺陷,具有机密性影响。
CVSS:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
该向量符合这里的核心行为:
修复方向很直接。
共享基础会话不应继承成员管理能力。
至少:
xc-shared-base-id 可达的权限中移除 baseUserList 和 userInviteGET 和 POST /api/v2/meta/bases/:baseId/users该问题在本地针对 NocoDB 0.301.3(提交 dac49b0122c5ee655fb8f46a1b6e42dfeec1f3ad)进行了验证。
报告演示了:
xc-shared-base-id 到普通基础角色的 ACL 映射该问题被分配为:
CVE-2026-46552
关键教训很简单:
共享链接访问不同于可信成员身份。
很多系统在将这两个概念合并到同一个角色模型中时就会陷入困境。
共享链接可能在操作上看起来类似于查看者账户,但信任假设不同:
如果这个低保证主体能够执行管理操作或创建新的持久身份,则共享边界已经被破坏。
这才是真正的要点。
该漏洞并非完全绕过认证。
它关于将两个本应保持独立的信任级别合并在一起。
在 NocoDB 中,共享基础链接本应提供对共享内容的临时、可撤销访问。 然而,它却被用来枚举成员、邀请真实用户进入基础,并将公共共享访问转化为在共享撤销后仍然存活的持久认证成员身份。
这就是它成为 CVE-2026-46552 的原因。