SAML轮盘赌:黑客必胜之道 | PortSwigger研究
Gareth Heyes
研究员
@garethheyes
发布时间:2025年3月18日 14:55 UTC
更新时间:2025年3月31日 07:10 UTC
引言
本文将详细展示如何通过组合往返攻击和命名空间混淆技术,利用ruby-saml库实现GitLab Enterprise的未授权管理员访问。
在研究过程中,GitHub独立发现并修复了我们的漏洞。然而,他们的披露遗漏了关键技术细节,包括特定的变异方式以及如何在无需认证的情况下利用该漏洞。
我们相信,完整分享这些攻击的工作原理对于提升安全性至关重要,通过让每个人都掌握识别、缓解和防御此类威胁所需的知识。
往返攻击基础
SAML库通常解析XML文档,将其存储为字符串,然后重新解析。在Ruby-SAML中,此过程涉及两个不同的解析器:REXML(用于解析文档和验证签名)和Nokogiri(用于访问属性)。如果在此过程中发生任何变异,文档在第二次解析时可能不再相同。
为了确保安全授权,文档必须被一致地解析和序列化;否则可能会出现结构不一致。这些不一致可被用于往返攻击。通过利用XML注释和CDATA部分,攻击者可以在变异过程中操纵文档结构,绕过签名验证,有效获得未授权访问并冒充其他用户身份。
Ruby SAML/REXML往返攻击
为便于测试,我们开发了一个测试平台来识别往返漏洞并高效评估多个SAML库。我首先检查文档类型定义(DOCTYPE),因为过去曾发现类似漏洞。我的初始方法侧重于分析XML实体如何被解析,因此在该领域进行了测试。
在Juho的原始发现中,使用符号声明引入了引号解析的不一致性。基于此,我调查是否有其他漏洞被遗漏。经过广泛测试,我发现可以在SYSTEM标识符中引入变异。
在文档的初始解析期间,遇到的第一个标签是原始"assertion":
|
|
然而,重新解析文档时,结果完全改变,现在反映攻击者的"assertion":
|
|
如所示,单引号的系统标识符被转换为双引号。但由于标识符内部包含双引号,这改变了XML文档的语法,导致XML注释被处理,产生完全不同的节点。我的同事Zak将此变异优化为更简化的攻击向量:
|
|
该向量允许通过操纵文档和伪造断言来利用GitLab和任何其他使用Ruby-SAML库的应用程序,使攻击者能够以任何用户身份登录。但这只是攻击的一部分。我的同事Zak将展示如何将此升级为在GitLab上实现未授权管理员访问。
通过往返攻击在GitLab实现权限提升
理解漏洞
GitLab依赖Ruby-SAML库进行SAML身份验证。但为实现未授权访问,我们需要仔细研究验证过程,因为它在攻击中起关键作用。
在往返发生之前,库会验证SAMLResponse是否包含嵌入文档中的有效证书。这是通过计算证书哈希值并与服务器存储的指纹进行比较来完成的。之后,该证书用于验证签名。请记住,签名是此攻击的关键方面,因为它允许在没有组织凭证的情况下完全接管账户。
签名验证过程
一旦从SAMLResponse中提取证书,实际的签名验证过程开始。首先,文档从其内存表示转换回XML格式。这是Gareth的往返攻击发挥作用的地方。在此阶段,库忽略攻击者断言并继续验证原始元素的签名。
如果攻击者以绕过签名验证的方式伪造断言元素,额外的安全检查就会启动。最关键检查包括:
- 签名元素的ID应在两个文档中匹配
- 规范化属性(用于标准化XML文档)必须在两个版本中相同
然而,所有其他验证检查都在攻击者断言而非原始签名文档上操作。这允许攻击者任意修改验证字段而不破坏签名验证过程。
克服XML模式限制
伪造签名XML文档的一个挑战是使用Nokogiri和预定义模式文件执行XML模式验证。这带来一个限制:攻击者要伪造有效的签名XML文档,必须首先获得通过XML模式验证的文档。
理解XML模式验证
XML模式定义SAML XML文档的结构,指定:
- 有效元素和属性
- 子元素的顺序和数量
- 元素和属性的数据类型
换句话说,签名元素必须是有效的SAML协议元素——例如登录响应、注销响应或元数据。您可能在开发者论坛上找到签名的XML文档,但这种情况不太可能。因此,我们将采用不同方法。相反,我们引入命名空间混淆攻击,使任何使用Ruby-SAML的应用程序都能实现未授权访问。
GitLab未授权访问
在深入攻击之前,让我们回顾SAML模式验证的工作原理。身份提供者(IdP)仅签名Signature节点,而非整个断言。
由于Ruby-SAML使用两个XML解析器:
- REXML读取Signature元素
- Nokogiri读取DigestValue
这两个解析器之间的差异允许我们绕过签名验证。
Ruby-SAML使用XPath查询搜索Signature元素:
|
|
这里,ds指XML命名空间。通常,命名空间防止元素名称冲突,但我们利用XPath搜索中命名空间解释方式的差异。
考虑以下场景:
|
|
第一个Signature元素缺少直接命名空间声明。相反,我们使用XML Doctype技巧:
|
|
安全专家通常关注XXE攻击中的!ENTITY声明,但!ATTLIST声明也可用于利用。!ATTLIST定义Signature元素并为其分配命名空间属性。REXML和Nokogiri都支持基于doctype的命名空间声明,但REXML有一个关键缺陷:
XML标准禁止具有相同名称的重复属性。然而,REXML在doctype声明中忽略此限制。
这允许攻击者定义两个冲突的命名空间属性,其中第二个覆盖第一个。
结果,REXML读取FAKE摘要值,而Nokogiri读取REAL摘要值。
利用策略
要利用此差异:
- 创建两个Signature元素:
- 一个带有有效Digest值
- 一个带有伪造值
这允许攻击者绕过Ruby-SAML的Digest验证过程。
通过组合命名空间混淆与往返攻击利用Ruby < 3.4.2
虽然命名空间混淆单独可以利用Ruby-SAML,但它面临一个限制:
REXML对XML编组/解组的处理不佳引入了另一个往返问题。在Ruby 3.4.2之前,REXML在doctype声明中截断!ATTLIST字符串,使利用变得脆弱。在GitLab中,这会破坏攻击,但两种漏洞的组合仍可使用:
第一次XML解析:REXML最初忽略!ATTLIST值,将其视为字符串文字。 第二次XML解析:REXML然后识别!ATTLIST声明,导致完全利用。
利用WS-Federation获取签名XML
找到有效的签名XML文档可能具有挑战性。幸运的是,身份提供者(IdP)默认静默支持每个租户的单一登录协议:WS-Federation。WS-Federation提供签名的元数据XML端点,例如:
|
|
联邦元数据文档对任何未授权用户公开可访问——所需的只是应用程序的唯一ID,可以轻松从身份提供者的URL提取或使用搜索引擎找到。
虽然此元数据不是有效的SAML元数据文档,但命名空间混淆攻击仅需要有效的Signature元素——一个使用与服务提供者存储的相同证书签名的元素。而它确实是。
通过使用此公开可用的签名文档,攻击者可以:
- 提取合法的Signature元素
- 伪造签名的断言
- 完全绕过GitLab SAML身份验证
结论
此攻击突显了如何组合往返攻击与命名空间混淆可导致GitLab的未授权访问。该漏洞源于不同XML解析器处理文档验证方式的不一致,允许攻击者操纵签名验证。
关键要点
- XML解析差异可能引入可利用的不一致
- 命名空间混淆可用于绕过签名验证
- 合法的签名WS-Federation元数据XML可被重新用于伪造断言
缓解措施
为防止此类攻击,确保使用相同库解析和验证签名XML文档。避免编组和解组不可信用户数据。这些漏洞已在GitLab社区版(CE)和企业版(EE)的17.9.2、17.8.5、17.7.7版本中修复。