Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
SAML 2.0 and OpenID Connect (OIDC) are the two dominant federation protocols for browser-based single sign-on, and they solve the same problem — letting an Identity Provider (IdP) assert a user’s identity to a Service Provider/Relying Party (SP/RP) without the SP handling credentials directly — with different wire formats and different failure modes. SAML is XML-based, built on the WS-Security tradition, and dominant in enterprise SaaS; OIDC is a JSON/JWT identity layer on top of OAuth 2.0 and dominant in consumer and API-centric stacks. Choosing between them, and validating either correctly, is a security decision, not just an integration one.
Both protocols are secure when implemented against a vetted library and validated fully; both have produced a long history of real-world vulnerabilities when implementers roll their own parsing or skip a validation step. The bug classes differ by format: SAML’s XML signature model is vulnerable to structural manipulation of the document (signature wrapping); OIDC’s JWT model is vulnerable to algorithm and key confusion when the verifier trusts fields the attacker controls.
Attack Prerequisites
Exploiting either federation protocol generally requires:
- Access to the login/assertion flow — either as a legitimate user of one tenant trying to impersonate another, or as a network position able to intercept or replay a POST-bound assertion or token.
- An SP or RP with incomplete validation — accepting a SAML response whose signature covers the wrong element, or an OIDC client/resource server that does not fully validate a JWT’s
iss,aud,exp, and signing algorithm. - For SAML specifically, the ability to submit a crafted XML document to the Assertion Consumer Service (ACS) URL — usually just a normal browser POST, since SAML relies on the browser as a confused deputy.
- For OIDC specifically, a resource server or client library that trusts the
algheader in the JWT rather than pinning the expected algorithm server-side.
How It Works
SAML SSO runs over browser redirects: the SP sends an AuthnRequest to the IdP (or the flow starts IdP-initiated with no request at all), the user authenticates at the IdP, and the IdP returns a Response containing one or more Assertion elements via the HTTP-POST binding — an auto-submitting HTML form that posts the XML to the SP’s Assertion Consumer Service URL. The Assertion carries the Subject, AudienceRestriction, Conditions (NotBefore/NotOnOrAfter), and is protected by an XML Digital Signature (XML-DSig) that the SP must verify before trusting anything inside it.
OIDC runs the OAuth 2.0 Authorization Code flow (with PKCE for public clients) and adds an ID Token — a signed JWT asserting who authenticated, when, and to which client. The RP fetches the IdP’s discovery document at /.well-known/openid-configuration, retrieves signing keys from the referenced JWKS endpoint, and must verify the token’s signature, iss (issuer), aud/azp (intended client), exp, and nonce (anti-replay, tied to the original authorization request) before trusting the claims.
The critical difference is *where* trust is anchored. SAML trust is anchored in whether the specific Assertion element the SP’s business logic reads is the one covered by a valid signature — and XML allows many ways to restructure a document without breaking the referenced signature. OIDC trust is anchored in whether the verifier pins the signing algorithm and key independently of what the token claims about itself — and naive JWT libraries have historically trusted the token’s own alg header to decide how to verify it.
Vulnerable Code / Configuration
XML Signature Wrapping (XSW). A signature over an Assertion is valid as long as the referenced element, by ID, has not changed — but nothing stops an attacker from moving the *signed* assertion elsewhere in the document and inserting a *forged, unsigned* assertion where the application actually looks for it:
<samlp:Response>
<!-- Forged assertion the app's parser reads first (e.g. by XPath
//Assertion or first-child), claiming admin -->
<saml:Assertion ID="_evil">
<saml:Subject><saml:NameID>admin@corp.example</saml:NameID></saml:Subject>
</saml:Assertion>
<!-- Original, validly signed assertion, now wrapped as a sibling
or moved under an <Extensions>/<Object> element -->
<saml:Assertion ID="_original">
<saml:Subject><saml:NameID>attacker@corp.example</saml:NameID></saml:Subject>
<ds:Signature>
<ds:SignedInfo>
<ds:Reference URI="#_original">...</ds:Reference>
</ds:SignedInfo>
</ds:Signature>
</saml:Assertion>
</samlp:Response>
<!-- Signature validation walks the Reference URI and finds a valid
signature over #_original -- but the app's business logic reads
the first/only Assertion it finds by tag name, which is #_evil. -->
XMLJWT algorithm confusion in an OIDC verifier that trusts the token’s own header instead of pinning the expected algorithm server-side:
// VULNERABLE: verify() derives the algorithm from the token itself.
const claims = jwt.verify(idToken, rsaPublicKeyPem);
// If the library supports both RS256 and HS256 and the caller does not
// restrict algorithms, an attacker can send a token with
// header {"alg":"HS256"} and sign it with the RSA PUBLIC key (which
// is not secret) as an HMAC secret -- verification succeeds because the
// same string is used as both the public key and the HMAC key.
// FIXED: pin the algorithm and key independently of the token header.
const claims = jwt.verify(idToken, rsaPublicKeyPem, {
algorithms: ['RS256'],
issuer: 'https://idp.example/',
audience: 'client-id-1234',
});
JavaScriptWalkthrough / Exploitation
Testing SAML signature handling with a tool that automates the known XSW variants (XSW1 through XSW8, from the Somorovsky et al. “Breaking SAML” research) against a captured, validly signed response:
git clone https://github.com/CommunicationSecurityGroup/SAMLRaider
# Burp extension: intercept a POST to the ACS URL, right-click the
# SAMLResponse parameter -> SAML Raider -> apply each XSW variant,
# resend, and check whether the SP authenticates as the forged Subject.
BashAlso test for missing InResponseTo validation (unsolicited-response replay), an overly broad AudienceRestriction, and IdP-initiated SSO accepted without an SP-side correlation to a request it actually made (a relay-state/login-CSRF risk).
For OIDC, probe algorithm handling and audience checks directly against the token endpoint and resource server:
# Decode header/claims to inspect alg and aud without a key:
echo <id_token> | cut -d. -f1 | base64 -d
echo <id_token> | cut -d. -f2 | base64 -d
# Test alg=none acceptance (strip signature, set alg to none):
python3 -c "import jwt; print(jwt.encode({'iss':'https://idp.example/','aud':'client-id','sub':'admin'}, key=None, algorithm=None))"
BashNote: SAML’s attack surface is larger per-message (a full XML document parser and canonicalizer in the trust path) but the protocol itself has no bearer-token replay problem across services the way a stolen OIDC access token can be replayed against any resource server that accepts it. OIDC’s JSON/JWT format is simpler to parse correctly but pushes more responsibility onto the client library’s algorithm and key-selection logic.
Opsec: SAML Raider and similar tooling generate malformed XML that some IdPs/SPs log as parser errors distinct from normal auth failures — expect elevated noise in SP application logs during this testing and coordinate the window with the blue team.
Detection and Defense
- Use a vetted, actively maintained library for both protocols (
python3-saml,Sustainsys.Saml2,passport-samlwith strict mode;oidc-client-ts, framework-native OIDC middleware) — do not hand-parse assertions or tokens. - Verify the SAML signature over the exact element the business logic reads, reject documents with duplicate
IDattributes or unexpected structure, and validateConditions,AudienceRestriction, andInResponseToon every response. - Pin JWT algorithm and signing key server-side; never derive the verification algorithm from the token’s own
algheader. - Validate
aud/azp,iss,exp, andnonceon every ID token and keep token lifetimes short. - Rotate signing keys and metadata on a schedule and monitor IdP admin consoles and sign-in logs for anomalous federation configuration changes.
Real-World Impact
XML Signature Wrapping against SAML implementations was documented across numerous major identity vendors and open-source libraries following the 2012 Ruhr-University Bochum research “On Breaking SAML: Be Whoever You Want to Be,” and JWT algorithm-confusion bugs (RS256/HS256 key confusion, alg:none acceptance) have recurred across OIDC client libraries in multiple languages since. Both bug classes are structural — they follow from the message formats themselves — which is why library choice and configuration, not protocol choice alone, determines real-world security outcomes.
Conclusion
Neither SAML nor OIDC is inherently more secure; each has a well-understood failure mode tied to its format — structural signature wrapping for XML, algorithm/key confusion for JWT — and both are reliably closed by using mature libraries and validating every field the specification requires, not just checking that *a* signature is present somewhere in the message.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments