CVE-2026-3532
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Drupal is an open-source content management system known for its flexibility, robust features, and strong community support. Organizations of all sizes use it to build and manage dynamic websites and web applications.
The module does not sufficiently validate account data received from the identity provider during first-time sign-in. In some cases, an invalid or case-variant duplicate email address can bypass the module's earlier checks and cause account provisioning to fail unexpectedly or behave incorrectly.
In general, Broken Access Control occurs when an application fails to properly enforce restrictions on what authenticated users are allowed to do, enabling attackers to access unauthorized functionality, data, or resources. It often stems from inadequate validation of user permissions, allowing someone to bypass intended security boundaries and perform actions beyond their assigned role.
Any of the following ramifications are possible:
- Allowing arbitrary code execution
- Complete system compromise
- Data theft or exposure
- Data manipulation or destruction
- Privilege escalation, and
- Denial of service.
This issue affects OpenID Connect versions 7.1.0 through 7.1.3.
Details
Module Info
- Product: Drupal 7
- Affected package: OpenID Connect
- Affected versions: >=7.1.0 <= 7.1.3
- Repository: https://git.drupalcode.org/project/openid_connect
- Project Page: https://www.drupal.org/project/openid_connect
- Package manager: Composer
- Fixed in: OpenID Connect NES 7.1.4
Vulnerability Info
This medium-severity vulnerability affects OpenID Connect versions 7.1.0 through 7.1.3 for Drupal 7.
During first-time sign-in with OpenID Connect, the module trusts identity-provider-supplied account data too early in the account provisioning flow. Although the authorization logic already checks for some obvious email conflicts, it ultimately passes provider-supplied username and email data into Drupal user creation without fully validating that the new account can actually be created. In particular, invalid email addresses and case-variant duplicates of existing email addresses can reach the user creation step and cause inconsistent or failed account provisioning.
As a result, a site using this module can end up with failed login attempts, confusing account-creation behavior, and edge cases where the module’s earlier conflict detection does not match Drupal’s actual account validation rules. The patch closes that gap by validating the generated account data before saving the user, rejecting invalid email addresses, rejecting case-insensitive duplicate email addresses, and making the authorization flow fail cleanly when account creation is not possible.
Steps To Reproduce
1. Create a Drupal 7 installation and install a vulnerable version of the OpenID Connect module, such as 7.1.3.
2. Enable the OpenID Connect module.
3. Prepare the mock OpenID Connect provider; write this file into /tmp/mock_oidc.php:
<?php
session_name('MOCKOIDC');
session_start();
function mock_b64url($value) {
return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
}
function mock_hidden($name, $value) {
return '<input type="hidden" name="' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '" value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '">';
}
function mock_redirect_with_code($redirect_uri, $state) {
$separator = strpos($redirect_uri, '?') === FALSE ? '?' : '&';
header('Location: ' . $redirect_uri . $separator . 'code=mock-code&state=' . rawurlencode($state));
exit;
}
function mock_render_login_form($redirect_uri, $state, $prompt) {
header('Content-Type: text/html; charset=UTF-8');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mock OIDC Login</title>
<style>
body { font-family: sans-serif; margin: 2rem; max-width: 48rem; }
code { background: #f4f4f4; padding: 0.1rem 0.3rem; }
.box { border: 1px solid #ccc; padding: 1rem 1.25rem; border-radius: 0.5rem; }
.meta { color: #444; font-size: 0.95rem; }
button { padding: 0.6rem 1rem; }
</style>
</head>
<body>
<div class="box">
<h1>Mock OpenID Connect Provider</h1>
<p>This page simulates a fresh IdP login challenge.</p>
<p class="meta">Current session: <strong><?php print empty($_SESSION['mock_authenticated']) ? 'not authenticated' : 'authenticated'; ?></strong></p>
<p class="meta">Received <code>prompt</code>: <strong><?php print htmlspecialchars($prompt, ENT_QUOTES, 'UTF-8'); ?></strong></p>
<form method="post" action="/authorize">
<?php print mock_hidden('redirect_uri', $redirect_uri); ?>
<?php print mock_hidden('state', $state); ?>
<?php print mock_hidden('prompt', $prompt); ?>
<button type="submit" name="mock_login" value="1">Log in as Mock User</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($path === '/authorize') {
$is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
$request = $is_post ? $_POST : $_GET;
$redirect_uri = isset($request['redirect_uri']) ? $request['redirect_uri'] : '';
$state = isset($request['state']) ? $request['state'] : '';
$prompt = isset($request['prompt']) ? trim($request['prompt']) : '';
if ($redirect_uri === '') {
http_response_code(400);
header('Content-Type: text/plain');
echo "missing redirect_uri\n";
exit;
}
if ($is_post && !empty($_POST['mock_login'])) {
$_SESSION['mock_authenticated'] = TRUE;
$_SESSION['mock_user'] = array(
'sub' => 'mock-user-1',
'email' => 'existinguser@example.com',
'name' => 'Mock User',
);
mock_redirect_with_code($redirect_uri, $state);
}
$force_login = strpos(' ' . $prompt . ' ', ' login ') !== FALSE;
$has_session = !empty($_SESSION['mock_authenticated']);
if ($has_session && !$force_login) {
mock_redirect_with_code($redirect_uri, $state);
}
mock_render_login_form($redirect_uri, $state, $prompt);
}
if ($path === '/token') {
header('Content-Type: application/json');
$claims = array(
'sub' => 'mock-user-1',
'email' => 'existinguser@example.com',
);
$id_token = mock_b64url(json_encode(array('alg' => 'none', 'typ' => 'JWT'))) . '.'
. mock_b64url(json_encode($claims)) . '.x';
echo json_encode(array(
'access_token' => 'mock-access-token',
'id_token' => $id_token,
'expires_in' => 3600,
'token_type' => 'Bearer',
));
exit;
}
if ($path === '/UserInfo') {
header('Content-Type: application/json');
$user = !empty($_SESSION['mock_user']) ? $_SESSION['mock_user'] : array(
'sub' => 'mock-user-1',
'email' => 'existinguser@example.com',
'name' => 'Mock User',
);
echo json_encode($user);
exit;
}
if ($path === '/logout') {
$_SESSION = array();
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
}
session_destroy();
header('Content-Type: text/plain');
echo "logged out\n";
exit;
}
if ($path === '/') {
header('Content-Type: text/html; charset=UTF-8');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mock OIDC Provider</title>
</head>
<body>
<h1>Mock OIDC Provider</h1>
<p>Session state: <strong><?php print empty($_SESSION['mock_authenticated']) ? 'not authenticated' : 'authenticated'; ?></strong></p>
<ul>
<li><a href="/logout">Log out of mock IdP</a></li>
<li>Authorize endpoint: <code>/authorize</code></li>
<li>Token endpoint: <code>/token</code></li>
<li>UserInfo endpoint: <code>/UserInfo</code></li>
</ul>
</body>
</html>
<?php
exit;
}
http_response_code(404);
header('Content-Type: text/plain');
echo "not found\n";
4. Start the mock IdP.
php -S 0.0.0.0:8008 /tmp/mock_oidc.php
5. At admin/config/services/openid-connect, configure Drupal’s Generic OpenID Connect client to use the mock.
If using DDEV, select endpoints reachable from both DDEV and your browser.
Reproduction Case 1: Case-Variant Duplicate Email
- Create a normal Drupal user account manually with an email address such as ExistingUser@Example.com.
- Ensure the mock provider is configured to return the same email address with different casing, such as existinguser@example.com, in both the ID token and the UserInfo response.
- Log out of Drupal.
- Visit the Drupal login page and start login with the configured OpenID Connect client.
- Complete authentication through the mock provider and return to Drupal.
Expected result before the patch:
- On affected installations, the module may fail to recognize a case-variant email address as conflicting with an existing local account during its earlier conflict check.
- The flow falls through to account creation.
- Drupal account creation fails because the email conflicts with an existing account under Drupal’s actual validation rules.
- The first-time login attempt fails unexpectedly or behaves inconsistently instead of cleanly rejecting the identity.
Expected result after the patch:
- The duplicate email is rejected before local account creation is attempted.
- The login flow fails cleanly.
- The user receives an error indicating that the email address is already in use or already taken.
- No new Drupal account is created.
Reproduction Case 2: Invalid Email From the Identity Provider
1. Edit /tmp/mock_oidc.php.
2. In the /authorize handler, change the $_SESSION['mock_user'] email value to not-an-email.
3. In the /token handler, change the $claims['email'] value to not-an-email.
Example change in the /authorize handler:
$_SESSION['mock_user'] = array(
'sub' => 'mock-user-1',
'email' => 'not-an-email',
'name' => 'Mock User',
);
4. Log out of Drupal.
5. Start a first-time OpenID Connect login using the configured Generic client.
6. Complete authentication through the mock provider and return to Drupal.
Expected result before the patch:
- Provider-supplied account data can reach the local provisioning path without being fully validated for actual user creation.
- The login flow may fail unexpectedly or behave inconsistently during account creation.
Expected result after the patch:
- The invalid email is rejected before local account creation.
- The login flow fails cleanly with an error message.
- No new Drupal account is created.
Verify the Result
- Check whether a new user account was created:
drush sqlq "SELECT uid, name, mail, status FROM users ORDER BY uid DESC LIMIT 10;"
Check recent watchdog entries:
drush ws --count=20
- Before patch:
- provider-supplied account data can reach the account-creation path without matching Drupal’s final validation behavior
- first-time login can fail unexpectedly when the email is invalid or conflicts by case only
- After patch:
- invalid and conflicting emails are rejected before user_save()
- the failure is handled cleanly
- no unintended new account is created
Addressing the Issue
Users of the affected module should apply one of the following mitigations:
- Disable Automatically connect existing users unless the identity provider is fully trusted and strongly verifies ownership of user email addresses.
- Restrict OpenID Connect logins to a pre-approved set of users or email domains by implementing hook_openid_connect_pre_authorize() or equivalent custom access checks.
- Limit use of this module to trusted identity providers under your control; do not allow first-time account provisioning from third-party or weakly governed providers.
- Avoid workflows that rely on automatic local account creation for unknown users until a patched version is deployed.
- Pre-provision local accounts where practical and allow OpenID Connect only for controlled account linking or access to known identities.
- Sign up for post-EOL security support; HeroDevs customers get immediate access to a patched version of this module.
Credits
Eric Smith (ericgsmith)