It's a Plan: Rechteausweitung zum Projekteigentümer über Einladungstoken
Ein Mitglied mit der Standardrolle liest das Klartext-Token einer offenen Owner-Einladung, registriert die eingeladene Adresse und wird Eigentümer des Projekts.
Advisory-ID: TP-2026-123
Produkt: It's a Plan (selbst gehostete Projektverwaltung und Issue-Tracking mit KI-Agenten)
Schwachstellentyp: Unzureichende Rechteverwaltung (CWE-269)
CVE: nicht beantragt
CVSS 3.1: 7.1 (Hoch) · CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:H
Betroffene Versionen: >= 1.0.0, < 1.7.0
Hersteller-Advisory: GHSA-mr34-5m49-6fvq
Behoben in: 1.7.0
Gemeldet: 11. Oktober 2026
Zusammenfassung
It's a Plan ist eine selbst gehostete Projektverwaltung mit Teams, Projekten und rollenbasierten Rechten. GET /projects/:projectKey/invites liefert die offenen Einladungen eines Projekts mitsamt dem einlösbaren Klartext-token, und die dafür nötige Berechtigung members_invite.read steckt in der Standardrolle jedes neuen Teams. Die Annahme einer Einladung vergleicht nur die eingeladene Adresse mit der Adresse der Sitzung, und die ausgelieferte Konfiguration erlaubt offene Registrierung ohne E-Mail-Bestätigung. Ein Mitglied registriert deshalb die Adresse einer offenen Owner-Einladung selbst und löst sie ein. Ein Mitglied mit der Standardrolle wird so Eigentümer seines Projekts, entfernt den bisherigen Eigentümer und kann das Projekt löschen.
Ursache
Die Route listProjectInvites liegt hinter dem Gatter memberAdmin: ['members_invite', 'read'] (apps/api/src/modules/invites/index.ts:168-177), und das Antwortschema enthält token: t.String() (apps/api/src/modules/invites/model.ts:31). defaultMemberPermissions() setzt p.members_invite.read = true (packages/db/src/permissions.ts:96), und insertOwnedTeam schreibt diese Matrix als Standardrolle Member mit isDefault: true in jedes neue Team (apps/api/src/modules/teams/service.ts:793-798). loadActionableInvite weist nur eine Einladung ab, deren Adresse nicht zur Sitzung passt, und prüft weder emailVerified noch etwas anderes (apps/api/src/modules/invites/index.ts:41-50). defaultAuthSettings liefert registration: 'open' und requireEmailVerification: false aus (packages/auth/src/instance.ts:67-76). Die Tabelle team_invite hat keine Ablaufspalte, sodass eine offene Owner-Einladung unbegrenzt lesbar und einlösbar bleibt (packages/db/src/schema/app.ts:366-414).
Proof of Concept
GET /projects/ALPHA/invites (Mitglied, Standardrolle)
-> 200 [{"token":"cdd664bd-…","email":"[email protected]","role":"owner","status":"pending", …}]
POST /api/auth/sign-up/email {"email":"[email protected]", …}
-> 200 {"user":{…,"emailVerified":false}}
POST /invites/cdd664bd-…/accept
-> 200 {"projectKey":"ALPHA","role":"owner", …}
Das Token ist die einzige Hürde der Einladung, und die Annahme verlangt keinen Nachweis, dass die Adresse dem Einlösenden gehört. Voraussetzung ist eine offene Owner-Einladung an eine Adresse, die noch kein Konto hat; das Mitglied kann selbst keine Einladung anlegen.
Auswirkung
- Rechteausweitung von der Standardrolle zum Eigentümer des Projekts.
- Entfernen des bisherigen Eigentümers und unwiderrufliches Löschen des Projekts mit Dokumenten, Dashboards, Webhooks und Mitgliederliste.
- Offenlegung des Webhook-Signaturgeheimnisses, das die Standardrolle nicht lesen darf.
- Weder der Eigentümer noch die eingeladene Adresse erhalten eine Benachrichtigung über die Annahme.
Referenzen
Steckt so etwas in Ihrer Software?
Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.
