Fix GHSA-cv95-8h7c-2ffq: Missing authorization on OIDC Meteor methods allows privilege escalation to admin (CWE-269, CWE-862).
Six Meteor methods used internally by the OIDC login flow were registered as globally DDP-callable
with no authorization, while their non-OIDC counterparts require admin. `setCreateOrgFromOidc` and
`setOrgAllFieldsFromOidc` (`server/models/org.js`), `setCreateTeamFromOidc` and
`setTeamAllFieldsFromOidc` (`server/models/team.js`) let any authenticated user create/rename/
deactivate/modify arbitrary organizations and teams — including `orgAutoAddUsersWithDomainName` —
bypassing the admin-only restriction. Most critically, `groupRoutineOnLogin`
(`packages/wekan-oidc/oidc_server.js`) sets `isAdmin` from caller-supplied group data, so with
`PROPAGATE_OIDC_DATA` enabled any authenticated user could call it over DDP with
`{groups:[{isAdmin:true,forceCreate:true}]}` and promote themselves to global admin;
`boardRoutineOnLogin` could likewise add the caller to the default board. These six methods are only
ever invoked server-side (via `Meteor.callAsync`) during the OIDC handshake, where a fix that checks
`isAdmin`/`this.userId` would break legitimate group/admin propagation (the user is not yet logged in
or admin at that point). Fixed by rejecting any direct client/DDP invocation: a server-to-server
`Meteor.callAsync` runs with `this.connection === null`, whereas a client call has a non-null
connection, so each of the six methods now throws `not-authorized` when `this.connection !== null`.
The legitimate OIDC login flow is unaffected.
Thanks to alexwaira for the coordinated disclosure, and Claude.
This commit is contained in:
1 parent
d475214224
commit
305864f0c7
3 files changed
+59
No files matched your search
@@ -336,6 +336,17 @@ var getTokenContent = function (token) {
|
||||
Meteor.methods({
|
||||
'groupRoutineOnLogin': async function(info, userId)
|
||||
{
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): This method is invoked only server-side
|
||||
// during the OIDC login flow (via Meteor.callAsync from the
|
||||
// OAuth.registerService('oidc') callback above). With PROPAGATE_OIDC_DATA
|
||||
// enabled it grants isAdmin based on the caller-supplied group data, so if
|
||||
// it were callable directly over DDP any authenticated user could promote
|
||||
// themselves to global admin. A server-side method call has
|
||||
// this.connection === null; a direct client call has a non-null
|
||||
// connection. Reject the latter.
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(info, Object);
|
||||
check(userId, String);
|
||||
var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false;
|
||||
@@ -360,6 +371,14 @@ Meteor.methods({
|
||||
Meteor.methods({
|
||||
'boardRoutineOnLogin': async function(info, oidcUserId)
|
||||
{
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): Invoked only server-side during the OIDC
|
||||
// login flow (via Meteor.callAsync from the OAuth.registerService('oidc')
|
||||
// callback above). It adds the OIDC user to the default board (optionally
|
||||
// as board admin) with no per-user authorization, so reject direct
|
||||
// client/DDP calls (this.connection non-null).
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(info, Object);
|
||||
check(oidcUserId, String);
|
||||
|
||||
|
||||
@@ -43,6 +43,18 @@ Meteor.methods({
|
||||
orgWebsite,
|
||||
orgIsActive,
|
||||
) {
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): This *FromOidc method is an internal
|
||||
// helper invoked only server-side during the OIDC login flow (via
|
||||
// Meteor.callAsync from packages/wekan-oidc/loginHandler.js). It performs
|
||||
// a privileged org write with no per-user authorization, so it must never
|
||||
// be callable directly by a client over DDP — otherwise any authenticated
|
||||
// user could create/modify organizations, bypassing the admin-only check
|
||||
// on setCreateOrg/setOrgAllFields. A server-side method call has
|
||||
// this.connection === null; a direct client call has a non-null
|
||||
// connection. Reject the latter.
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(orgDisplayName, String);
|
||||
check(orgDesc, String);
|
||||
check(orgShortName, String);
|
||||
@@ -125,6 +137,14 @@ Meteor.methods({
|
||||
orgWebsite,
|
||||
orgIsActive,
|
||||
) {
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): Internal OIDC-login-only helper (called
|
||||
// server-side from packages/wekan-oidc/loginHandler.js). Performs a
|
||||
// privileged org update of an arbitrary _id with no per-user
|
||||
// authorization, so reject direct client/DDP calls (this.connection
|
||||
// non-null) to keep the admin-only restriction of setOrgAllFields intact.
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(org, Object);
|
||||
check(orgDisplayName, String);
|
||||
check(orgDesc, String);
|
||||
|
||||
@@ -39,6 +39,18 @@ Meteor.methods({
|
||||
teamWebsite,
|
||||
teamIsActive,
|
||||
) {
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): This *FromOidc method is an internal
|
||||
// helper invoked only server-side during the OIDC login flow (via
|
||||
// Meteor.callAsync from packages/wekan-oidc/loginHandler.js). It performs
|
||||
// a privileged team write with no per-user authorization, so it must never
|
||||
// be callable directly by a client over DDP — otherwise any authenticated
|
||||
// user could create teams, bypassing the admin-only check on
|
||||
// setCreateTeam/setTeamAllFields. A server-side method call has
|
||||
// this.connection === null; a direct client call has a non-null
|
||||
// connection. Reject the latter.
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(teamDisplayName, String);
|
||||
check(teamDesc, String);
|
||||
check(teamShortName, String);
|
||||
@@ -108,6 +120,14 @@ Meteor.methods({
|
||||
teamWebsite,
|
||||
teamIsActive,
|
||||
) {
|
||||
// SECURITY (GHSA-cv95-8h7c-2ffq): Internal OIDC-login-only helper (called
|
||||
// server-side from packages/wekan-oidc/loginHandler.js). Performs a
|
||||
// privileged team update of an arbitrary _id with no per-user
|
||||
// authorization, so reject direct client/DDP calls (this.connection
|
||||
// non-null) to keep the admin-only restriction of setTeamAllFields intact.
|
||||
if (this.connection !== null) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
check(team, Object);
|
||||
check(teamDisplayName, String);
|
||||
check(teamDesc, String);
|
||||
|
||||
Reference in new issue
Block a user