← Research / CVE-2026-87947
Drupal · SAML SSO module

Authentication bypass to admin in Drupal's SAML SSO module

Drupal 14/25 · Moderately critical Patched · 3.2.0 CWE-347 · Signature confusion

Drupal's SAML SSO - Service Provider module (miniorange_saml) logs a user in from a signed assertion handed over by a trusted identity provider. The catch: it reads the signature algorithm from the attacker's assertion instead of pinning it to the IdP's key. Flip that algorithm from RSA to HMAC and the module verifies the forgery using the IdP's public certificate as the signing secret, material that is public by design. An unauthenticated attacker forges an assertion for any account, including an administrator, and is logged straight in. No password, no access to the IdP.

Identifier
CVE-2026-87947
Weakness
CWE-347 · Signature verification
Drupal risk
14 / 25 (Mod. critical)
Module
miniorange_saml
Vulnerable
< 3.2.0
Fixed in
3.2.0
Research
Timo De Clercq
// Demo

Anonymous → administrator in one run

The proof of concept holds no account and never touches the identity provider. It builds an assertion for the admin user, signs it with HMAC keyed on the IdP's public certificate, and posts it to the SAML login endpoint. What comes back is a Drupal administrator session.

attacker@lab:~/saml-poc

Replay of saml_takeover_poc.py. Reproduced end-to-end in a lab against miniorange_saml 3.1.4 on Drupal 11.

// The chain

Four steps, nothing secret required

Every input the attacker needs is public: the IdP certificate, the entity IDs, the login route, and the username to impersonate. The only privileged actor is the Service Provider itself, trusting a signature it should have rejected.

anon

Take the cert

The IdP's x509 certificate is published in the SP config and metadata. Grab it.

anon

Forge the claim

Build a SAML assertion naming any user: here, admin.

anon

Switch to HMAC

Sign with HMAC-SHA1, keyed on the public cert the SP already trusts.

admin

Accepted as admin

The SP verifies the HMAC with that same cert and logs you in.

// Background

The signature is the whole trust boundary

In SAML single sign-on, a site (the Service Provider, or SP) outsources login to an identity provider (IdP). The IdP authenticates the user and hands back a signed XML assertion: "this is alice, she logged in at 09:14". The SP does not re-check the password. It only checks one thing before it trusts that claim and creates a session: is the signature valid for the IdP we configured? That signature is the entire security boundary.

How that check is done depends on the signing scheme, and the two common schemes are not interchangeable:

  • RSA (asymmetric). The IdP signs with a private key; the SP verifies with the matching public key. The public key is meant to be published, and that is safe, because it cannot produce signatures.
  • HMAC (symmetric). One shared secret both signs and verifies. Anyone who holds that secret can forge a valid signature.
The confusion, in one line If a verifier lets the message choose "HMAC" and then uses the public RSA key as the HMAC secret, then the key everyone is allowed to know becomes the key that signs valid messages. This is the SAML cousin of the well-known JWT RS256 → HS256 algorithm-confusion bug.
// Root cause · CVE-2026-87947

The verifier trusts the attacker's algorithm

When the SP validates an assertion, it needs a key object for the signature check. The module builds that key from the algorithm named inside the incoming assertion, then loads the configured IdP certificate into it. There is no step that binds the algorithm to the key's actual type:

// src/Utilities.php -- castKey() takes the algorithm from the assertion
$algorithm = $signatureMethod;          # read from <ds:SignatureMethod>, attacker-controlled
$key = new XMLSecurityKey($algorithm);  # RSA? HMAC? whatever was sent
$key->loadKey($idpCertificate);         # the PUBLIC cert, used as key material

Send an RSA-signed assertion and loadKey() treats the certificate as a public key, which is correct. Send an HMAC assertion and the exact same certificate bytes are treated as the shared secret. Because that certificate is public, the attacker computes the HMAC themselves, and the signature verifies. A forged assertion for any NameID, including an administrator, now passes the one check that stood between an anonymous request and a session.

What the session is worth

The forged login lands as a full Drupal administrator. From there the site is effectively owned: create and elevate accounts, change any content or configuration, read the data the site holds, and in a typical deployment reach code execution through administrative features. The authentication bypass is the whole ballgame; everything after it is ordinary admin.

// Severity

Scoring & scope

Drupal contrib advisories use the Drupal Security Team's 25-point risk model rather than a CVSS vector. SA-CONTRIB-2026-145 is rated Moderately critical, 14/25 (AC:Complex/A:None/CI:Some/II:Some/E:Theoretical/TD:Default):

Access complexity
Complex
Authentication
None
Confidentiality
Some
Integrity
Some
Exploit
Theoretical
Target dist.
Default

The metric that matters most to a defender is Authentication: None. The attack runs with zero credentials on the Drupal site and no access to the identity provider, and every input it needs (the IdP certificate, entity IDs, the ACS route, the target username) is public metadata or a fixed route. The bug reaches any site running the module with SAML login enabled.

BranchAffectedFixedStatus
3.x< 3.2.03.2.0patch
// Remediation

Fixing & detecting it

  • Update to 3.2.0 (composer require drupal/miniorange_saml:^3.2.0). The fix adds an explicit algorithm allow-list: the SP now accepts only RSA signature methods and rejects anything else before it ever loads the key, so an HMAC assertion is thrown out instead of verified.
  • Pin the algorithm, always. The general lesson for any SAML or JWT verifier: bind the accepted signature algorithm to the configured key type. Never let the incoming message decide how it gets checked.
  • Detection: look in request logs for SAMLResponse bodies whose <ds:SignatureMethod> is an HMAC algorithm (...#hmac-sha1 and friends) when your IdP signs with RSA; and for logins attributed to privileged accounts that were not preceded by a redirect out to the IdP.
// Disclosure

Timeline

  • 2026-06-26Reported to the Drupal Security Team through the private process, with a reproducing proof of concept.
  • 2026-07Maintainers develop the fix (an RSA-only algorithm allow-list in castKey()); I review and verify it in the lab.
  • 2026-09-14Advisory SA-CONTRIB-2026-145 published, 3.2.0 released, CVE-2026-87947 assigned.

Coordinated disclosure through the Drupal Security Team, the authorized channel for contrib modules. Details are released only after a fixed build was available.

Published for defensive and educational purposes against patched software. Any security testing should target only systems you own or are authorized to assess.