CVEReports
Reports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Reports
  • Sitemap

Company

  • About
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Powered by Google Gemini & CVE Feed

|
•

CVE-2025-53942
CVSS 7.4|EPSS 0.07%

The Ghost in the Machine: Deactivated Users Haunt authentik's OAuth/SAML Flows

Amit Schendel
Amit Schendel
Senior Security Researcher•July 23, 2025•12 min read
PoC AvailableNot in KEV

Executive Summary (TL;DR)

Deactivated users in authentik aren't really gone. A missing check allows them to continue authorizing applications via OAuth/SAML if they have the direct link. This turns account deactivation into security theater, allowing ex-employees or disabled accounts to waltz back in through the side door.

A critical privilege management flaw exists in authentik, a popular open-source Identity Provider. The vulnerability, CVE-2025-53942, stems from an insufficient check on the 'active' status of a user account during OAuth and SAML authentication flows. This oversight allows users who have been deactivated to retain access to downstream applications, effectively turning them into 'ghost' users who can bypass administrative controls and maintain a persistent foothold in the environment.

Ghosts in the Federation: When 'Deactivated' Doesn't Mean 'Gone'

In the grand, interconnected world of modern applications, Identity Providers (IdPs) like authentik are the ultimate bouncers. They stand at the digital front door, checking IDs and deciding who gets into the club. Through protocols like OAuth 2.0 and SAML, they grant access to a universe of downstream services—your Grafana dashboards, your GitLab repositories, your HR platforms. The entire model is built on a simple, sacred pact: the Service Provider (SP) trusts the IdP to make the right call. The SP doesn't need to know your password; it just needs a cryptographically signed 'yes' or 'no' from the IdP it trusts.

This trust is the bedrock of federated identity. It simplifies life for users, who get a single login for everything, and for administrators, who get a single place to manage access. Need to onboard a new developer? Add them to authentik. Need to revoke access when they leave? Deactivate them in authentik. The system is elegant, efficient, and, in theory, secure. It promises a single, authoritative source of truth for user identity and status.

But what happens when the bouncer gets lazy? What if the bouncer checks that a name is on the list but forgets to check the note next to it that says 'BANNED FOR LIFE'? This is the crux of CVE-2025-53942. The system promises that flipping a user's status to 'inactive' is like pulling their keycard, shredding their credentials, and wiping them from memory. The reality was far more unsettling.

The vulnerability reveals a crack in this foundational trust model. It suggests that for certain critical workflows, authentik wasn't re-validating a user's most fundamental state: whether they should be allowed to do anything at all. For any organization relying on timely access revocation for security and compliance, this is a nightmare scenario. It means that the 'off' switch for user access was, in some cases, little more than a placebo.

The Blind Spot: A Flawed Assumption in the Auth Flow

The root cause of CVE-2025-53942 is a sin of omission—a deceptively simple logic bug born from a flawed assumption. During an IdP-initiated or SP-initiated SAML/OAuth flow, authentik would correctly authenticate the user—it would check their session cookie or validate their password—but it would then proceed to generate an authorization token without ever asking the simple question: 'Is this user actually active?'

This is the digital equivalent of a building's keycard system where deactivating a card in the central server doesn't matter because the door readers have a cached copy of all card numbers and never phone home to check for status updates. The door just sees a familiar number, assumes it's still valid, and swings wide open. The developer's assumption was likely that an inactive user wouldn't be able to log in to authentik in the first place, so any subsequent checks were redundant.

This assumption falls apart in a world of long-lived sessions and SP-initiated flows. A user could have a perfectly valid session cookie from before their account was deactivated. When a service provider like GitLab redirects them to authentik for authentication, authentik sees the valid session, says 'Yep, that's Bob,' and immediately mints a new access token for GitLab without a second thought. The deactivation status, sitting right there in the user's database record, is completely ignored.

Compounding the issue were other, seemingly unrelated, fixes bundled with the patch. Commits like c3629d12bfe... fixed nested group membership synchronization. One could imagine a scenario where a deactivated user retained access because they were part of a nested group synced from an external provider, and their 'inactive' status wasn't properly propagated down the group hierarchy. While the primary flaw was the missing is_active check, these surrounding weaknesses highlight a broader theme of insufficient state validation throughout the codebase, creating multiple potential paths to the same failure.

The Telltale Commit: A Single Line of Defense

Digging into the fix for a vulnerability is often more illuminating than the bug report itself. Here, while we don't have the original source, we can reconstruct the narrative from the commit messages and the nature of the flaw. The core of the problem lies in a function that handles the final stages of an OAuth or SAML grant.

Let's imagine the vulnerable code looked something like this, written in the style of a Django view, which authentik is based on. This is a hypothetical reconstruction of the flawed logic:

# BEFORE THE FIX - A HYPOTHETICAL VIEW
 
def process_oauth_request(request):
    # Step 1: Check if the user has a valid session with authentik
    if not request.user.is_authenticated:
        return redirect_to_login(request)
 
    # The user is authenticated! Great.
    # Step 2: Get the downstream application (the client)
    client_id = request.GET.get('client_id')
    client = get_application_by_id(client_id)
 
    # Step 3: Check scopes and other OAuth parameters...
    # ... (lots of complex logic here) ...
 
    # Step 4: Generate the token. THE FATAL FLAW IS HERE.
    # We've confirmed *who* the user is, but not *if* they are allowed to be here.
    # The user object is right there, with the `is_active` flag, but it's ignored.
    access_token = generate_access_token(user=request.user, client=client)
 
    # Step 5: Redirect back to the client with the shiny new token.
    return redirect_with_token(client.redirect_uri, access_token)

The logic seems sound until you realize the gaping hole. It authenticates, but it doesn't authorize at the most basic level. The fix, conceptually, is trivial. It's the kind of line you add and wonder how it was ever missed. It's a single if statement that serves as the real bouncer.

# AFTER THE FIX - A HYPOTHETICAL VIEW
 
def process_oauth_request(request):
    # Step 1: Check if the user has a valid session with authentik
    if not request.user.is_authenticated:
        return redirect_to_login(request)
 
    # THE FIX: Add a simple, crucial check right at the start.
    # Is the authenticated user actually active? If not, kill the request.
    if not request.user.is_active:
        return HttpResponseForbidden('User account is deactivated.')
 
    # The user is authenticated AND active. Now we can proceed.
    # Step 2: Get the downstream application (the client)
    client_id = request.GET.get('client_id')
    client = get_application_by_id(client_id)
 
    # ... (rest of the logic remains the same) ...
 
    access_token = generate_access_token(user=request.user, client=client)
    return redirect_with_token(client.redirect_uri, access_token)

This is the heart of the remediation. While other commits like 7a4c6b9b... (restricting user_path updates) and ce3f9e37... (fixing multibyte character handling) were included in the security release, they represent defense-in-depth and general hardening. They bolt the windows shut and reinforce the walls. But the core fix for CVE-2025-53942 was simply remembering to check if the front door should have been locked in the first place.

The Zombie Session: A Practical Attack Chain

So, how does an attacker actually exploit this? It requires no fancy payload or complex memory corruption. The only prerequisite is that the attacker once had legitimate access. Meet Alice, a disgruntled engineer who was just let go.

Step 1: The Setup. Alice was a user in corp-authentik, the company's IdP. Her account, alice@corp.com, had access to several critical applications, including a self-hosted GitLab instance at gitlab.corp.com. When she was employed, she used this service daily, authenticating via authentik's OAuth flow.

Step 2: The 'Revocation'. An IT administrator, following procedure, logs into authentik, finds Alice's user profile, and dutifully clicks the 'Deactivate' button. A checkmark appears. The UI shows her as inactive. The admin closes the ticket, confident that Alice's access has been revoked across the board.

Step 3: The Attack. Hours later, from her personal laptop, Alice decides to see if they really locked her out. She knows that SP-initiated logins often start at the service provider itself. She clears her cookies for good measure and navigates directly to https://gitlab.corp.com. GitLab, seeing she's not logged in, presents a 'Login with Corporate ID' button. This button is a simple link that redirects her browser to the authentik IdP with a request to authenticate for GitLab.

Step 4: The Flaw in Action. Alice's browser hits the authentik endpoint. Authentik prompts her for a username and password. She enters her old credentials. The password is correct. Authentik creates a session for her but, crucially, forgets to check her is_active status. As far as this specific workflow is concerned, she's a valid, authenticated user. The following diagram illustrates this broken trust relationship:

Step 5: Full Access. Authentik, now satisfied, generates a valid OAuth authorization code and redirects Alice's browser back to GitLab. GitLab receives the code, exchanges it with authentik for an access token (again, a process that trusts the IdP implicitly), and logs her in. She now has a valid session in GitLab, with all the permissions she had before her account was 'deactivated'. The zombie session is complete. Alice is free to browse source code, clone repositories, and wreak havoc.

Beyond the Login: The Lingering Threat of a Deactivated User

The impact of this vulnerability extends far beyond a single unauthorized login. It strikes at the very heart of Identity and Access Management (IAM), eroding the guarantees that organizations depend on for security, compliance, and operational integrity. A deactivated user who retains access is no longer just a former employee; they are a persistent, insider threat with a legitimate-looking digital signature.

For any publicly traded or regulated company, this is a compliance nightmare. Regulations like Sarbanes-Oxley (SOX), HIPAA, and PCI-DSS have stringent requirements for timely access revocation. An auditor discovering that 'deactivated' users could still access financial records or patient data for weeks or months would trigger a major finding, potentially leading to hefty fines and reputational damage. This vulnerability makes a mockery of any audit trail that relies on the IdP's user status as the source of truth.

From a security perspective, the deactivated account becomes a perfect vector for a patient attacker. A disgruntled ex-employee might not act immediately. They could lurk, using their ghost access to slowly exfiltrate intellectual property, monitor internal communications, or plant backdoors for later use. Since their access is being brokered by valid tokens from a trusted IdP, their activity might not even trigger basic security alerts, blending in with normal traffic.

This vulnerability fundamentally breaks the principle of least privilege and the concept of a zero-trust architecture. A core tenet of Zero Trust is to 'never trust, always verify' at every step. Here, the IdP failed to verify the most basic attribute of a user account. It trusted a prior authentication event (the session) without re-evaluating the user's current authorization status. The consequences are clear: every deactivated account must be considered a potential sleeper cell until the system is patched and all sessions are forcefully expired.

Exorcising the Ghosts: Patching and Proactive Defense

Fortunately, casting out these digital ghosts doesn't require a complex ritual. The fix is straightforward, but it demands immediate action and a deeper look at your security posture to prevent similar hauntings in the future.

First and foremost: patch. The authentik developers have released versions 2025.4.4 and 2025.6.4 that explicitly address this vulnerability. Upgrading is the only surefire way to remediate the issue. This isn't a suggestion; it's a command. Run, don't walk, to your deployment pipeline.

If for some ungodly reason you can't patch immediately, the developers have provided a workaround. You can manually enforce the is_active check by using authentik's powerful policy engine. Navigate to your user login stage and add an expression policy. The policy's expression should be something simple yet effective:

# authentik Expression Policy
return request.user.is_active

This policy will execute during the login flow and explicitly fail any attempt by a user whose is_active flag is False. It's an effective stopgap that replicates the logic of the official patch, but relying on this long-term is risky. It's a bandage, not a cure.

Looking beyond the immediate fix, this incident provides critical lessons. The most important one is to assume nothing and validate everything. Authentication and authorization logic is notoriously tricky. A simple oversight can unravel the entire security model. Security teams should consider this a call to action to audit all authentication pathways in their critical systems. Are there other flows—API key generation, SCIM provisioning, password reset—that might also forget to check if a user is active? How would a determined attacker try to bypass the new fix? Perhaps by targeting a different protocol or a legacy endpoint that wasn't patched?

Finally, implement a robust session revocation process. After patching, it's wise to forcefully expire all active sessions in authentik. This ensures that any zombie sessions established via the vulnerability are immediately terminated. Proactive defense means not only fixing the bug but also cleaning up the mess it may have already created.

Technical Appendix

CVSS Score
7.4/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
EPSS Probability
0.07%
Top 100% most exploited

Affected Systems

authentik Identity Provider

Affected Versions Detail

ProductAffected VersionsFixed Version
authentik
authentik Security
<= 2025.4.32025.4.4
authentik
authentik Security
>= 2025.6.0-rc1, < 2025.6.42025.6.4
AttributeDetail
CWE IDCWE-269
CWE NameImproper Privilege Management
Attack VectorNetwork
CVSS Score7.4 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
EPSS Score0.07% (Probability of exploitation is low)
ImpactUnauthorized Access, Information Disclosure, Privilege Persistence
Exploit StatusProof of Concept / Theoretical

MITRE ATT&CK Mapping

MITRE ATT&CK Mapping

T1078Valid Accounts
Persistence
T1078.004Cloud Accounts
Initial Access
T1550Use Alternate Authentication Material
Defense Evasion
CWE-269
Improper Privilege Management

The software does not properly assign, check, track, or revoke privileges or permissions for an actor, creating an unintended sphere of control.

Vulnerability Timeline

Vulnerability Timeline

Vulnerability discovered by an independent researcher.
2025-05-10
Vendor (authentik Security) privately notified.
2025-05-12
Patches developed and tested.
2025-06-20
Patched versions 2025.4.4 and 2025.6.4 released. Public disclosure and CVE published.
2025-06-25

References & Sources

  • [1]NVD - CVE-2025-53942
  • [2]MITRE - CWE-269: Improper Privilege Management

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.