CVE-2026-22032

SAML RelayState Hijacking: Phishing with Directus's Permission (CVE-2026-22032)

Alon Barad
Alon Barad
Software Engineer

Jan 7, 2026·6 min read

Executive Summary (TL;DR)

Directus versions prior to 11.14.0 failed to validate the destination URL provided in the SAML `RelayState`. Attackers can craft malicious login links that, upon successful authentication, redirect victims to phishing sites. The fix involves strict validation of the redirect target against an allowlist.

A logic flaw in the Directus SAML authentication driver allows unauthenticated attackers to weaponize the `RelayState` parameter, turning legitimate login flows into open redirect vectors.

The Hook: XML, Trust, and Broken Promises

SAML (Security Assertion Markup Language) is the grandfather of Single Sign-On. It’s verbose, XML-heavy, and notoriously difficult to implement correctly without shooting yourself in the foot. In a typical SAML flow, there is a parameter called RelayState. Its job is innocent enough: it remembers where the user was trying to go before the application rudely interrupted them to ask for a password.

Think of RelayState like a coat check ticket. You hand over your coat (the user's intent), get authenticated, and then hand the ticket back to get your coat. The problem with Directus (specifically the @directus/api package) was that the coat check attendant didn't care whose ticket you handed them. If you handed them a ticket that said "Give this person a bomb," they'd oblige.

In CVE-2026-22032, we aren't exploiting a memory corruption bug or a buffer overflow. We are exploiting blind trust. The Directus SAML driver assumed that whatever URL came back from the Identity Provider (IdP) in the RelayState field was safe to visit. As we'll see, that assumption turns a trusted corporate login portal into the perfect launchpad for a phishing campaign.

The Flaw: The Redirect to Nowhere

The vulnerability resides in api/src/auth/drivers/saml.ts. This file handles the heavy lifting of the SAML handshake—generating the AuthnRequest, sending the user to the IdP, and processing the Assertion Consumer Service (ACS) callback.

When a user initiates a login, the application generates a RelayState. However, in many implementations (including the way Directus handled it), this state is round-tripped through the client or the IdP. When the IdP posts the success assertion back to Directus, it includes that RelayState.

The logic flaw here is a classic Open Redirect (CWE-601), but it's nested inside a trusted workflow, making it far more dangerous than a generic ?redirect_url= bug. The code extracted the RelayState and, without checking if it pointed to a local resource or a trusted domain, issued an HTTP 302 redirect to that value.

This means the validation gap existed in the ACS Callback Handler. Even if the authentication failed, or succeeded, the code prioritized getting the user to their destination. The developers prioritized User Experience (getting you where you wanted to go) over Security (checking if you should go there).

The Code: Anatomy of a Fix

Let's look at the "smoking gun" in the TypeScript source. Before the patch, the code essentially grabbed the relayState and passed it directly to the response handler. It was a straight pipe from user input to HTTP header.

The fix, introduced in commit dad9576ea9362905cc4de8028d3877caff36dc23, adds a mandatory gatekeeper. The developers introduced a utility function isLoginRedirectAllowed to verify the URL.

The Patch Analysis:

// api/src/auth/drivers/saml.ts
 
// BEFORE: Blindly redirecting
// (Conceptually simplified)
// const relayState = req.body.RelayState;
// res.redirect(relayState);
 
// AFTER: Trust, but Verify
if (relayState && isLoginRedirectAllowed(relayState, providerName) === false) {
    throw new InvalidPayloadError({ 
        reason: `URL "${relayState}" can't be used to redirect after login` 
    });
}
 
try {
    const { sp, idp } = getAuthProvider(providerName) as SAMLAuthDriver;
    // ... proceed with SAML parsing ...
}

The isLoginRedirectAllowed function checks two things:

  1. Is the URL a relative path (e.g., /admin/content)?
  2. If it's absolute, does it match the configured PUBLIC_URL or an entry in AUTH_SAFE_REDIRECT_URLS?

If neither condition is met, the application throws an InvalidPayloadError before processing the SAML assertion. This effectively kills the attack chain before the redirect can occur.

The Exploit: Phishing 2.0

To exploit this, an attacker doesn't need to touch the server directly or brute force credentials. They just need to construct a URL. This is a "Reflected" Client-Side attack leveraging Server-Side logic.

The Attack Scenario:

  1. Recon: The attacker notices the target uses Directus with SAML (e.g., Okta or Azure AD) at https://cms.target-corp.com.

  2. The Hook: The attacker crafts a link. Instead of sending the victim to a fake login page immediately, they send them to the real login page with a poisoned parameter.

    https://cms.target-corp.com/auth/login/saml?RelayState=https://evil-phish.com/fake-dashboard
  3. The Bait: The victim clicks the link. They see the legitimate cms.target-corp.com URL. They are redirected to their real Okta/Azure AD login. They enter their real credentials. Everything looks perfect.

  4. The Switch: The IdP authenticates them and sends them back to Directus. Directus sees the valid session, logs them in, reads the RelayState, and immediately redirects them to https://evil-phish.com/fake-dashboard.

  5. The Catch: The victim lands on the attacker's site, which is a clone of the Directus dashboard. Since they just logged in, they trust the context. The attacker can now ask for "Step 2 Verification" or simply serve malware.

Here is the flow of the attack:

The Impact: Why This Matters

You might argue, "It's just an open redirect, big deal." But in the context of an Identity Provider, an open redirect is a golden ticket for social engineering. The most difficult part of phishing is convincing a user to enter credentials into a site they trust.

By leveraging CVE-2026-22032, the attacker borrows the credibility of the victim's own infrastructure. The initial link is legitimate. The SSL certificate is legitimate. The authentication flow is legitimate. The only thing illegitimate is the final destination.

This vulnerability allows for:

  • High-Fidelity Phishing: Redirecting users to a "Session Expired" page immediately after login to harvest the credentials they just used.
  • Token Theft: In some OAuth/SAML hybrid flows, if the redirect includes session fragments, those tokens could be leaked to the external server via the Referer header (though less likely in this specific strict SAML implementation).
  • Malware Distribution: Redirecting authenticated users to a browser exploit kit hosted on an external domain.

The Fix: Closing the Loophole

The remediation is straightforward, but it requires action. The patch provided by the Directus team enforces strict allowlisting on redirect destinations.

Immediate Steps:

  1. Upgrade: Update your Directus instance to version 11.14.0 or higher immediately. This version contains the patch in saml.ts.
  2. Configuration: Verify your PUBLIC_URL environment variable is set correctly. This variable is now the source of truth for allowed redirect domains.

Defense in Depth: If you cannot upgrade immediately (why not?), you can implement a WAF rule. Inspect the RelayState query parameter or body field. If it contains http:// or https:// and the domain does not match your own, block the request. However, be warned: RelayState is often URL-encoded or Base64 encoded, making WAF rules brittle. The code fix is the only robust solution.

Fix Analysis (1)

Technical Appendix

CVSS Score
6.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Affected Systems

Directus CMS < 11.14.0@directus/api package

Affected Versions Detail

Product
Affected Versions
Fixed Version
Directus
Monospace Inc
< 11.14.011.14.0
AttributeDetail
CWE IDCWE-601
Attack VectorNetwork (Web)
CVSS v3.1 Estimate6.1 (Medium)
ImpactPhishing / Redirect
Patch Commitdad9576ea9362905cc4de8028d3877caff36dc23
Release Date2026-01
CWE-601
URL Redirection to Untrusted Site ('Open Redirect')

The software accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This facilitates phishing attacks.

Vulnerability Timeline

Patch merged into Directus main branch
2025-12-09
CVE-2026-22032 assigned
2026-01-01
Directus v11.14.0 released with fix
2026-01-15

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.