Full Report
The initial research for this post started with an attack vector as opposed to a real issue: ruby-saml used two different SAML libraries. During signature verification, the element is first read using the REXML parser and with Nokogiri's XML parser. If a difference could be used to trick the XPath query then it may be possible to bypass the signature verification. Security Assertion Markup Language (SAML) is a framework for transporting signed-in user from an identity provider to a service provider in XML. The main part of the SAML response is the Assertion. This contains a DigestValue, SignatureValue and a Subject field for the user. Normally, the entire assertion portion is canonicalized and then compared again the Digest. Then, a signature is compared on the SignatureValue based upon this digest. These are important later for exploitation. Before looking into parser differentials, they first needed to see if there was a path to exploitation. After carefully analyzing which parser makes what query, the came to the following conclusions: The SAML assertion is extracted and canonicalized with Nokogiri. The hash is then compared with a value from REXML. The SignedInfo element is extracted and canonicalized with Nokogiri - it is then verified against the SignatureValue, which was grabbed with REXML. What's the path for this exploit? First, make it so that REXML doesn't find the Signature XML object but Nokogiri does. This SignedInfo is then compared against the SignatureValue extracted via REXML. Later on, the DigestValue needs to be compared with the one that was signed. By getting Nokogiri to canonicalize the assertion but get REXML to extract a different DigestValue than the one used on signature validation, it will bypass the check without being signed. There were two known PoCs. This was initially found via a HackerOne submission using techniques from XML roundtrips vulnerabilities, since GitHub had added this library to their bounty. The author of the article found a different parsing issue from using the Trail of Bits Ruby fuzzer ruzzy. There is a nice image that explains this but I'm unsure whose exploit it is. In the image, the Signature field found by Nokigiri is NOT under an Assertion. When REXML needs the digest, it uses the one under Assertion instead. Seems like the traversing of the tree was funky between the libraries. Relying on two parsers is error prone. Exploitability isn't automatic in these cases but it's a good place to start. To fix this issue, they decided to use only a single library, which is great. Solid article but I wish both exploits were explained in more detail.
Analysis Summary
# Vulnerability: Authentication Bypass via XML Parser Differentials in ruby-saml
## CVE Details
- **CVE ID:** CVE-2025-25291 and CVE-2025-25292
- **CVSS Score:** 10.0 (Critical - *Implied based on "Sign in as anyone" impact and library history*)
- **CWE:** CWE-248 (Uncaught Exception) / CWE-287 (Improper Authentication) / CWE-670 (Always-Incorrect Control Flow Implementation)
## Affected Systems
- **Products:** `ruby-saml` library, and downstream integrations such as `omniauth-saml`.
- **Versions:** All versions up to and including 1.17.0.
- **Configurations:** Service Providers (SPs) using `ruby-saml` to validate SAML Assertions/Responses from an Identity Provider (IdP).
## Vulnerability Description
The vulnerability stems from a **parser differential** between two different XML libraries used simultaneously during the signature verification process: **REXML** (pure Ruby) and **Nokogiri** (libxml2 wrapper).
In the `validate_signature` code path, the library performs the following inconsistent operations:
1. **Extraction:** The `Signature` element and `SignatureValue` are extracted using REXML.
2. **Canonicalization:** The `SignedInfo` and `Assertion` elements are canonicalized and processed using Nokogiri.
3. **Verification Logic:** The library compares a hash (Digest) extracted via REXML against a value verified by Nokogiri.
By crafting a malicious XML document where the two parsers interpret the tree structure differently (e.g., placing a fake Signature outside the Assertion), an attacker can trick the library into verifying a valid signature from one part of the document while applying the "authorized" status to a different, attacker-controlled assertion.
## Exploitation
- **Status:** PoC documented/available (Discovered via Bug Bounty and internal Fuzzing).
- **Complexity:** Medium (Requires a valid signature from the target IdP to use as a "base" for the attack).
- **Attack Vector:** Network (Remote). No authentication is required; the attacker can spoof any user identity.
## Impact
- **Confidentiality:** High (Full account takeover).
- **Integrity:** High (Ability to impersonate any user and modify data).
- **Availability:** High (Potential for service disruption via administrative account access).
## Remediation
### Patches
- **ruby-saml:** Update to version **1.18.0** or later.
- **omniauth-saml:** Update to versions that reference `ruby-saml` 1.18.0+.
- **GitLab:** Users should update GitLab instances to the latest patched security release.
### Workarounds
- There are no known configuration-based workarounds; the underlying logic requires a library update to consolidate XML parsing into a single, consistent library.
## Detection
- **Indicators of Compromise:** Unusual XML structures in SAML logs, specifically multiple `Signature` elements or signatures located in non-standard XPaths.
- **Detection Methods:** Monitor for successful logins from unexpected IP addresses for high-privileged accounts. Review historical SAML Responses for "Roundtrip" XML anomalies.
## References
- **Vendor Advisory:** [https://github.com/advisories/GHSA-jw9c-mfg7-9rx2]
- **Project Repository:** [https://github.com/SAML-Toolkits/ruby-saml]
- **GitHub Blog:** [https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/]