Skip to content

🔒 Org self-join via shadowed Organization policy subject #3851

@PierreBrisorgueil

Description

@PierreBrisorgueil

Part of #3848 · severity: high · related: #3833

Where

modules/organizations/policies/organizations.policy.js:19-25 and modules/organizations/controllers/organizations.membership.controller.js:76.

Problem

organizations.policy.js registers a document subject Organization whose guard returns true for any path under /api/organizations — which also matches POST /api/organizations/:id/members. In policy.js isAllowed(), document-subject resolution runs first and wins (because req.organization is set app-wide via app.param('organizationId', …)), so the intended Membership path-subject (owner/admin gate) is never consulted. create on Organization is unconditionally allowed → horizontal privilege escalation.

Exploit

Any authenticated user (not a member of org X) posts {"userId":"<self>","role":"member"} to /api/organizations/<X>/members; the document-subject grants create, the elevated-role guard is skipped for member, and a pending owner_add membership is created — self-join into any organization. Org ObjectIds are discoverable via the search/listByUser endpoints.

Fix

Tighten the Organization document-subject guard to exclude membership sub-routes (return false when the path contains /members or /requests) so the dedicated Membership owner/admin gate applies. Defense in depth: in addMember, require the actor to be an org owner/admin or a global admin before any add.

Created via /dev:issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions