AnalysisType: rule
RuleID: "Kubernetes.ClusterRoleBinding.Privileged"
DisplayName: "Kubernetes ClusterRoleBinding to Privileged Role"
Enabled: true
Filename: k8s_clusterrolebinding_privileged.py
LogTypes:
- Amazon.EKS.Audit
- Azure.MonitorActivity
- GCP.AuditLog
Tags:
- Kubernetes
- Privilege Escalation
- Persistence
- RBAC
- Unified Detection
Severity: Medium
Description: >
This detection monitors for ClusterRoleBindings being created that grant privileged cluster
roles like cluster-admin or system:masters. Attackers who gain initial cluster access often create
ClusterRoleBindings to escalate privileges and gain full control over all cluster resources and namespaces.
While some bindings to privileged roles are legitimate for cluster operators, unexpected bindings should be
investigated immediately as they may indicate compromise or insider threat.
Runbook: |
1. Review the subjects being granted the privileged role and immediately delete the ClusterRoleBinding if unauthorized
2. Identify all API operations by the creating user in the 2 hours before and after the alert and audit all actions by the subjects that were granted privileges
3. Review all ClusterRoleBindings across all clusters and search for other RBAC changes in the past 7 days
Reports:
Stratus Red Team:
- k8s.persistence.create-admin-clusterrole
- k8s.privilege-escalation.create-admin-clusterrole
MITRE ATT&CK:
- TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
- TA0003:T1098 # Persistence: Account Manipulation
Reference: >
- https://kubernetes.io/docs/reference/access-authn-authz/rbac/
- https://seifrajhi.github.io/blog/kubernetes-rbac-privilege-escalation-mitigation/#%EF%B8%8F-rolebinding-permissions-in-kubernetes-implications-and-safeguards
DedupPeriodMinutes: 60
SummaryAttributes:
- username
- name
- p_source_label
Tests:
- Name: EKS ClusterRoleBinding to cluster-admin
ExpectedResult: true
Log:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"verb": "create",
"user": {"username": "attacker@example.com"},
"sourceIPs": ["203.0.113.42"],
"userAgent": "kubectl/v1.28.0",
"objectRef": {
"resource": "clusterrolebindings",
"name": "backdoor-admin",
"apiGroup": "rbac.authorization.k8s.io",
"apiVersion": "v1"
},
"responseStatus": {"code": 201},
"requestObject": {
"kind": "ClusterRoleBinding",
"metadata": {"name": "backdoor-admin"},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "cluster-admin"
},
"subjects": [
{
"kind": "User",
"name": "attacker@example.com",
"apiGroup": "rbac.authorization.k8s.io"
}
]
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: AKS ClusterRoleBinding to system:masters
ExpectedResult: true
Log:
{
"p_log_type": "Azure.MonitorActivity",
"category": "kube-audit",
"operationName": "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read",
"properties": {
"log": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"verb\":\"create\",\"user\":{\"username\":\"malicious-user@example.com\"},\"sourceIPs\":[\"1.2.3.4\"],\"objectRef\":{\"resource\":\"clusterrolebindings\",\"name\":\"privilege-escalation\",\"apiGroup\":\"rbac.authorization.k8s.io\"},\"responseStatus\":{\"code\":201},\"requestObject\":{\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"name\":\"privilege-escalation\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"system:masters\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"malicious-sa\",\"namespace\":\"default\"}]}}"
},
"p_source_label": "aks-cluster"
}
- Name: GKE ClusterRoleBinding to admin role
ExpectedResult: true
Log:
{
"protoPayload": {
"authenticationInfo": {"principalEmail": "user@company.com"},
"authorizationInfo": [{
"granted": true,
"permission": "io.k8s.rbac.v1.clusterrolebindings.create",
"resource": "rbac.authorization.k8s.io/v1/clusterrolebindings/admin-binding"
}],
"methodName": "io.k8s.rbac.v1.clusterrolebindings.create",
"requestMetadata": {"callerIP": "8.8.8.8"},
"resourceName": "rbac.authorization.k8s.io/v1/clusterrolebindings/admin-binding",
"serviceName": "k8s.io",
"request": {
"kind": "ClusterRoleBinding",
"metadata": {"name": "admin-binding"},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "admin"
},
"subjects": [
{
"kind": "User",
"name": "user@company.com"
}
]
}
},
"resource": {
"type": "k8s_cluster",
"labels": {"project_id": "test-project"}
},
"p_log_type": "GCP.AuditLog",
"p_source_label": "gke-cluster"
}
- Name: EKS ClusterRoleBinding to controller-manager role
ExpectedResult: true
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "suspicious-user@example.com"},
"objectRef": {
"resource": "clusterrolebindings",
"name": "controller-access",
"apiGroup": "rbac.authorization.k8s.io"
},
"responseStatus": {"code": 201},
"requestObject": {
"roleRef": {
"kind": "ClusterRole",
"name": "system:kube-controller-manager"
},
"subjects": [
{
"kind": "ServiceAccount",
"name": "attacker-sa",
"namespace": "default"
}
]
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: ClusterRoleBinding to non-privileged role
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "clusterrolebindings",
"name": "view-binding",
"apiGroup": "rbac.authorization.k8s.io"
},
"responseStatus": {"code": 201},
"requestObject": {
"roleRef": {
"kind": "ClusterRole",
"name": "view"
},
"subjects": [
{
"kind": "User",
"name": "readonly-user@example.com"
}
]
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: RoleBinding (not ClusterRoleBinding)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "rolebindings",
"namespace": "default",
"name": "admin-binding",
"apiGroup": "rbac.authorization.k8s.io"
},
"responseStatus": {"code": 201},
"requestObject": {
"roleRef": {
"kind": "ClusterRole",
"name": "cluster-admin"
}
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: System principal creating binding (excluded)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "system:serviceaccount:kube-system:controller"},
"objectRef": {
"resource": "clusterrolebindings",
"name": "system-binding"
},
"responseStatus": {"code": 201},
"requestObject": {
"roleRef": {
"kind": "ClusterRole",
"name": "cluster-admin"
}
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: Failed request (excluded)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "attacker@example.com"},
"objectRef": {
"resource": "clusterrolebindings",
"name": "backdoor"
},
"responseStatus": {"code": 403},
"requestObject": {
"roleRef": {
"kind": "ClusterRole",
"name": "cluster-admin"
}
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: Reading ClusterRoleBinding (not creating)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "get",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "clusterrolebindings",
"name": "admin-binding"
},
"responseStatus": {"code": 200},
"p_log_type": "Amazon.EKS.Audit"
}
# ------ paired body: k8s_clusterrolebinding_privileged.py ------
from panther_base_helpers import deep_get
from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context
PRIVILEGED_CLUSTER_ROLES = {
"cluster-admin", # Full cluster control
"system:masters", # Superuser group
"admin", # Namespace admin capabilities
"system:kube-controller-manager", # Controller manager privileges
"system:kube-scheduler", # Scheduler privileges
}
def rule(event):
verb = event.udm("verb")
resource = event.udm("resource")
username = event.udm("username")
response_status = event.udm("responseStatus")
# Only check ClusterRoleBinding creation events
if verb != "create" or resource != "clusterrolebindings":
return False
# Skip failed requests
if is_failed_request(response_status):
return False
# Exclude system principals to reduce false positives from legitimate operators
if is_system_principal(username):
return False
# Check if binding references a privileged cluster role
request_object = event.udm("requestObject") or {}
role_name = deep_get(request_object, "roleRef", "name", default="")
if role_name in PRIVILEGED_CLUSTER_ROLES:
return True
return False
def title(event):
username = event.udm("username") or "<UNKNOWN_USER>"
name = event.udm("name") or "<UNKNOWN_BINDING>"
request_object = event.udm("requestObject") or {}
role_ref = request_object.get("roleRef", {})
role_name = role_ref.get("name", "<UNKNOWN_ROLE>")
# Extract subject information
subjects = request_object.get("subjects", [])
subject_names = []
for subject in subjects:
subject_type = subject.get("kind", "")
subject_name = subject.get("name", "")
if subject_type and subject_name:
subject_names.append(f"{subject_type}:{subject_name}")
subjects_str = ", ".join(subject_names) if subject_names else "<UNKNOWN_SUBJECTS>"
return (
f"[{username}] created ClusterRoleBinding [{name}] granting [{role_name}] "
f"to [{subjects_str}]"
)
def dedup(event):
username = event.udm("username") or "<UNKNOWN_USER>"
name = event.udm("name") or "<UNKNOWN_BINDING>"
return f"k8s_clusterrolebinding_{username}_{name}"
def severity(event):
"""Increase severity for cluster-admin and system:masters roles."""
request_object = event.udm("requestObject") or {}
role_ref = request_object.get("roleRef", {})
role_name = role_ref.get("name", "")
# High for cluster-admin and system:masters (full cluster control)
if role_name in {"cluster-admin", "system:masters"}:
return "HIGH"
# Medium for other privileged system roles
return "DEFAULT"
def alert_context(event):
request_object = event.udm("requestObject") or {}
role_ref = request_object.get("roleRef", {})
subjects = request_object.get("subjects", [])
return k8s_alert_context(
event,
extra_fields={
"binding_name": event.udm("name"),
"role_name": role_ref.get("name"),
"role_kind": role_ref.get("kind"),
"subjects": subjects,
},
)