Safeguard
Application Security

SAML SSO vulnerabilities: signature wrapping and assertion replay explained

A 2024 ruby-saml flaw (CVE-2024-45409, CVSS 9.8) let attackers forge SAML assertions and log in as any user, including admins — seven years after the same bug class was first disclosed.

Safeguard Research Team
Research
6 min read

SAML has been the backbone of enterprise single sign-on since the SAML 2.0 spec shipped in 2005, and its core weakness has aged remarkably badly: the protocol separates where an assertion sits in an XML document from what a service provider reads out of it, and for two decades implementers have gotten that separation wrong. Duo Security researcher Kelby Ludwig found a wave of XML Signature Wrapping (XSW) bugs across major SAML libraries during an internal review in late 2017, and Duo publicly disclosed them in February 2018, coordinated with CERT/CC: OneLogin's python-saml (CVE-2017-11427), OneLogin's ruby-saml (CVE-2017-11428), Clever's saml2-js (CVE-2017-11429), and OmniAuth-SAML (CVE-2017-11430) — all sharing the same root cause: a signed XML document can be verified as authentic while an attacker-controlled copy of an element gets parsed and trusted instead. Shibboleth (CVE-2018-0489) and Duo Network Gateway (CVE-2018-7340) followed. More than six years later, on September 10, 2024, independent researcher ahacker1 of SecureSAML disclosed CVE-2024-45409 in ruby-saml — CVSS 9.8, the same bug class, still capable of full authentication bypass. This post walks through why SAML keeps reintroducing this flaw, how assertion replay compounds it, and what actually closes both gaps.

What is XML Signature Wrapping, and why does it fool signature checks?

XML Signature Wrapping works because XML signature validation and business-logic parsing are two separate code paths that don't have to agree on which element they're looking at. A SAML response is validated by walking the document, finding a signed <Assertion>, confirming its digest and signature, and declaring the response trustworthy. But the code that later extracts the NameID or attributes to actually log the user in often does a separate, independent traversal — commonly an XPath query like "find the first Assertion" or "find the first element with this ID." An attacker who has one legitimately-signed assertion (their own) can wrap it: insert a second, forged assertion or a decoy element ahead of or around the signed one, so the signature-checking path validates the original untouched assertion while the extraction path reads attacker-controlled content instead. Ludwig's original research showed this could impersonate any user with a handful of injected XML nodes, because canonicalization and value-extraction disagreed about the document's effective structure.

How did CVE-2024-45409 bring signature wrapping back in 2024?

CVE-2024-45409 (ruby-saml, which underpins GitLab's SAML SSO) recurred because ruby-saml's XPath selector for the signature's DigestValue searched the whole document rather than scoping to the specific signed element by ID. An attacker could take one validly-signed assertion, wrap a second forged assertion around it, and place the forged assertion's digest inside a samlp:extensions block earlier in the document. Because the digest lookup wasn't anchored to the ID referenced in the SignedInfo, it picked up the attacker's decoy digest, matched it against the attacker's forged content, and reported the whole response as validly signed — enabling full authentication bypass, including admin account takeover. Around the same period, GitHub Enterprise Server shipped fixes for a separate but related signature-verification bypass in its own SAML SSO stack, tracked as CVE-2024-4985 and its regression CVE-2024-9487. The ruby-saml maintainers fixed the root cause in ruby-saml 1.17.0 and the 1.12.x backport 1.12.3, switching to relative XPath scoped to the signed element and enforcing that only one element may carry a given signature ID — closing the ambiguity that made wrapping possible in the first place.

How is assertion replay different from signature wrapping, and why is it still common?

Assertion replay doesn't touch the signature at all — it reuses a completely valid, correctly-signed assertion a second time. If a service provider doesn't enforce the assertion's NotOnOrAfter condition, doesn't track which assertion IDs it has already consumed, or doesn't check InResponseTo against a request it actually issued, an attacker who intercepts one login response (over an unencrypted proxy, a shared browser, a misconfigured log pipeline, or a compromised network device) can resubmit it and get authenticated again, potentially long after the original session ended. This is a design gap rather than a parsing bug, which is exactly why it doesn't get a CVE of its own each time it appears — it's a missing control, not a broken one. It shows up repeatedly in penetration-test findings against homegrown SAML consumers precisely because the SAML spec makes replay protection optional-by-implementation: OneTimeUse and short validity windows are recommended, not mandated by every library's defaults.

What actually stops signature wrapping at the code level?

Stopping XSW requires the signature-validation path and the data-extraction path to agree on exactly one element, referenced by its cryptographic ID, not by document position. Concretely: validate the signature against the canonicalized XML per the SignedInfo's Reference URI, then extract the NameID and attributes from that same referenced element — never re-query the document independently. Reject any response containing more than one element sharing the same ID, since a legitimate assertion never needs a duplicate. And parse with an XML library that strips comments and processing instructions before both canonicalization and extraction, since Ludwig's 2017 disclosures showed that some of the affected libraries were vulnerable to attackers hiding forged content inside an XML comment that the extraction code silently skipped past. This is a library-level fix, not a configuration option — it's why every one of the CVEs above required a patched release, not a settings change.

What stops assertion replay at the service-provider level?

Replay protection needs three enforced conditions, none of which are safe to leave as defaults you assume are on. First, honor NotOnOrAfter and NotBefore on both the assertion and its SubjectConfirmationData, with a short window — a few minutes, not hours — since a captured assertion is only dangerous within its validity period. Second, track consumed assertion IDs (and InResponseTo values against requests you actually issued) in server-side state for at least as long as the validity window, and reject anything seen twice; a stateless SP that only checks the signature is replay-blind by design. Third, enforce AudienceRestriction so an assertion minted for one service provider's entity ID can't be replayed against a different one — a real risk in multi-tenant or multi-app SSO deployments sharing one identity provider.

How does Safeguard's own SAML integration reflect this?

Safeguard acts as a SAML service provider itself, with a Keycloak-backed Assertion Consumer Service at /auth/idp/realms/<tenant>/broker/<alias>/endpoint, and its documented configuration for providers like Auth0 and Authentik requires a signed assertion carrying a matching email attribute rather than trusting an unsigned NameID alone. Safeguard's ACS also only accepts HTTP-POST-bound responses — Authentik's setup guide notes explicitly that a signed assertion sent over HTTP-Redirect binding gets rejected, not silently downgraded — which removes an entire transport-level path (URL-length-constrained, cacheable, more easily logged or replayed via browser history) that attackers have historically abused to move assertions somewhere binding checks weren't enforced. Binding and signature enforcement don't substitute for the ID-scoped XPath and replay-tracking fixes described above — those live in the identity broker's SAML library, not in application configuration — but they remove two easy misconfiguration paths before a single assertion is ever parsed.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.