CVE-2026-76216

highPriorität · verfolgen · 0OT-Risiko · erhöhtEPSS 0.004 · 30. PerzentilUS
grimoire priorityP2 · Scheduled
Exploitable?
—
no known exploitation
Likely / severe?
0.36% (30th) EPSS
CVSS 7.5 · high
Is it mine?
2 products
Prüfe deine Angriffsfläche
Who says so?
1 sources
osv

EPSS · bewertet 2026-10-27

Vikunja through 2.4.0 contains a principal-type confusion vulnerability where LinkSharing principals with id N are treated as user principals with users.id == N at three permission checks lacking type guards. Attackers with a link-share JWT can remove victims from teams, enumerate and delete victim bot users, or read team rosters by exploiting id collisions in the autoincrement space.

01

cvss

Technischer Schweregrad, 0–10. Eine Zeile je Quelle.

7.7
010

Basiswert 0–10 · höher = schwerwiegender · Vektor überfahren zum Auflösen⚠ Quellen uneinig · medium–high · 5.2–7.7

v3.17.5highCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:Hsecondary · disclosure@vulncheck.comAusnutzbarkeit 1.6 · Auswirkung 5.9
v3.17.5highCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:Hsecondary · OSV
v4.07.7highCVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · disclosure@vulncheck.com
v4.05.2mediumCVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · CVSS-BT (t0sche)
02

schwächen

Die CWE-Schwächeklassen, zu denen dies gehört.

03

betroffene Produkte (2)

Betroffene Produkte und Versionsbereiche.

04

Advisories (2)

Hersteller- und Ökosystem-Meldungen dazu.

  • GHSA-32r8-5843-4qw2Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure2026-10-09ghsa · osvUS

    ## Summary Vikunja's `web.Auth` interface (`pkg/web/web.go`, single method `GetID() int64`) is satisfied by BOTH `*user.User` and `*models.LinkSharing`. A link-share's `GetID()` returns the **raw positive** `share.ID` (`pkg/models/link_sharing.go:83-85`), which lives in the same positive autoincrement ID space as `users.id`. The safe negated form `getUserID() = share.ID * -1` (`link_sharing.go:126-128`) exists but is NOT used at three permission sinks. As a result, a link-share principal with id `N` — which should have zero authority over teams or bot users — is treated as the *user* whose `users.id == N` at three permission checks that lack the `a.(*LinkSharing)` guard their sibling methods have. This is the same principal-type-confusion class as CVE-2026-68581 (GHSA-vvcv-vpph-h844), but at three code paths that advisory/fix never touched. ## Root Cause `web.Auth` is a one-method interface (`GetID() int64`). `*LinkSharing.GetID()` returns the raw positive share id. Three permission methods compare this raw id directly and omit the link-share type guard used elsewhere in the same files: 1. **`TeamMember.CanDelete`** (`pkg/models/team_members_permissions.go:31-40`): the self-removal fast path `if u.ID == a.GetID() { return true }` (:36) executes **before** `IsAdmin`. `IsAdmin` (:48-51) is the ONLY place that rejects link shares (`if _, is := a.(*LinkSharing); is { return false }`, :50) — and it is never reached when the fast path returns true. 2. **`BotUser.isOwner`** (`pkg/models/bot_users_permissions.go:47-56`): `return u.BotOwnerID == a.GetID()` (:55), used by `CanRead`/`CanUpdate`/`CanDelete` (:36-45). Unlike `CanCreate` (:27-30) which type-asserts `a.(*user.User)`, these three paths have no principal guard. 3. **`Team.CanRead`** (`pkg/models/teams_permissions.go:68-78`): matches membership on `And("user_id = ?", a.GetID())` (:76) with no link-share guard, unlike sibling `IsAdmin` (:45-49, guard at :47). Link-share JWTs reach these routes: `SetupTokenMiddleware` validates the signature only, `GetAuthFromClaims` returns `*models.LinkSharing`, and the `/user`/`/teams` route groups add no link-share rejection. Link sharing is enabled by default (`config.go` `ServiceEnableLinkSharing.setDefault(true)`). ## Impact A link-share principal (obtainable from any public share link, or self-registered via a share on the attacker's own project) whose id `N` collides with a victim's `users.id == N` can, without being that user or any user: - **Integrity (I:H):** remove the victim from any team they belong to (`DELETE /api/v1/teams/{T}/members/{username}`) → revokes all project permissions the victim inherited through that team. - **Availability/Integrity (A:H, I:H):** enumerate the victim's bot users (`GET /api/v1/user/bots` → `bot_owner_id = a.GetID()`), then disable/rename or **permanently delete** them (`DELETE /api/v1/user/bots/{id}` → `DeleteUser`), destroying data owned solely by those bots. - **Confidentiality (C:H):** read the roster + metadata (name, description, full member list) of any team the colliding user belongs to (`GET /api/v1/teams/{T}`), plus read bot-user records via `isOwner`-gated reads. ## Attack Chain ### Sink 1 — TeamMember.CanDelete (integrity) 1. **Entry:** `POST /api/v1/shares/{hash}/auth` → link-share JWT with `id = N`. **Guard:** JWT middleware — signature only. **Bypass proof:** `GetAuthFromClaims` returns `*models.LinkSharing`; no route-group link-share rejection on the `/teams` group. 2. **Action:** `DELETE /api/v1/teams/{T}/members/{usernameOfUserN}` with the link-share bearer, targeting a team `T` (≥2 members) that user `N` belongs to. **Guard:** `CanDelete` → `GetUserByUsername(tm.Username)` returns user N, then `u.ID == a.GetID()` (`team_members_permissions.go:36`). **Bypass proof:** `a.GetID()` returns `N` (raw positive `share.ID`, `link_sharing.go:84`) == user N's id → `true`. No `a.(*LinkSharing)` check on this branch (only `IsAdmin` at :50 has it, never reached). 3. **Sink:** `Delete` (`team_members.go`) removes user N from team T (last-member check passes when team has ≥2 members). **Impact:** victim loses all project access inherited through team T. ### Sink 2 — BotUser.isOwner (bot takeover / destruction) 1. **Entry:** link-share JWT id N (as above). **Guard:** signature-only; `/user` group adds no link-share reject. 2. **Enumerate:** `GET /api/v1/user/bots` → `ReadAll` runs `Where("bot_owner_id = ?", a.GetID())` = bots owned by user N. **Bypass proof:** `a.GetID()` = N; returns victim's bot ids self-contained (removes the id-guessing barrier). 3. **Sink:** `DELETE /api/v1/user/bots/{botId}` → `CanDelete` → `isOwner` → `u.BotOwnerID == a.GetID()` (`bot_users_permissions.go:55`) → true; `Delete` calls `DeleteUser`. **Bypass proof:** no `a.(*LinkSharing)` guard here (Create-only, :28). **Impact:** disable/rename/permanently delete victim's bot automation identities. ### Sink 3 — Team.CanRead (info disclosure) 1. **Entry:** link-share JWT id N. **Guard:** signature-only; no reject on `GET /teams/:team`. 2. **Sink:** `GET /api/v1/teams/{T}` → `CanRead` runs `Where("team_id=?", t.ID).And("user_id=?", a.GetID()).Get(tm)` (`teams_permissions.go:74-77`). **Bypass proof:** `a.GetID()` = N matches user N's `team_members` row → `can = true`; no `a.(*LinkSharing)` check (contrast `IsAdmin` at :47). **Impact:** read roster + metadata of a team the link share is not part of. ## Bypass Evidence - `link_sharing.go:83-85` `GetID()` returns raw positive `share.ID` (NOT the negated `getUserID()` at :126-128). - `team_members_permissions.go:36` raw `u.ID == a.GetID()` before `IsAdmin`; the LinkSharing guard sits at :50 on a branch never reached when the fast path returns true. - `bot_users_permissions.go:55` raw `u.BotOwnerID == a.GetID()`; the `a.(*user.User)` guard at :28 is Create-only and NOT replicated on `isOwner`. - `teams_permissions.go:76` raw `a.GetID()` in `CanRead`; sibling `IsAdmin` has the guard at :47, `CanRead` omits it. - All three sinks verified present on latest release tag `v2.4.0` (`git show v2.4.0:<file>`). No fix commits touch these files between v2.4.0 and HEAD (the only post-tag commit to link_sharing.go, `c580d51`, merely shadows an embedded Update method). ## Affected Versions `<= 2.4.0` (latest release; also present on HEAD of `main`). Requires default-enabled link sharing. ## Exploitability Constraint (reflected in AC:H) The attacker cannot freely choose the colliding id — `link_shares.id` is autoincrement. Exploitation is (a) *opportunistic* (a guest holding a share with id N attacks the user whose `users.id == N`) or (b) *targeted* (self-register and walk the autoincrement toward a chosen id; low-numbered shares collide with low-numbered/early/admin accounts). This is the identical constraint the accepted CVE-2026-68581 had; it affects target selection (AC), not reachability of the boundary crossing. ## Suggested Fix Add the link-share principal guard (`if _, is := a.(*LinkSharing); is { return false }`) — which `IsAdmin`/`CanCreate` already use — to all three sinks: the `TeamMember.CanDelete` self-removal fast path (before the `u.ID == a.GetID()` check), `BotUser.isOwner`, and `Team.CanRead`. Alternatively, resolve principals through `getUserID()` (negated id space) at every permission check so link-share ids can never collide with user ids. --- Reported by **zx (Jace)** — GitHub: @manus-use

  • GO-2026-6711Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure in code.vikunja.io/api2026-10-19vendor · osvUS

    Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure in code.vikunja.io/api

05

auch bekannt als (2)

Weitere Namen und Kennungen dieser Entität.

06

quellen

Welche Quellen diesen Datensatz belegen und wann.

veröffentlicht 2026-08-19geändert 2026-09-16
07

Referenzen (2) nvd

Externe Belege — Patches, Meldungen, Analysen.

scry

Angreifer nutzen Schwachstellen wie diese gegen im Internet exponierte Dienste aus. Sehen Sie mit einem kostenlosen Passiv-Scan, was exponiert ist.

Kostenloser Scan