Back to Intelligence

CVE-2026-120984: Keycloak Authorization Services RCE — Detection and Remediation Guide

SA
Security Arsenal Team
November 7, 2026
12 min read

On the NVD feed this week: CVE-2026-120984, a critical CVSS 9.1 vulnerability in Keycloak, the open-source identity and access management (IAM) platform that sits in front of authentication for a huge portion of enterprise and government infrastructure. The flaw lives in the client management component and, if exploited, delivers arbitrary code execution on the Keycloak server itself.

Let that sink in. Keycloak is the trust anchor of your SSO, your token issuance, your identity federation. An attacker who executes code on the Keycloak host doesn't just get a shell — they get the keys to every identity, every realm, every client secret, and every signed token flowing through the environment. This is a crown-jewel compromise scenario.

The one mitigating factor: exploitation requires an authenticated attacker with client management permissions. That's not a low bar for an external actor — but it is a trivially reachable bar for a malicious insider, a compromised admin account, or a delegated realm administrator whose credentials were phished. In my experience, IAM admin credentials are among the most commonly stolen and least monitored credentials in the enterprise. Treat this as urgent.

Technical Analysis

Vulnerability Overview

AttributeDetail
CVECVE-2026-120984
CVSS 3.x Score9.1 (Critical)
Attack VectorNetwork (AV:N)
Affected ProductKeycloak (Red Hat build of Keycloak / upstream Keycloak)
Affected ComponentClient management / authorization services setup
Privileges RequiredAuthenticated with client management role (e.g., manage-clients, realm admin)
ImpactRemote code execution on the Keycloak server
SourceNVD — CVE-2026-120984

Root Cause

The flaw is a stale internal session attribute during the client setup workflow. When a client is created or imported with authorization services enabled, Keycloak fails to properly clear an internal session attribute used during the setup process. That residual state allows an attacker who can manage clients to bypass the security controls that would normally gate policy upload — specifically, the upload of a JavaScript policy to the client's authorization configuration.

Keycloak's authorization services support user-defined JavaScript-based policies (JS policies) for fine-grained access decisions. By design, the ability to upload JS policies is tightly restricted because that JavaScript executes server-side when an authorization request is evaluated. The session-attribute bug punches a hole in that restriction: the attacker uploads a malicious JS policy, then triggers any authorization request against the affected client, and the policy is evaluated on the server — with the attacker's code running in the Keycloak JVM context.

Attack Chain (Defender's View)

  1. Access: Attacker authenticates to Keycloak (Admin Console or Admin REST API) with an account holding client management privileges in a realm.
  2. Setup abuse: Attacker creates or imports a client with authorization services enabled, exploiting the improperly cleared session attribute.
  3. Policy upload: Attacker uploads a crafted JavaScript policy to the client's authorization resource server configuration.
  4. Trigger: Any authorization/permission evaluation against that client causes Keycloak to execute the malicious JavaScript on the server.
  5. Impact: Arbitrary code execution in the Keycloak server process — typically leading to a reverse shell, credential/token theft from realm configuration and caches, or lateral movement from the IAM tier.

Post-exploitation on a Keycloak host predictably looks like: the Java process spawning a shell, outbound egress from the Keycloak host to attacker infrastructure, dumping of realm secrets and client credentials, or deployment of a persistence mechanism on the host.

Exploitation Status

At time of publication, CVE-2026-120984 is documented in NVD with no confirmed public reports of in-the-wild exploitation and not yet listed in CISA's Known Exploited Vulnerabilities catalog. That said, Keycloak's ubiquity, the clarity of the attack path, and the fact that exploitation rides on legitimate authenticated API calls make this a high-value target for both red teams and real adversaries. Do not wait for KEV inclusion to act.

Detection & Response

Detection for this vulnerability operates on two planes: (1) the policy-upload event itself — an authenticated admin API call that is your best pre-exploitation signal — and (2) post-exploitation behavior on the Keycloak host, where the JVM spawning a shell is an unambiguous alarm.

Sigma Rules

YAML
---
title: Keycloak Java Process Spawning Shell or Scripting Interpreter
id: 8f2c1d4a-6b3e-4a9f-b7d2-5e1c9a0f3b8d
status: experimental
description: Detects the Keycloak JVM spawning a shell or scripting interpreter, consistent with post-exploitation activity following CVE-2026-120984 malicious JS policy execution on the server.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-120984
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/01/15
tags:
  - attack.execution
  - attack.t1059
  - attack.t1190
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentCommandLine|contains:
      - 'keycloak'
      - 'kc.sh'
      - 'quarkus'
  selection_parent_java:
    ParentImage|endswith:
      - '/java'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
      - '/python'
      - '/python3'
      - '/perl'
  condition: (selection_parent or selection_parent_java) and selection_child
falsepositives:
  - Keycloak startup scripts invoking curl for health checks (constrain parent cmdline to the long-running JVM, not kc.sh wrappers)
  - Automated tooling co-located on the host
level: high
---
title: Keycloak Authorization JavaScript Policy Upload via Admin REST API
id: 3d7a9e1b-2c4f-4d8a-a1e6-9b0c5f7d2e4a
status: experimental
description: Detects POST/PUT requests to Keycloak Admin REST endpoints used to upload JavaScript authorization policies to a client's resource server, the core exploitation action of CVE-2026-120984.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-120984
  - https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/01/15
tags:
  - attack.initial_access
  - attack.t1190
  - attack.t1059.007
logsource:
  category: webserver
  product: linux
detection:
  selection_uri:
    cs-uri|contains:
      - '/authz/resource-server/policy/js'
      - '/authz/resource-server/policy'
  selection_method:
    cs-method:
      - 'POST'
      - 'PUT'
  selection_path:
    cs-uri|contains:
      - '/admin/realms/'
  condition: selection_uri and selection_method and selection_path
falsepositives:
  - Legitimate administrators uploading JS policies as part of approved authorization configuration (tune by approved admin source IPs and correlate with change tickets)
level: medium
---
title: Keycloak Client Created with Authorization Services Enabled
id: 5b1e8c3d-7f2a-4b6d-c9e1-3a8f0d5b2c6e
status: experimental
description: Detects creation or import of a Keycloak client with authorization services enabled via the Admin REST API, the first stage of the CVE-2026-120984 attack chain where the session attribute bug is triggered.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-120984
  - https://attack.mitre.org/techniques/T1136/
author: Security Arsenal
date: 2026/01/15
tags:
  - attack.persistence
  - attack.t1136
logsource:
  category: webserver
  product: linux
detection:
  selection_uri:
    cs-uri|contains:
      - '/admin/realms/'
      - '/clients'
  selection_method:
    cs-method: 'POST'
  selection_status:
    sc-status:
      - 201
      - 200
  condition: selection_uri and selection_method and selection_status
falsepositives:
  - Normal client provisioning by realm administrators or IaC pipelines (whitelist automation service accounts and known provisioning source IPs)
level: low

Analyst note: The two webserver rules assume you are shipping Keycloak's HTTP access logs (enable quarkus.http.access-log.enabled=true) or reverse-proxy logs into your SIEM. If you aren't logging Keycloak admin-plane traffic today, you are blind to exactly the tier attackers want to live in. Fix that this week.

KQL (Microsoft Sentinel / Defender)

Keycloak admin API traffic reaches Sentinel via Syslog/CEF ingestion from your reverse proxy or from Keycloak access logs. This query hunts for JavaScript policy uploads and client creation events, then flags any that originate from source IPs not on an approved-admin list.

KQL — Microsoft Sentinel / Defender
// Hunt: Keycloak JS policy uploads and client creation from non-approved sources
let ApprovedAdminIPs = dynamic(["10.0.0.10", "10.0.0.11"]); // replace with your IAM admin/jump-host IPs
union isfuzzy=true
    (CommonSecurityLog
    | where RequestURL has_any ("/authz/resource-server/policy/js", "/authz/resource-server/policy")
    | where HttpMethod in~ ("POST", "PUT")
    | project TimeGenerated, SourceIP, RequestURL, HttpMethod, RequestContext, DeviceVendor
    | extend EventType = "JS Policy Upload"),
    (Syslog
    | where SyslogMessage has_any ("/authz/resource-server/policy/js", "/admin/realms/") and SyslogMessage has "clients"
    | where SyslogMessage has_any ("POST", "PUT")
    | project TimeGenerated, HostIP, SyslogMessage, ProcessName
    | extend EventType = "Keycloak Admin Client/Policy Operation")
| where SourceIP !in (ApprovedAdminIPs) or isnull(SourceIP)
| sort by TimeGenerated desc

And a host-level hunt for post-exploitation on the Keycloak server itself — the JVM spawning interpreters or shells:

KQL — Microsoft Sentinel / Defender
// Hunt: Keycloak/JVM process spawning shells or downloaders (post-RCE behavior)
DeviceProcessEvents
| where InitiatingProcessFileName =~ "java" or InitiatingProcessCommandLine has_any ("keycloak", "quarkus")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "nc", "ncat", "python", "python3", "perl")
    or ProcessCommandLine has_any ("/bin/sh", "/bin/bash", "curl ", "wget ", "nc ")
| project TimeGenerated, DeviceName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, AccountName, RemoteIP
| sort by TimeGenerated desc

Velociraptor VQL

For live-response on Keycloak hosts, hunt for the JVM with suspicious children and enumerate recent network connections off the host — an IAM server has a predictable egress profile, so unknown outbound connections are high-signal.

VQL — Velociraptor
-- Keycloak post-exploitation hunt: JVM child processes and unexpected egress
LET procs = SELECT Pid, Ppid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE CommandLine =~ '(?i)(/bin/(ba)?sh|curl |wget |nc |ncat |python|perl)'

SELECT Pid, Ppid, Name, CommandLine, Username, CreateTime
FROM procs
WHERE Name =~ '(?i)^(sh|bash|dash|curl|wget|nc|ncat|python.*|perl)$'

-- Unexpected outbound connections from the Keycloak host
SELECT Pid, Name, Laddr, Lport, Raddr, Rport, Status
FROM netstat()
WHERE Status =~ 'ESTABLISHED'
  AND Rport NOT IN (443, 8443, 5432, 389, 636, 53, 8080, 9990)  -- tune to your DB/LDAP/mgmt ports
  AND Raddr !~ '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)'

Verification & Hardening Script

Run this against your Keycloak deployment to enumerate clients with authorization services enabled, inventory existing JS policies (any JS policy is a potential implant — audit every one), and check version posture.

Bash / Shell
#!/bin/bash
# CVE-2026-120984 verification: enumerate JS policies and authz-enabled clients across realms
# Requires: kcadm.sh (Keycloak Admin CLI) authenticated as a realm admin with view rights
set -euo pipefail

KCADM="/opt/keycloak/bin/kcadm.sh"
KC_URL="https://keycloak.example.com"

echo "=== Keycloak server version ==="
curl -sk "${KC_URL}/realms/master/.well-known/openid-configuration" >/dev/null && echo "Server reachable."
# Authenticate (use a read-only admin service account where possible)
${KCADM} config credentials --server "${KC_URL}" --realm master --user svc_audit --password "${KC_AUDIT_PW}"

echo "=== Iterating realms: clients with authorizationServicesEnabled=true ==="
for REALM in $(${KCADM} get realms --fields realm --format csv --noquotes); do
  echo "--- Realm: ${REALM} ---"
  ${KCADM} get clients -r "${REALM}" --fields id,clientId,authorizationServicesEnabled \
    | grep -B2 '"authorizationServicesEnabled" : true' || echo "  (none with authz enabled)"
done

echo "=== Auditing ALL JavaScript policies per authz-enabled client ==="
for REALM in $(${KCADM} get realms --fields realm --format csv --noquotes); do
  for CID in $(${KCADM} get clients -r "${REALM}" -q authorizationServicesEnabled=true --fields id --format csv --noquotes 2>/dev/null); do
    echo "[${REALM}] client uuid ${CID} — JS policies:"
    ${KCADM} get "clients/${CID}/authz/resource-server/policy" -r "${REALM}" \
      | grep -E '"type" : "js"|"name"' || echo "  no JS policies"
  done
done

echo "=== Checking host for signs of post-exploitation: JVM-spawned shells in process tree ==="
ps -eo pid,ppid,comm,args | awk '$3=="sh"||$3=="bash"||$3=="dash" {print}'

echo "=== Keycloak admin event review hint ==="
echo "Enable and review Admin Events (Events > Config > Save Admin Events = ON) in every realm;"
echo "filter for CREATE/UPDATE operations on CLIENT and on authorization policy resources."

Important: Any JavaScript policy you discover that your team did not author and approve through change control should be treated as a potential implant — preserve it as forensic evidence before deleting, capture the client and realm configuration, and initiate IR scoping on the host.

Remediation

  1. Patch immediately. Upgrade Keycloak to the fixed release identified in the vendor advisory. Consult the NVD entry for CVE-2026-120984 and the Keycloak project's security advisories (keycloak.org and the project's GitHub Security Advisories) for the exact fixed version for your release train. Red Hat customers running the Red Hat build of Keycloak (RHBK) should apply the corresponding RHSA errata as published. Given CVSS 9.1 and a network-reachable path, treat this as an emergency change — same-week SLA, not next-quarter.

  2. Audit who has client management permissions — today. The exploitation prerequisite is the manage-clients (or realm admin) role. Enumerate every principal with that role across every realm and ruthlessly reduce it. Separate duties: client administration should not be casually bundled with read roles for service accounts or delegated admins.

  3. Inventory and attest all JavaScript authorization policies. As scripted above, enumerate every JS policy in every realm. Require a change ticket or code-commit reference for each one. Unattributed JS policy = incident.

  4. If you cannot patch immediately — interim mitigations:

    • Restrict network access to the Admin Console and Admin REST API (/admin/, /realms/*/clients*) to a dedicated admin network or jump hosts only. This alone breaks the attack path for most adversaries.
    • Where feasible, avoid enabling authorization services on new clients until patched; review recently created/imported clients with authz enabled.
    • Disable or tightly govern JS policy upload capability in line with your change-control process.
  5. Turn on and ship Admin Events. In each realm: Realm Settings → Events → Save Events = ON and Admin Events Settings → Save Events = ON, with includeRepresentation enabled. Forward these logs to your SIEM. Client creation and policy upload operations become queryable, alertable audit records instead of invisible API calls.

  6. Monitor the Keycloak host like a production workload, because it is one. EDR on the Keycloak hosts, egress filtering with an allowlist, and alerting on any JVM child process (rules above). The Keycloak host should never spawn bash, curl, or python outside of controlled maintenance windows.

  7. Rotate if suspicious. If you find evidence of a malicious JS policy or unauthorized client creation: isolate the host, preserve forensic images, rotate all realm client secrets, signing keys, and admin credentials, and assume any token issued during the exposure window is compromised.

Conclusion

CVE-2026-120984 is a reminder that IAM infrastructure is attack-surface infrastructure. A stale session attribute in a setup workflow turned a delegated admin permission into full server-side code execution. The defensive plays are unglamorous but decisive: patch the server, shrink the admin role population, gate the admin plane behind the network, inventory your JS policies, and put eyes on every admin API call. If your Keycloak host can execute attacker JavaScript and nobody would notice — that's the real incident.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.