GitHub Advisoryadvisory · Packagist

GHSA-25cw-98hg-g3cg Admidio Ignores SAML Signature Validation Result, Processes Forged AuthnRequests and LogoutRequests

HIGH Published 2026-04-29 Modified 2026-05-08 Official page ↗
Aliases
CVE-2026-41669
Ecosystem
Packagist
Packages
admidio/admidio
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N
Weaknesses
CWE-347
Reviewed
2026-04-29

Affected packages

• Packagist admidio/admidio: < 5.0.9 → fixed in 5.0.9

Description

Summary The Admidio SAML Identity Provider implementation discards the return value of its `validateSignature()` method at both call sites (`handleSSORequest()` line 418 and `handleSLORequest()` line 613). The method returns error strings on failure rather than throwing exceptions, but the developer believed it would throw (per comments on lines 416 and 611). This means the `smc_require_auth_signed` configuration option is completely ineffective — unsigned or invalidly-signed SAML AuthnRequests and LogoutRequests are processed identically to properly signed ones. Details The `validateSignature()` method at `src/SSO/Service/SAMLService.php:355` has three possible return paths: ```php // Line 355-392 public function validateSignature(SAMLClient $client, SamlMessage $message, bool $required = false): bool|string { global $gL10n; $certPem = $client->getValue('smc_x509_certificate'); if (!$certPem) { if ($required) { return $gL10n->get('SYS_SSO_SAML_SIGNATURE_KEY_MISSING'); // Returns STRING, not throw } else { return false; } } // ... $signatureReader = $message->getSignature(); if (is_null($signatureReader)) { if ($required) { return $gL10n->get('SYS_SSO_SAML_SIGNATURE_MISSING'); // Returns STRING, not throw } else { return false; } } try { $ok = $signatureReader->validate($key); if ($ok) { return true; } else { return $gL10n->get('SYS_SSO_SAML_SIGNATURE_FAILED'); // Returns STRING, not throw } } catch (Exception $ex) { return $gL10n->get('SYS_SSO_SAML_SIGNATURE_FAILED'); // Returns STRING, not throw } } ``` Both call sites discard the return value entirely: ```php // Line 416-419 in handleSSORequest() // Validate signatures. Will throw an exception getValue('smc_require_auth_signed') || $client->getValue('smc_validate_signatures')) { $this->validateSignature($client, $request, $client->getValue('smc_require_auth_signed')); // Return value discarded — execution continues regardless of validation result } // Line 611-614 in handleSLORequest() // Validate signatures. Will throw an exception getValue('smc_require_auth_signed') || $client->getValue('smc_validate_signatures')) { $this->validateSignature($client, $request, $client->getValue('smc_require_auth_signed')); // Return value discarded — execution continues regardless of validation result } ``` SSO exploitation path (for already-logged-in users): 1. `modules/sso/index.php:92` routes to `handleSSORequest()` 2. Line 403: `receiveMessage()` parses SAML binding directly from HTTP GET/POST — no authentication required 3. Line 408-409: Entity ID extracted from the forged request's Issuer element, client config loaded 4. Line 417-419: Signature validation called but return value discarded — flow continues 5. Line 421: `$gValidLogin` is true for logged-in users, so login form is skipped 6. Lines 438-580: SAML Response built with user's real attributes (login, name, email, roles) and sent to the `AssertionConsumerServiceURL` from the forged request SLO exploitation path: 1. `modules/sso/index.php:94` routes to `handleSLORequest()` 2. Line 613: Signature validation discarded 3. Lines 621-629: User's session is deleted from the database and `$gCurrentSession->logout()` is called PoC ```bash Prerequisites: • Admidio instance with SAML SSO enabled (sso_saml_enabled=1) • At least one registered SAML SP client with smc_require_auth_signed=true • A user with an active session (e.g., admin browsing the Admidio panel) 1. Generate an unsigned AuthnRequest impersonating a registered SP: AUTHN_REQUEST=$(python3 -c " import base64, zlib req = 'https://legitimate-sp.example.com/entity-id' print(base64.b64encode(zlib.compress(req.encode())[2:-4]).decode()) ") 2. Send the unsigned request via HTTP-Redirect binding (GET): If a logged-in user's browser follows this link (e.g., via CSRF/social engineering), Admidio generates a signed SAML assertion with the user's PII and sends it to the attacker-controlled ACS URL. curl -v "https://admidio.example.org/adm_program/modules/sso/index.php/saml/sso?SAMLRequest=${AUTHN_REQUEST}" \ -b 'PHPSESSID=VICTIM_SESSION_COOKIE' Expected: Despite smc_require_auth_signed=true, the unsigned request is processed. The response contains a SAML assertion with the victim's attributes. 3. For SLO — forge a LogoutRequest to terminate a victim's session: LOGOUT_REQUEST=$(python3 -c " import base64, zlib req = 'https://legitimate-sp.example.com/[email protected]' print(base64.b64encode(zlib.compress(req.encode())[2:-4]).decode()) ") curl -v "https://admidio.example.org/adm_program/modules/sso/index.php/saml/slo?SAMLRequest=${LOGOUT_REQUEST}" \ -b 'PHPSESSID=VICTIM_SESSION_COOKIE' Expected: Victim's session is terminated, logout cascaded to all registered SPs. ``` Impact • Signature enforcement bypass: The `smc_require_auth_signed` setting is entirely ineffective. Administrators who enable this setting believing it protects against forged requests have a false sense of security. • User attribute disclosure (SSO): When combined with the ability to specify an arbitrary `AssertionConsumerServiceURL`, an attacker can redirect a logged-in user's SAML assertion (containing login name, email, real name, role memberships) to an attacker-controlled endpoint. • Session termination (SLO): An attacker can forge LogoutRequests to terminate any user's Admidio session and trigger cascading single logout across all registered Service Providers, causing denial of service for targeted users. • Amplifies ACS URL injection: The signature requirement was the primary defense against unvalidated ACS URLs in AuthnRequests. Without signature enforcement, the ACS redirect becomes trivially exploitable via GET redirect binding (which bypasses SameSite=Lax cookie restrictions). Recommended Fix Check the return value of `validateSignature()` and throw on failure. In `src/SSO/Service/SAMLService.php`, fix both call sites: ```php // In handleSSORequest(), replace lines 416-419: // Validate signatures if ($client->getValue('smc_require_auth_signed') || $client->getValue('smc_validate_signatures')) { $result = $this->validateSignature($client, $request, (bool)$client->getValue('smc_require_auth_signed')); if ($result !== true && $result !== false) { // $result is an error message string — validation failed throw new Exception($result); } } // In handleSLORequest(), replace lines 611-614 with the same pattern: if ($client->getValue('smc_require_auth_signed') || $client->getValue('smc_validate_signatures')) { $result = $this->validateSignature($client, $request, (bool)$client->getValue('smc_require_auth_signed')); if ($result !== true && $result !== false) { throw new Exception($result); } } ``` Alternatively, refactor `validateSignature()` to throw exceptions on failure (matching the developer's original intent as documented in the comments), which would make both call sites correct as-is: ```php public function validateSignature(SAMLClient $client, SamlMessage $message, bool $required = false): bool { global $gL10n; $certPem = $client->getValue('smc_x509_cert…

Related

Alias · CVE (1)

  • CVE-2026-41669 Admidio is an open-source user management solution. vulnerability

Weakness · CWE (1)

  • CWE-347 Improper Verification of Cryptographic Signature weakness

References