SAML轮盘赌:黑客必胜之道

本文深入分析了如何通过组合往返攻击和命名空间混淆技术,利用ruby-saml库实现GitLab企业版的未授权管理员访问。详细探讨了XML解析差异导致的签名验证绕过机制,并提供了完整的技术实现细节。

SAML轮盘赌:黑客必胜 | PortSwigger研究

引言

在这篇文章中,我们将展示如何通过链接往返攻击和命名空间混淆,利用ruby-saml库在GitLab企业版上实现未授权的管理员访问。

在研究过程中,GitHub独立发现并修补了我们的漏洞。然而,他们的披露省略了关键技术细节,包括特定的变异方式以及如何在无需认证的情况下利用它。

我们相信分享这些攻击的完整细节对于提高安全性至关重要,通过赋予每个人识别、缓解和有效防御此类威胁所需的知识。

这项研究始于我们偶然发现Juho Forsén的一篇关于XML往返漏洞的精彩文章。最初的好奇心很快演变成对SAML复杂性的深入探索,结果超出了我们最初的预期。我们花了数月时间探索各种往返攻击,目标是在黑帽大会上展示我们的发现。然而,运气使然,我们与Alexander Tan(ahacker1)发生了研究碰撞,导致我们的发现在提交之前就被修补。尽管有这个转折,我们相信这项工作仍然值得分享,虽然今年可能不会登上黑帽大会,但我们希望您能发现它同样引人入胜。

往返攻击基础

SAML库通常解析XML文档,将其存储为字符串,然后重新解析。在Ruby-SAML中,这个过程涉及两个不同的解析器:REXML用于解析文档和验证签名,而Nokogiri用于访问属性。如果在此过程中发生任何变异,文档在第二次解析时可能不完全相同。

为了安全授权,文档必须被一致地解析和序列化;否则可能会出现结构不一致。这些不一致可以被利用来进行往返攻击。通过利用XML注释和CDATA部分,攻击者可以在变异过程中操纵文档结构,绕过签名验证,并通过冒充其他用户身份 effectively 获得未授权访问。

Ruby SAML/REXML上的往返攻击

为了便于测试,我们开发了一个测试平台来识别往返漏洞并有效评估多个SAML库。我首先检查了文档类型定义(DOCTYPE),因为过去曾发现过类似的漏洞。我的初始方法侧重于分析XML实体如何被解析,因此我在该领域进行了测试。

在Juho的原始发现中,符号声明被用来引入引号解释的不一致性。在此基础上,我调查了是否有任何额外的漏洞被忽视。经过广泛测试,我发现可以在SYSTEM标识符中引入变异。

在文档的初始解析过程中,遇到的第一个标签是原始的"assertion":

1
<!DOCTYPE assertion SYSTEM '"><!--'

然而,重新解析文档时,结果完全改变,现在反映了攻击者的"assertion":

1
<!DOCTYPE assertion SYSTEM ""><!--">

如所示,单引号的系统标识符被转换为双引号。然而,由于标识符内部包含双引号,这改变了XML文档的语法,导致XML注释被处理,并产生完全不同的节点。我技术高超的同事Zak将这个变异精炼成更简化和有效的攻击向量:

1
<!DOCTYPE assertion SYSTEM '"><!--<assertion>

这个向量允许通过操纵文档和伪造断言来利用GitLab和任何其他使用Ruby-SAML库的应用程序,有效地使攻击者能够以任何用户身份登录。然而,这只是攻击的一部分。我的同事Zak将展示如何将其升级以在GitLab上实现未授权的管理员访问。

通过往返攻击在Gitlab上提升权限

理解漏洞

GitLab依赖Ruby-SAML库进行SAML认证。然而,要实现未授权访问,我们需要仔细查看验证过程,因为它在攻击中起着关键作用。

在往返发生之前,库会验证SAMLResponse是否包含嵌入在文档中的有效证书。这是通过计算证书的哈希值并与服务器上存储的指纹进行比较来完成的。之后,这个证书用于验证签名。请记住,签名是此攻击的一个关键方面,因为它允许在没有组织凭证的情况下完全接管账户。

签名验证过程

一旦从SAMLResponse中提取出证书,实际的签名验证过程就开始了。首先,文档从其内存表示转换回XML格式。这就是Gareth的往返攻击发挥作用的地方。在这个阶段,库忽略攻击者断言并继续验证原始元素的签名。

如果攻击者以绕过签名验证的方式伪造断言元素,额外的安全检查就会启动。最关键检查包括:

  • 签名元素的ID应在两个文档中匹配
  • 规范化属性(规范化XML文档)必须在两个版本中相同

然而,所有其他验证检查都在攻击者断言上操作,而不是原始签名文档。这允许攻击者任意修改验证字段而不破坏签名验证过程。

克服XML模式限制

伪造签名XML文档的一个挑战是XML模式验证是使用Nokogiri与预定义模式文件执行的。这提出了一个限制:攻击者要伪造有效的签名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元素:

1
//ds:Signature

这里,ds指的是XML命名空间。通常,命名空间防止元素名称冲突,但我们利用了XPath搜索中命名空间解释方式的差异。

考虑以下场景:

1
2
3
4
5
<!DOCTYPE root [
<!ATTLIST Signature xmlns CDATA "http://www.w3.org/2000/09/xmldsig#">
]>
<Signature>FAKE</Signature>
<Signature>REAL</Signature>

第一个Signature元素缺少直接的命名空间声明(xmlns=“http://www.w3.org/2000/09/xmldsig#")。相反,我们使用XML Doctype技巧:

安全专家通常关注XXE攻击中的!ENTITY声明,但!ATTLIST声明也可以用于利用。!ATTLIST定义了Signature元素并为其分配命名空间属性。REXML和Nokogiri都支持基于doctype的命名空间声明,但REXML有一个关键缺陷:

  • XML标准禁止具有相同名称的重复属性
  • 然而,REXML在doctype声明中忽略此限制

这允许攻击者定义两个冲突的命名空间属性,其中第二个覆盖第一个。

结果,REXML读取FAKE摘要值,而Nokogiri读取REAL摘要值。

利用策略

要利用此差异:

  1. 创建两个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文档可能具有挑战性。幸运的是,身份提供者(IdPs)默认为每个租户静默支持单点登录协议:WS-Federation。WS-Federation提供签名的元数据XML端点,例如:

1
https://login.microsoftonline.com/contoso.onmicrosoft.com...

联邦元数据文档对任何未授权用户公开可访问——所需要的只是应用程序的唯一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版本中修复。

请确保在X(前Twitter)和Bluesky上关注我们,并加入官方PortSwigger Discord以保持更新!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计