Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
creation_date = "2026/02/04"
integration = ["kubernetes"]
maturity = "production"
updated_date = "2026/03/03"
updated_date = "2026/04/27"

[rule]
author = ["Elastic"]
Expand Down Expand Up @@ -68,25 +68,22 @@ FROM logs-kubernetes.audit_logs-* metadata _id, _index, _version
kubernetes.audit.verb in ("create", "update", "patch") and
`kubernetes.audit.annotations.authorization_k8s_io/decision` == "allow" and
kubernetes.audit.level == "RequestResponse" and kubernetes.audit.stage == "ResponseComplete" and
KQL("""kubernetes.audit.requestObject.rules.verbs:("*" or "escalate" or "bind" or "impersonate") or kubernetes.audit.requestObject.rules.resources:("clusterroles" or "clusterrolebindings" or "roles" or "rolebindings")""")
| KEEP
@timestamp,
data_stream.namespace,
`kubernetes.audit.annotations.authorization_k8s_io/decision`,
kubernetes.audit.level,
kubernetes.audit.objectRef.name,
kubernetes.audit.objectRef.resource,
kubernetes.audit.requestURI,
kubernetes.audit.responseStatus.code,
kubernetes.audit.sourceIPs,
kubernetes.audit.stage,
kubernetes.audit.user.groups,
kubernetes.audit.user.username,
user_agent.original,
kubernetes.audit.verb,
_id,
_index,
_version
not kubernetes.audit.sourceIPs in ("::1", "127.0.0.1") and
not (user.name like "eks:*" and kubernetes.audit.objectRef.name like "eks:*") and
not (user.name == "aksService" and event.action == "create" and kubernetes.audit.objectRef.name like "aks:*") and
not (user.name == "system:serviceaccount:kube-system:clusterrole-aggregation-controller" and kubernetes.audit.objectRef.name in ("admin", "edit") and event.action == "patch") and
(
// using requestObject
KQL(""" kubernetes.audit.requestObject.rules.verbs:("*" or "escalate" or "bind" or "impersonate") """) or
KQL(""" kubernetes.audit.requestObject.rules.verbs: ("*" or "create" or "patch" or "update") and kubernetes.audit.requestObject.rules.resources:("*" or "clusterroles" or "clusterrolebindings" or "roles" or "rolebindings" or "pods/exec" or "serviceaccounts/token" or "nodes/proxy" or "daemonsets") """) or
KQL(""" kubernetes.audit.requestObject.rules.verbs: ("*" or "get" or "list") and kubernetes.audit.requestObject.rules.resources:("*" or "secrets") """) or

// using responseObject
KQL(""" kubernetes.audit.responseObject.rules.verbs:("*" or "escalate" or "bind" or "impersonate") """) or
KQL(""" kubernetes.audit.responseObject.rules.verbs: ("*" or "create" or "patch" or "update") and kubernetes.audit.responseObject.rules.resources:("*" or "clusterroles" or "clusterrolebindings" or "roles" or "rolebindings" or "pods/exec" or "serviceaccounts/token" or "nodes/proxy" or "daemonsets") """) or
KQL(""" kubernetes.audit.responseObject.rules.verbs: ("*" or "get" or "list") and kubernetes.audit.responseObject.rules.resources:("*" or "secrets") """)
)
| keep user.name, user_agent.original, event.action, source.ip, kubernetes.audit.verb, kubernetes.audit.objectRef.resource, kubernetes.audit.objectRef.name, kubernetes.audit.requestURI, kubernetes.audit.user.username, kubernetes.audit.user.groups, `kubernetes.audit.annotations.authorization_k8s_io/decision`, event.original, _id, _version, _index, data_stream.namespace
'''

[[rule.threat]]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
[metadata]
creation_date = "2026/04/27"
integration = ["kubernetes"]
maturity = "production"
updated_date = "2026/04/27"

[rule]
author = ["Elastic"]
description = """
Flags an existing Role or ClusterRole being changed (patch or update) so the effective rules become cluster-admin-like:
wildcard on every API resource and wildcard on every verb. That is usually a deliberate privilege expansion, not a typo.
RequestResponse audit and the response body are required so the detection reads the merged role after apply; loopback
source IPs are ignored.
"""
false_positives = [
"""
Platform installers, GitOps controllers, and emergency break-glass roles sometimes ship or widen wildcard
ClusterRoles; correlate with change records and narrow by user or service account when baselined.
""",
]
from = "now-9m"
interval = "5m"
language = "esql"
license = "Elastic License v2"
name = "Kubernetes RBAC Wildcard Elevation on Existing Role"
note = """## Triage and analysis

### Investigating Kubernetes RBAC Wildcard Elevation on Existing Role

Someone patched or updated a Role or ClusterRole so the stored rules grant star verbs and star resources—near
cluster-admin breadth on that scope. Confirm the actor (user, group, impersonation), client, and non-loopback source
IP; then see who can bind that role.

### Possible investigation steps

- Diff the role YAML before and after; list RoleBindings and ClusterRoleBindings that reference it and which subjects
gained the widened access.
- In the same window, check secret reads, exec, and further RBAC changes from the same identity.

### False positive analysis

- Approved GitOps or vendor upgrades sometimes widen a known ClusterRole; allowlist stable automation when documented.

### Response and remediation

- Revert the role, drop unexpected bindings, rotate credentials for the actor, and block future wildcard RBAC outside
governed pipelines (policy-as-code, PR-only RBAC).
"""
references = [
"https://attack.mitre.org/techniques/T1098/006/",
"https://kubernetes.io/docs/reference/access-authn-authz/rbac/",
]
risk_score = 73
rule_id = "c8f4a2e1-9b3d-4c7e-8f2a-1d0e5b6c7a89"
severity = "high"
tags = [
"Data Source: Kubernetes",
"Domain: Kubernetes",
"Use Case: Threat Detection",
"Tactic: Privilege Escalation",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"
query = '''
from logs-kubernetes.audit_logs-* metadata _id, _index, _version
| where
kubernetes.audit.objectRef.resource in ("roles", "clusterroles") and
kubernetes.audit.verb in ("update", "patch") and
`kubernetes.audit.annotations.authorization_k8s_io/decision` == "allow" and
kubernetes.audit.level == "RequestResponse" and
kubernetes.audit.stage == "ResponseComplete" and
kubernetes.audit.sourceIPs is not null and
not kubernetes.audit.sourceIPs in ("::1", "127.0.0.1") and
KQL(""" kubernetes.audit.responseObject.rules.verbs:"*" and kubernetes.audit.responseObject.rules.resources:"*" """)
| keep user.name, user_agent.original, event.action, source.ip, kubernetes.audit.verb, kubernetes.audit.objectRef.resource, kubernetes.audit.objectRef.name, kubernetes.audit.requestURI, kubernetes.audit.user.username, kubernetes.audit.user.groups, `kubernetes.audit.annotations.authorization_k8s_io/decision`, event.original, _id, _version, _index, data_stream.namespace
'''

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1098"
name = "Account Manipulation"
reference = "https://attack.mitre.org/techniques/T1098/"

[[rule.threat.technique.subtechnique]]
id = "T1098.006"
name = "Additional Container Cluster Roles"
reference = "https://attack.mitre.org/techniques/T1098/006/"

[rule.threat.tactic]
id = "TA0004"
name = "Privilege Escalation"
reference = "https://attack.mitre.org/tactics/TA0004/"
Loading