Detection rules › Panther

Panther rules: k8s

Kubernetes Admission Controller Webhook Created

#
Severity
medium
Group by
username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Persistence, Credential Access, Collection, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for creation of MutatingWebhookConfiguration or ValidatingWebhookConfiguration resources. Admission controller webhooks can intercept all API requests to the Kubernetes API server, allowing attackers to inspect, modify, or block any resource creation or modification. This provides powerful capabilities for persistence (modifying deployments to inject backdoors), credential theft (intercepting secrets), and reconnaissance (enumerating all cluster activity).

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context

# Admission controller webhook resource types
WEBHOOK_RESOURCES = {
    "mutatingwebhookconfigurations",
    "validatingwebhookconfigurations",
}


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    response_status = event.udm("responseStatus")

    # Only check webhook creation events
    if verb != "create":
        return False

    if resource not in WEBHOOK_RESOURCES:
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude noise from cluster maintenance
    username = event.udm("username")
    if is_system_principal(username):
        return False

    # Alert on any admission controller webhook creation
    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "webhook"
    name = event.udm("name") or "<UNKNOWN_NAME>"

    webhook_type = "Mutating" if "mutating" in resource.lower() else "Validating"

    return f"[{username}] created {webhook_type} admission controller webhook [{name}] "


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    return f"k8s_admission_webhook_{username}"


def severity(event):
    """Increase severity for webhooks that intercept all resources."""
    webhooks = event.udm("webhooks") or []

    for webhook in webhooks:
        rules = webhook.get("rules", [])
        for rule_config in rules:
            resources = rule_config.get("resources", [])
            api_groups = rule_config.get("apiGroups", [])

            # Check for wildcard rules that intercept everything
            if "*" in resources or "*" in api_groups:
                return "HIGH"

    return "MEDIUM"


def alert_context(event):
    webhooks = event.udm("webhooks") or []

    # Extract webhook details
    webhook_details = []
    for webhook in webhooks:
        client_config = webhook.get("clientConfig", {})
        webhook_details.append(
            {
                "name": webhook.get("name"),
                "url": client_config.get("url"),
                "service": client_config.get("service"),
                "failure_policy": webhook.get("failurePolicy"),
                "rules": webhook.get("rules", []),
            }
        )

    return k8s_alert_context(
        event,
        extra_fields={
            "webhook_name": event.udm("name"),
            "webhook_type": event.udm("resource"),
            "webhooks": webhook_details,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.AdmissionController.Created"
DisplayName: "Kubernetes Admission Controller Webhook Created"
Enabled: true
Filename: k8s_admission_controller_created.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Persistence
  - Credential Access
  - Collection
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for creation of MutatingWebhookConfiguration or ValidatingWebhookConfiguration
  resources. Admission controller webhooks can intercept all API requests to the Kubernetes API server,
  allowing attackers to inspect, modify, or block any resource creation or modification. This provides
  powerful capabilities for persistence (modifying deployments to inject backdoors), credential theft
  (intercepting secrets), and reconnaissance (enumerating all cluster activity).
Runbook: |
  1. Review the webhook configuration details including the target webhook service URL and failure policy
  2. Identify all API operations performed by the username in the 48 hours before webhook creation to establish intent
  3. Search for other webhook configurations or suspicious API activity from this user across all clusters in the past 7 days
Reports:
  MITRE ATT&CK:
    - TA0003:T1546 # Persistence: Event Triggered Execution
    - TA0006:T1552 # Credential Access: Unsecured Credentials
    - TA0009:T1530 # Collection: Data from Cloud Storage Object
Reference: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is one of mutatingwebhookconfigurations, validatingwebhookconfigurations
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
resourcein
  • mutatingwebhookconfigurations
  • validatingwebhookconfigurations
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"
verbeq
  • create
field:"verb" kind:eq value:"create"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review the webhook configuration details including the target webhook service URL and failure policy

2. Identify all API operations performed by the username in the 48 hours before webhook creation to establish intent

3. Search for other webhook configurations or suspicious API activity from this user across all clusters in the past 7 days

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "admissionregistration.k8s.io",
    "apiVersion": "v1",
    "name": "custom-mutator",
    "resource": "mutatingwebhookconfigurations"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "MutatingWebhookConfiguration",
    "metadata": {
      "name": "custom-mutator"
    },
    "webhooks": [
      {
        "clientConfig": {
          "url": "https://webhook.example.com/mutate"
        },
        "name": "mutate.example.com",
        "rules": [
          {
            "apiGroups": [
              "*"
            ],
            "apiVersions": [
              "*"
            ],
            "operations": [
              "CREATE",
              "UPDATE"
            ],
            "resources": [
              "*"
            ]
          }
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes All Secrets Dumped Across Namespaces

#
Status
Experimental
Severity
critical
Group by
username
Compliance
Stratus Red Team k8s.credential-access.dump-secrets
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Credential Access, Mass Exfiltration, Secrets Management, Unified Detection
Reference
stratus-red-team.cloud
Source
github.com/panther-labs/panther-analysis

This detection monitors for cluster-wide secret list operations that dump all secrets across all namespaces in a single API call. Attackers with list secrets permissions at the cluster level can trivially access every secret in the cluster using the LIST /api/v1/secrets API, exposing all credentials, tokens, and sensitive configuration data. This is a known attack technique documented by Stratus Red Team and represents mass credential theft.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Kuberneteslist-secrets: list secrets

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context

# System components that legitimately list all secrets cluster-wide
ALLOWED_SECRET_LISTERS = {
    "system:serviceaccount:kube-system:namespace-controller",
    "system:serviceaccount:kube-system:kube-state-metrics",
    "system:apiserver",
}


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username") or ""
    response_status = event.udm("responseStatus")
    request_uri = event.udm("requestURI") or ""

    # Only check secret list operations that are successful
    if verb != "list" or resource != "secrets" or is_failed_request(response_status):
        return False

    # Key indicator: namespace is empty (cluster-wide list, not namespaced)
    # and requestURI doesn't contain /namespaces/ in path
    if namespace or (request_uri and "/namespaces/" in request_uri):
        return False

    # Exclude system principals and specific components
    if is_system_principal(username) or username in ALLOWED_SECRET_LISTERS:
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    request_uri = event.udm("requestURI") or ""

    return f"[{username}] dumped all secrets across all namespaces (URI: {request_uri})"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    return f"k8s_secrets_dump_{username}"


def alert_context(event):
    request_uri = event.udm("requestURI") or ""

    # Extract query parameters if present (e.g., limit=500)
    query_params = {}
    if "?" in request_uri:
        query_string = request_uri.split("?", 1)[1]
        for param in query_string.split("&"):
            if "=" in param:
                key, value = param.split("=", 1)
                query_params[key] = value

    return k8s_alert_context(
        event,
        extra_fields={
            "request_uri": request_uri,
            "query_parameters": query_params,
            "operation": "list_all_secrets",
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Secrets.DumpAllNamespaces"
DisplayName: "Kubernetes All Secrets Dumped Across Namespaces"
Enabled: true
Status: Experimental
Filename: k8s_secrets_dump_all_namespaces.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Credential Access
  - Mass Exfiltration
  - Secrets Management
  - Unified Detection
Severity: Critical
Description: >
  This detection monitors for cluster-wide secret list operations that dump all secrets across all namespaces in a
  single API call. Attackers with list secrets permissions at the cluster level can trivially access every secret in
  the cluster using the LIST /api/v1/secrets API, exposing all credentials, tokens, and sensitive configuration data.
  This is a known attack technique documented by Stratus Red Team and represents mass credential theft.
Runbook: |
  1. Immediately investigate the user or service account performing this operation and determine if they are compromised
  2. Review what secrets exist in the cluster to assess the scope of credential exposure and rotate all sensitive credentials
  3. Search for other suspicious API activity by this user in the past 24 hours and check for data exfiltration or lateral movement attempts
Reports:
  Stratus Red Team:
    - k8s.credential-access.dump-secrets
  MITRE ATT&CK:
    - TA0006:T1552.007 # Credential Access: Unsecured Credentials - Container API
    - TA0009:T1530 # Collection: Data from Cloud Storage Object
Reference: https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.credential-access.dump-secrets/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is list
  • resource is secrets
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • namespace is empty
  • any of:
    • requestURI is empty
    • requestURI does not contain /namespaces/
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • username is not one of system:serviceaccount:kube-system:namespace-controller, system:serviceaccount:kube-system:kube-state-metrics, system:apiserver

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
usernameis_not_null(no value, null check)excludes:username
usernameinsystem:apiserver, system:serviceaccount:kube-system:kube-state-metrics, system:serviceaccount:kube-system:namespace-controllerexcludes:username field:"username" value:"system:apiserver" field:"username" value:"system:serviceaccount:kube-system:kube-state-metrics" field:"username" value:"system:serviceaccount:kube-system:namespace-controller"
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenesecretsexcludes:resource field:"resource" value:"secrets"
verbnelistexcludes:verb field:"verb" value:"list"
requestURIcontains/namespaces/excludes:requestURI field:"requestURI" value:"/namespaces/"
requestURIis_not_null(no value, null check)excludes:requestURI
namespaceis_not_null(no value, null check)excludes:namespace

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Immediately investigate the user or service account performing this operation and determine if they are compromised

2. Review what secrets exist in the cluster to assess the scope of credential exposure and rotate all sensitive credentials

3. Search for other suspicious API activity by this user in the past 24 hours and check for data exfiltration or lateral movement attempts

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "resource": "secrets"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestURI": "/api/v1/secrets?limit=500",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "stage": "ResponseComplete",
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "list"
}

Kubernetes Anonymous API Access Detected

#
Status
Experimental
Severity
informational
Group by
userAgent
Entities
ip_addresses, usernames
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, API, Initial Access, Unified Detection
Reference
raesene.github.io
Source
github.com/panther-labs/panther-analysis

This rule detects anonymous API requests made to Kubernetes API servers across AWS EKS, Azure AKS, and GCP GKE clusters. In production environments, anonymous access should be disabled to prevent unauthorized access to the API server.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Detection logic

from panther_kubernetes_helpers import k8s_alert_context


def is_system_access(event):
    # Skip localhost and health check traffic
    # nosec: Fallback to empty list is safe - we check for localhost separately
    src_ip = event.udm("sourceIPs") or []
    if src_ip == ["127.0.0.1"]:
        return True

    user_agent = event.udm("userAgent") or ""

    # AWS EKS: Exclude ELB health checker from internal IPs
    if user_agent == "ELB-HealthChecker/2.0" and src_ip and src_ip[0].startswith("10.0."):
        return True

    system_user_agents = (
        "kube-probe/",
        "GoogleHC/",
    )
    # GCP GKE & Azure AKS: Exclude kube-probe (Kubernetes liveness/readiness probes)
    # GCP GKE: Exclude Google Cloud Load Balancer health checks

    if any(user_agent.startswith(pattern) for pattern in system_user_agents):
        return True
    return False


def is_health_check(event):
    request_uri = event.udm("requestURI") or ""
    health_patterns = (
        "/healthz",
        "/readyz",
        "/livez",
        "/apis/healthz",
        "/apis/readyz",
        "/apis/livez",
    )
    if any(request_uri.startswith(pattern) for pattern in health_patterns):
        return True
    return False


def rule(event):
    if event.udm("username") == "system:anonymous":
        if not is_system_access(event) and not is_health_check(event):
            return True
    return False


def title(event):
    # For failed attempts or /version endpoint, use generic titles
    annotations = event.udm("annotations") or {}
    if annotations.get("authorization.k8s.io/decision") != "allow":
        return "Failed Anonymous Kubernetes API Access Attempt(s) Detected"
    if event.udm("requestURI") == "/version":
        return "Anonymous Kubernetes API Access to /version Endpoint Detected"

    # For successful access to other endpoints, provide detailed information
    source_ips = event.udm("sourceIPs") or []
    source_ip = source_ips[0] if source_ips else "<UNKNOWN_IP>"
    request_uri = event.udm("requestURI") or "<UNKNOWN_URI>"
    return (
        f"Anonymous API access detected on Kubernetes API server "
        f"from [{source_ip}] to [{request_uri}]"
    )


def severity(event):
    annotations = event.udm("annotations") or {}
    if annotations.get("authorization.k8s.io/decision") != "allow":
        return "INFO"
    if event.udm("requestURI") == "/version":
        return "INFO"
    return "DEFAULT"


def dedup(event):

    user_agent = event.udm("userAgent") or "<UNKNOWN_USER_AGENT>"
    return f"anonymous_access_{user_agent}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={"annotations": event.udm("annotations")},
    )

Rule specification

AnalysisType: rule
Filename: k8s_anonymous_api_access.py
RuleID: "Kubernetes.API.AnonymousAccess"
DisplayName: "Kubernetes Anonymous API Access Detected"
Enabled: true
Status: Experimental
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Severity: Info
Reports:
  MITRE ATT&CK:
    - TA0001:T1190 # Initial Access: Exploit Public-Facing Application
Description: >
  This rule detects anonymous API requests made to Kubernetes API servers across
  AWS EKS, Azure AKS, and GCP GKE clusters. In production environments, anonymous
  access should be disabled to prevent unauthorized access to the API server.
DedupPeriodMinutes: 60
Reference:
  https://raesene.github.io/blog/2023/03/18/lets-talk-about-anonymous-access-to-Kubernetes/
Runbook: |
  1. Query all API requests by the anonymous username (system:anonymous) in the 24 hours before and after the alert to identify scope of unauthorized access
  2. Check if the sourceIPs field contains internal, cloud provider, or external addresses to determine access vector
  3. Review other authentication attempts from the same sourceIPs in the past 7 days to identify related suspicious activity
SummaryAttributes:
  - username
  - p_any_ip_addresses
  - p_source_label
Tags:
  - Kubernetes
  - Security Control
  - API
  - Initial Access
  - Unified Detection

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • username is system:anonymous
  • any of:
    • userAgent is not ELB-HealthChecker/2.0
    • sourceIPs is empty
  • userAgent does not start with kube-probe/
  • userAgent does not start with GoogleHC/
  • requestURI does not start with /healthz
  • requestURI does not start with /readyz
  • requestURI does not start with /livez
  • requestURI does not start with /apis/healthz
  • requestURI does not start with /apis/readyz
  • requestURI does not start with /apis/livez

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
sourceIPsis_not_null(no value, null check)excludes:sourceIPs
userAgenteqELB-HealthChecker/2.0excludes:userAgent field:"userAgent" value:"ELB-HealthChecker/2.0"
userAgentstarts_withGoogleHC/excludes:userAgent field:"userAgent" value:"GoogleHC/"
userAgentstarts_withkube-probe/excludes:userAgent field:"userAgent" value:"kube-probe/"
requestURIstarts_with/apis/healthzexcludes:requestURI field:"requestURI" value:"/apis/healthz"
requestURIstarts_with/apis/livezexcludes:requestURI field:"requestURI" value:"/apis/livez"
requestURIstarts_with/apis/readyzexcludes:requestURI field:"requestURI" value:"/apis/readyz"
requestURIstarts_with/healthzexcludes:requestURI field:"requestURI" value:"/healthz"
requestURIstarts_with/livezexcludes:requestURI field:"requestURI" value:"/livez"
requestURIstarts_with/readyzexcludes:requestURI field:"requestURI" value:"/readyz"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernameeq
  • system:anonymous
field:"username" kind:eq value:"system:anonymous"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Query all API requests by the anonymous username (system:anonymous) in the 24 hours before and after the alert to identify scope of unauthorized access

2. Check if the sourceIPs field contains internal, cloud provider, or external addresses to determine access vector

3. Review other authentication attempts from the same sourceIPs in the past 7 days to identify related suspicious activity

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "annotations": {
    "authorization.k8s.io/decision": "allow",
    "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding system:public-info-viewer"
  },
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abcde12345",
  "kind": "Event",
  "level": "Request",
  "objectRef": {
    "apiVersion": "v1",
    "name": "test-pod",
    "namespace": "default",
    "resource": "pods"
  },
  "p_any_ip_addresses": [
    "8.8.8.8"
  ],
  "p_any_usernames": [
    "system:anonymous"
  ],
  "p_event_time": "2022-11-29 00:09:04.38",
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "example-cluster-eks-logs",
  "requestReceivedTimestamp": "2022-11-29 00:09:04.38",
  "requestURI": "/api/v1/namespaces/default/pods/test-pod",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "8.8.8.8"
  ],
  "stage": "ResponseComplete",
  "user": {
    "username": "system:anonymous"
  },
  "userAgent": "kubectl/v1.25.4"
}

Kubernetes API Activity from Tor Exit Node

#
Severity
medium
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Command and Control, Encrypted Channel, Unified Detection
Reference
medium.com
Source
github.com/panther-labs/panther-analysis

This detection monitors for Kubernetes API requests originating from known Indicators of Compromise, specifically Tor exit nodes. Tor usage may indicate attempts to hide the true source of malicious activity or unauthorized access attempts. This detection works across AWS EKS, Azure AKS, and GCP GKE clusters.

MITRE ATT&CK coverage

Detection logic

from panther_kubernetes_helpers import is_k8s_log, k8s_alert_context


def rule(event):
    # Check if this is a Kubernetes audit log with Tor exit node enrichment
    if is_k8s_log(event) and event.deep_get("p_enrichment", "tor_exit_nodes"):
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    tor_nodes = event.deep_get("p_enrichment", "tor_exit_nodes", default=[])
    tor_ip = tor_nodes[0] if tor_nodes else "<UNKNOWN_IP>"

    return f"Kubernetes API activity from Tor exit node [{tor_ip}] by user [{username}]"


def dedup(event):
    tor_nodes = event.deep_get("p_enrichment", "tor_exit_nodes", default=[])
    tor_ip = tor_nodes[0] if tor_nodes else "<UNKNOWN_IP>"
    return f"k8s_tor_{tor_ip}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={"tor_exit_nodes": event.deep_get("p_enrichment", "tor_exit_nodes")},
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.API.IOC.Activity"
DisplayName: "Kubernetes API Activity from Tor Exit Node"
Enabled: true
Filename: k8s_ioc_activity.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Command and Control
  - Encrypted Channel
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for Kubernetes API requests originating from known Indicators
  of Compromise, specifically Tor exit nodes. Tor usage may indicate attempts to hide
  the true source of malicious activity or unauthorized access attempts. This detection
  works across AWS EKS, Azure AKS, and GCP GKE clusters.
Runbook: |
  1. Find all API operations performed by username through the Tor exit node IP in the 6 hours before and after the alert
  2. Compare the operations and resources accessed against normal baseline activity for this user in the past 30 days to identify anomalous behavior
  3. Check if the Tor exit node IP has accessed other clusters or sensitive resources in the past 24 hours to assess campaign scope
Reference: https://medium.com/snowflake/from-logs-to-detection-using-snowflake-and-panther-to-detect-k8s-threats-d72f70a504d7
Reports:
  MITRE ATT&CK:
    - TA0011:T1573.002 # Command and Control: Encrypted Channel - Asymmetric Cryptography
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - p_any_ip_addresses
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • p_log_type is one of Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
  • any of:
    • all of:
      • p_log_type is GCP.AuditLog
      • protoPayload.serviceName is k8s.io
    • all of:
      • p_log_type is not GCP.AuditLog
      • p_log_type is Azure.MonitorActivity
      • category is one of kube-audit, kube-audit-admin
  • p_enrichment.tor_exit_nodes is present

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Find all API operations performed by username through the Tor exit node IP in the 6 hours before and after the alert

2. Compare the operations and resources accessed against normal baseline activity for this user in the past 30 days to identify anomalous behavior

3. Check if the Tor exit node IP has accessed other clusters or sensitive resources in the past 24 hours to assess campaign scope

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "namespace": "default",
    "resource": "pods"
  },
  "p_enrichment": {
    "tor_exit_nodes": [
      "1.2.3.4"
    ]
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestURI": "/api/v1/namespaces/default/pods",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "list"
}

Kubernetes API Multiple 403 Responses from Single Public IP

#
Status
Experimental
Severity
informational
Entities
ip_addresses
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Container and Resource Discovery, Unified Detection
Reference
aws.github.io
Source
github.com/panther-labs/panther-analysis

This detection identifies when a public source IP generates multiple 403 (Forbidden) responses from the Kubernetes API server. This pattern may indicate reconnaissance attempts, permission enumeration, brute force attacks, or misconfigured access. Private IPs are excluded as they typically represent legitimate internal traffic.

MITRE ATT&CK coverage

Detection logic

from ipaddress import ip_address

from panther_kubernetes_helpers import k8s_alert_context


def rule(event):
    response_status = event.udm("responseStatus") or {}
    source_ips = event.udm("sourceIPs") or []

    # Check for 403 response (HTTP) or 7 (gRPC PERMISSION_DENIED)
    status_code = response_status.get("code")
    if status_code not in (403, 7):
        return False
    if not source_ips:
        return False

    try:
        source_ip = source_ips[0]
        ip_obj = ip_address(source_ip)
        # alert on public IPs
        # Check if is_global attribute exists (Python 3.4+)
        if hasattr(ip_obj, "is_global") and ip_obj.is_global:
            return True
    except (ValueError, IndexError):
        return False

    return False


def title(event):
    source_ips = event.udm("sourceIPs") or []
    source_ip = source_ips[0] if source_ips else "<UNKNOWN_IP>"

    return f"Multiple 403 responses from public IP [{source_ip}]"


def dedup(event):
    source_ips = event.udm("sourceIPs") or []
    source_ip = source_ips[0] if source_ips else "<UNKNOWN_IP>"
    return f"k8s_403_{source_ip}"


def alert_context(event):
    return k8s_alert_context(event)

Rule specification

AnalysisType: rule
Filename: k8s_multiple_403_public_ip.py
RuleID: "Kubernetes.API.Multiple403.PublicIP"
DisplayName: "Kubernetes API Multiple 403 Responses from Single Public IP"
Enabled: true
Status: Experimental
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Container and Resource Discovery
  - Unified Detection
Reports:
  MITRE ATT&CK:
    - TA0007:T1613 # Container and Resource Discovery
Reference: https://aws.github.io/aws-eks-best-practices/security/docs/detective/
Severity: Info
Description: >
  This detection identifies when a public source IP generates multiple 403 (Forbidden)
  responses from the Kubernetes API server. This pattern may indicate reconnaissance
  attempts, permission enumeration, brute force attacks, or misconfigured access.
  Private IPs are excluded as they typically represent legitimate internal traffic.
Runbook: |
  1. Find all API requests from the sourceIPs address in the 1 hour before and after the alert to identify attempted operations
  2. Identify which API resources or operations the source IP attempted to access to determine if this is systematic probing or random scanning
  3. Check if the same source IP has generated 403 errors against other Kubernetes clusters in the past 24 hours to assess campaign scope
DedupPeriodMinutes: 30
Threshold: 10
SummaryAttributes:
  - username
  - p_any_ip_addresses
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • responseStatus.code is one of 403, 7
  • sourceIPs is present

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert cadence
alerts after 10 matches within 30m

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
responseStatus.codein
  • 403 transforms: number
  • 7 transforms: number
field:"responseStatus.code" kind:in
sourceIPsis_not_null
  • (no value, null check)
field:"sourceIPs" kind:is_not_null

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Find all API requests from the sourceIPs address in the 1 hour before and after the alert to identify attempted operations

2. Identify which API resources or operations the source IP attempted to access to determine if this is systematic probing or random scanning

3. Check if the same source IP has generated 403 errors against other Kubernetes clusters in the past 24 hours to assess campaign scope

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "discovery.k8s.io",
    "apiVersion": "v1",
    "resource": "endpointslices"
  },
  "p_any_ip_addresses": [
    "5.5.5.5"
  ],
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "responseStatus": {
    "code": 403
  },
  "sourceIPs": [
    "5.5.5.5"
  ],
  "stage": "ResponseComplete",
  "user": {
    "username": "system:serviceaccount:kube-system:coredns"
  },
  "userAgent": "Go-http-client/2.0",
  "verb": "watch"
}

Kubernetes Client Certificate Credential Created

#
Severity
informational
Group by
name, username
Compliance
Stratus Red Team k8s.persistence.create-client-certificate
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Persistence, Credential Access, Unified Detection
Reference
stratus-red-team.cloud
Source
github.com/panther-labs/panther-analysis

Detects the creation of client certificate signing requests (CSRs) for Kubernetes API authentication. Attackers with appropriate RBAC permissions can create and approve client certificates to establish persistent access to the cluster. Client certificates provide long-term authentication that bypasses service account token expiration and can be harder to revoke. This technique is documented by Stratus Red Team as a persistence mechanism.

MITRE ATT&CK coverage

TacticTechniques
Persistence
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check CertificateSigningRequest creation
    if verb != "create" or resource != "certificatesigningrequests":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals
    if is_system_principal(username):
        return False

    # Exclude node bootstrap processes that legitimately create CSRs during cluster operations
    if username == "kubelet-nodepool-bootstrap":
        return False

    # Check if this is for client authentication
    request_object = event.udm("requestObject") or {}
    spec = request_object.get("spec", {})
    usages = spec.get("usages", [])
    signer_name = spec.get("signerName", "")

    # Look for client auth certificates
    if "client auth" in usages or "kubernetes.io/kube-apiserver-client" in signer_name:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    name = event.udm("name") or "<UNKNOWN_CSR>"

    return f"[{username}] created client certificate signing request [{name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    name = event.udm("name") or "<UNKNOWN_CSR>"
    return f"k8s_client_cert_{username}_{name}"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    spec = request_object.get("spec", {})

    return k8s_alert_context(
        event,
        extra_fields={
            "csr_name": event.udm("name"),
            "signer_name": spec.get("signerName"),
            "usages": spec.get("usages"),
            "groups": spec.get("groups"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.ClientCertificate.Created"
DisplayName: "Kubernetes Client Certificate Credential Created"
Enabled: true
Filename: k8s_client_certificate_created.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Persistence
  - Credential Access
  - Unified Detection
Severity: Info
Description: >
  Detects the creation of client certificate signing requests (CSRs) for Kubernetes API authentication. Attackers with
  appropriate RBAC permissions can create and approve client certificates to establish persistent access to the cluster.
  Client certificates provide long-term authentication that bypasses service account token expiration and can be harder
  to revoke. This technique is documented by Stratus Red Team as a persistence mechanism.
Runbook: |
  1. Immediately investigate the user creating the certificate and verify if they are authorized to create client auth credentials
  2. Check if the CSR was approved and review who approved it within the last 10 minutes by searching audit logs for certificate approval events
  3. Search for API activity using the newly created certificate in the past 24 hours to identify any unauthorized access
Reports:
  Stratus Red Team:
    - k8s.persistence.create-client-certificate
  MITRE ATT&CK:
    - TA0003:T1098 # Persistence: Account Manipulation
    - TA0006:T1552 # Credential Access: Unsecured Credentials
Reference: https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.persistence.create-client-certificate/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is certificatesigningrequests
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • username is not kubelet-nodepool-bootstrap
  • any of:
    • requestObject.spec.usages contains client auth
    • requestObject.spec.signerName contains kubernetes.io/kube-apiserver-client

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Immediately investigate the user creating the certificate and verify if they are authorized to create client auth credentials

2. Check if the CSR was approved and review who approved it within the last 10 minutes by searching audit logs for certificate approval events

3. Search for API activity using the newly created certificate in the past 24 hours to identify any unauthorized access

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "certificates.k8s.io/v1",
    "name": "malicious-csr",
    "resource": "certificatesigningrequests"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "apiVersion": "certificates.k8s.io/v1",
    "kind": "CertificateSigningRequest",
    "metadata": {
      "name": "malicious-csr"
    },
    "spec": {
      "groups": [
        "system:authenticated"
      ],
      "request": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0=",
      "signerName": "kubernetes.io/kube-apiserver-client",
      "usages": [
        "client auth"
      ]
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes ClusterRoleBinding to Privileged Role

#
Severity
medium
Group by
name, username
Compliance
Stratus Red Team k8s.persistence.create-admin-clusterrole, k8s.privilege-escalation.create-admin-clusterrole
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, Persistence, RBAC, Unified Detection
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
Source
github.com/panther-labs/panther-analysis

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.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

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,
        },
    )

Rule specification

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

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is clusterrolebindings
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • requestObject.roleRef.name is one of cluster-admin, system:masters, admin, system:kube-controller-manager, system:kube-scheduler

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
requestObject.roleRef.namein
  • admin
  • cluster-admin
  • system:kube-controller-manager
  • system:kube-scheduler
  • system:masters
field:"requestObject.roleRef.name" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name
namerequestObject.roleRef.name

Response 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

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "backdoor-admin",
    "resource": "clusterrolebindings"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRoleBinding",
    "metadata": {
      "name": "backdoor-admin"
    },
    "roleRef": {
      "apiGroup": "rbac.authorization.k8s.io",
      "kind": "ClusterRole",
      "name": "cluster-admin"
    },
    "subjects": [
      {
        "apiGroup": "rbac.authorization.k8s.io",
        "kind": "User",
        "name": "attacker@example.com"
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes CronJob Created or Modified

#
Status
Experimental
Severity
informational
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Persistence, Scheduled Task, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for creation or modification of CronJobs in Kubernetes clusters. Attackers may create or modify scheduled jobs to achieve cluster persistence, execute malicious code on a schedule, or maintain backdoor access to compromised clusters. This detection works across AWS EKS, Azure AKS, and GCP GKE clusters.

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")
    response_status = event.udm("responseStatus")
    namespace = event.udm("namespace")
    username = event.udm("username")

    # Check for CronJob create/update/patch operations
    if verb in ("create", "update", "patch") and resource == "cronjobs":
        # Only alert on successful operations
        if is_failed_request(response_status):
            return False

        # Exclude status updates (routine execution tracking, not spec changes)
        if subresource == "status":
            return False

        # Exclude system controllers creating/modifying CronJobs in system namespaces
        if is_system_namespace(namespace) and is_system_principal(username):
            return False

        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    cronjob_name = event.udm("name") or "<UNKNOWN>"
    verb = event.udm("verb")

    action = "created" if verb == "create" else "modified"
    return f"[{username}] {action} CronJob " f"[{namespace}/{cronjob_name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_cronjob_{username}_{namespace}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "cronjob_name": event.udm("name"),
            "requestObject": event.udm("requestObject"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.CronJob.CreatedOrModified"
DisplayName: "Kubernetes CronJob Created or Modified"
Enabled: true
Status: Experimental
Filename: k8s_cronjob_created_or_modified.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Persistence
  - Scheduled Task
  - Unified Detection
Severity: Info
Description: >
  This detection monitors for creation or modification of CronJobs in Kubernetes clusters.
  Attackers may create or modify scheduled jobs to achieve cluster persistence, execute
  malicious code on a schedule, or maintain backdoor access to compromised clusters.
  This detection works across AWS EKS, Azure AKS, and GCP GKE clusters.
Runbook: |
  1. Query all CronJob creation and modification events by username in the 24 hours before the alert to establish deployment patterns
  2. Analyze the CronJob schedule specification and container image from requestObject to identify suspicious commands or known malicious patterns
  3. Search for other CronJobs created by this user in the past 30 days to determine if this represents established activity or anomalous behavior
Reference: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
Reports:
  MITRE ATT&CK:
    - TA0003:T1053.003 # Persistence: Scheduled Task/Job - Cron
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is one of create, update, patch
  • resource is cronjobs
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • subresource is not status
  • any of:
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Query all CronJob creation and modification events by username in the 24 hours before the alert to establish deployment patterns

2. Analyze the CronJob schedule specification and container image from requestObject to identify suspicious commands or known malicious patterns

3. Search for other CronJobs created by this user in the past 30 days to determine if this represents established activity or anomalous behavior

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "batch/v1",
    "name": "backup-job",
    "namespace": "default",
    "resource": "cronjobs"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "CronJob",
    "metadata": {
      "name": "backup-job",
      "namespace": "default"
    },
    "spec": {
      "jobTemplate": {
        "spec": {
          "template": {
            "spec": {
              "containers": [
                {
                  "image": "backup:latest",
                  "name": "backup"
                }
              ]
            }
          }
        }
      },
      "schedule": "0 2 * * *"
    }
  },
  "requestURI": "/apis/batch/v1/namespaces/default/cronjobs",
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes DaemonSet Created

#
Severity
informational
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Persistence, Deploy Container, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for creation of DaemonSets in Kubernetes clusters. DaemonSets ensure that a copy of a pod runs on all (or selected) nodes in the cluster. Attackers may abuse DaemonSets to deploy malicious containers across all nodes for cluster-wide persistence, credential harvesting, cryptomining, or lateral movement. This detection works across AWS EKS, Azure AKS, and GCP GKE clusters.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    response_status = event.udm("responseStatus")
    namespace = event.udm("namespace")
    username = event.udm("username")

    # Check for DaemonSet create operation
    if verb == "create" and resource == "daemonsets":
        # Only alert on successful operations
        if is_failed_request(response_status):
            return False

        # Exclude system controllers creating DaemonSets in system namespaces
        if is_system_namespace(namespace) and is_system_principal(username):
            return False

        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    daemonset_name = event.udm("name") or "<UNKNOWN>"

    return f"[{username}] created DaemonSet [{namespace}/{daemonset_name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_daemonset_{username}_{namespace}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "daemonset_name": event.udm("name"),
            "requestObject": event.udm("requestObject"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.DaemonSet.Created"
DisplayName: "Kubernetes DaemonSet Created"
Enabled: true
Filename: k8s_daemonset_created.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Persistence
  - Deploy Container
  - Unified Detection
Severity: Info
Description: >
  This detection monitors for creation of DaemonSets in Kubernetes clusters.
  DaemonSets ensure that a copy of a pod runs on all (or selected) nodes in the cluster.
  Attackers may abuse DaemonSets to deploy malicious containers across all nodes for
  cluster-wide persistence, credential harvesting, cryptomining, or lateral movement.
  This detection works across AWS EKS, Azure AKS, and GCP GKE clusters.
Runbook: |
  1. Review all DaemonSet and Deployment creation events by username in the 24 hours before the alert to establish if this is routine activity
  2. Analyze the container image, hostPath volumes, and privileged security context from the requestObject specification to identify risk factors
  3. Search for similar DaemonSet deployments by this user in the past 90 days and compare images, namespaces, and security settings to identify anomalies
Reference: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
Reports:
  MITRE ATT&CK:
    - TA0002:T1610 # Execution: Deploy Container
    - TA0003:T1543 # Persistence: Create or Modify System Process
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is daemonsets
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review all DaemonSet and Deployment creation events by username in the 24 hours before the alert to establish if this is routine activity

2. Analyze the container image, hostPath volumes, and privileged security context from the requestObject specification to identify risk factors

3. Search for similar DaemonSet deployments by this user in the past 90 days and compare images, namespaces, and security settings to identify anomalies

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "apps/v1",
    "name": "monitoring-agent",
    "namespace": "kube-system",
    "resource": "daemonsets"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "DaemonSet",
    "metadata": {
      "name": "monitoring-agent",
      "namespace": "kube-system"
    },
    "spec": {
      "selector": {
        "matchLabels": {
          "app": "monitoring"
        }
      },
      "template": {
        "metadata": {
          "labels": {
            "app": "monitoring"
          }
        },
        "spec": {
          "containers": [
            {
              "image": "monitoring-agent:latest",
              "name": "agent",
              "volumeMounts": [
                {
                  "mountPath": "/var/run/docker.sock",
                  "name": "docker-sock"
                }
              ]
            }
          ],
          "volumes": [
            {
              "hostPath": {
                "path": "/var/run/docker.sock"
              },
              "name": "docker-sock"
            }
          ]
        }
      }
    }
  },
  "requestURI": "/apis/apps/v1/namespaces/kube-system/daemonsets",
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Data Copy via kubectl cp

#
Severity
medium
Group by
name, namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Exfiltration, Data Theft, Credential Access, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for kubectl cp operations that copy files from pods to local machines, which can indicate data exfiltration. When kubectl cp is used to copy files from a pod, it executes a tar command with stdout output (tar cf -) inside the container and streams the data back through the Kubernetes API server. Attackers who gain cluster access can use this technique to steal application secrets, credentials, configuration files, or sensitive data from container filesystems without leaving obvious traces inside the pod itself. While kubectl cp has legitimate uses for debugging and backup, unexpected usage should be investigated.

MITRE ATT&CK coverage

TacticTechniques
Credential Access
Collection

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from urllib.parse import parse_qs, urlparse

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def get_exec_command(event):
    """Extract exec command from requestObject (GCP/Azure) or requestURI query params (EKS).

    Returns:
        List of command arguments, or empty list if not found
    """
    # Try requestObject first (GCP/Azure format)
    request_object = event.udm("requestObject") or {}
    command = request_object.get("command")
    if command:
        return command

    # Fall back to parsing requestURI query parameters (EKS format)
    # EKS format: /api/v1/.../exec?command=tar&command=cf&command=-
    request_uri = event.udm("requestURI") or ""
    if "command=" in request_uri:
        try:
            parsed = urlparse(request_uri)
            params = parse_qs(parsed.query)
            return params.get("command", [])
        except Exception:  # pylint: disable=broad-except
            return []

    return []


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check exec subresource operations
    if verb not in ("create", "get") or resource != "pods" or subresource != "exec":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Extract command from either requestObject or requestURI
    command = get_exec_command(event)
    if not command:
        return False

    # Check if command contains tar with cf - pattern
    # tar cf - indicates copying FROM pod (stdout output = exfil)
    tar_found = False
    cf_flag_found = False
    stdout_dash_found = False

    for i, arg in enumerate(command):
        arg_str = str(arg).lower()

        if "tar" in arg_str:
            tar_found = True

        # Look for cf flag (create+file) - handles: cf, -cf, czf, -czf, etc.
        if "cf" in arg_str and arg_str != "-c":
            cf_flag_found = True

        # Check if - appears after cf flag was found (stdout redirect)
        if arg_str == "-" and i > 0 and cf_flag_found:
            stdout_dash_found = True

    return tar_found and cf_flag_found and stdout_dash_found


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_POD>"

    command = get_exec_command(event)

    # Extract the path being copied if possible
    # Command format: ["tar", "cf", "-", "/path/to/file"]
    path = "<UNKNOWN_PATH>"
    if len(command) >= 4:
        for i, arg in enumerate(command):
            if arg == "-" and i + 1 < len(command):
                path = str(command[i + 1])
                break

    return f"[{username}] copied data from pod [{namespace}/{name}] path [{path}] via kubectl cp"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_POD>"
    return f"k8s_kubectl_cp_{username}_{namespace}_{name}"


def severity(event):
    """Increase severity for copying from sensitive paths."""
    command = get_exec_command(event)
    command_str = " ".join(str(arg) for arg in command).lower()

    # Critical severity for copying credentials or SSH keys
    critical_patterns = [
        "/root",
        ".ssh",
        "id_rsa",
        "id_ecdsa",
        "id_ed25519",
        "credentials",
        "secrets",
        "token",
        ".kube",
        "serviceaccount",
    ]
    if any(pattern in command_str for pattern in critical_patterns):
        return "CRITICAL"

    # High severity for copying from sensitive system directories
    sensitive_paths = ["/etc", "/var/run", "/proc", "config", "password", "shadow"]
    if any(path in command_str for path in sensitive_paths):
        return "HIGH"

    return "MEDIUM"


def alert_context(event):
    command = get_exec_command(event)
    request_object = event.udm("requestObject") or {}

    return k8s_alert_context(
        event,
        extra_fields={
            "pod_name": event.udm("name"),
            "command": command,
            "container": request_object.get("container"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Kubectl.CP.Operation"
DisplayName: "Kubernetes Data Copy via kubectl cp"
Enabled: true
Filename: k8s_kubectl_cp_operation.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Exfiltration
  - Data Theft
  - Credential Access
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for kubectl cp operations that copy files from pods to local machines,
  which can indicate data exfiltration. When kubectl cp is used to copy files from a pod, it
  executes a tar command with stdout output (tar cf -) inside the container and streams the data
  back through the Kubernetes API server. Attackers who gain cluster access can use this technique
  to steal application secrets, credentials, configuration files, or sensitive data from container
  filesystems without leaving obvious traces inside the pod itself. While kubectl cp has legitimate
  uses for debugging and backup, unexpected usage should be investigated.
Runbook: |
  1. Review all exec and kubectl cp operations by this user in the 24 hours before and after the alert
  2. Identify what files or directories were exfiltrated and assess their sensitivity
  3. Search for other suspicious API activity from this user or service account across all clusters in the past 7 days
Reports:
  MITRE ATT&CK:
    - TA0010:T1530 # Exfiltration: Data from Cloud Storage
    - TA0006:T1552 # Credential Access: Unsecured Credentials
Reference: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#cp
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is one of create, get
  • resource is pods
  • subresource is exec
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
verbincreate, getexcludes:verb field:"verb" value:"create" field:"verb" value:"get"
resourcenepodsexcludes:resource field:"resource" value:"pods"
subresourceneexecexcludes:subresource field:"subresource" value:"exec"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review all exec and kubectl cp operations by this user in the 24 hours before and after the alert

2. Identify what files or directories were exfiltrated and assess their sensitivity

3. Search for other suspicious API activity from this user or service account across all clusters in the past 7 days

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "webapp-pod",
    "namespace": "production",
    "resource": "pods",
    "subresource": "exec"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": null,
  "requestURI": "/api/v1/namespaces/production/pods/webapp-pod/exec?command=tar&command=cf&command=-&command=/app/secrets/credentials.json&container=webapp&stdout=true",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Exec Into Pod

#
Status
Experimental
Severity
medium
Group by
name, namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Configuration Required, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

Detects when users exec into pods across Kubernetes clusters. Execing into pods should be monitored as it can be used for unauthorized access, privilege escalation, or persistent access to workloads. This detection is disabled by default and should be configured with inline filters in the Panther UI to exclude legitimate use cases (e.g., specific service accounts, namespaces, or authorized users).

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")

    # Check for exec action on pods
    if verb in ("create", "get") and resource == "pods" and subresource == "exec":
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    pod_name = event.udm("name") or "<UNKNOWN>"

    return f"[{username}] executed into pod [{namespace}/{pod_name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    pod_name = event.udm("name") or "<UNKNOWN_POD>"
    return f"k8s_exec_{username}_{namespace}_{pod_name}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={"pod_name": event.udm("name")},
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Exec"
DisplayName: "Kubernetes Exec Into Pod"
Enabled: false
Status: Experimental
Filename: k8s_exec_into_pod.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Configuration Required
  - Unified Detection
Severity: Medium
Description: >
  Detects when users exec into pods across Kubernetes clusters. Execing into pods should be
  monitored as it can be used for unauthorized access, privilege escalation, or persistent
  access to workloads. This detection is disabled by default and should be configured with
  inline filters in the Panther UI to exclude legitimate use cases (e.g., specific service
  accounts, namespaces, or authorized users).
Runbook: |
  1. Query all exec operations by the username in the 24 hours before and after the alert to identify scope of pod access
  2. Review the namespace and pod name from the alert context to determine if accessing sensitive workloads or data
  3. Cross-reference the username against authorized administrator or DevOps groups in the past 30 days to verify authorization
Reference: https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is one of create, get
  • resource is pods
  • subresource is exec

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Query all exec operations by the username in the 24 hours before and after the alert to identify scope of pod access

2. Review the namespace and pod name from the alert context to determine if accessing sensitive workloads or data

3. Cross-reference the username against authorized administrator or DevOps groups in the past 30 days to verify authorization

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "nginx-pod",
    "namespace": "default",
    "resource": "pods",
    "subresource": "exec"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestURI": "/api/v1/namespaces/default/pods/nginx-pod/exec",
  "responseStatus": {
    "code": 101
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "groups": [
      "system:authenticated"
    ],
    "username": "john.doe@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Ingress Created Without TLS

#
Severity
medium
Group by
name, namespace
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Network Security, Encryption, Compliance, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for Ingress objects being created without TLS certificates configured. Ingresses without TLS expose services over unencrypted HTTP, allowing sensitive data like passwords, tokens, and PII to be transmitted in cleartext. This violates security best practices and compliance requirements like PCI-DSS and HIPAA, and enables man-in-the-middle attacks.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_base_helpers import deep_get
from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check ingress creation events
    if verb != "create" or resource != "ingresses":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system namespaces and system principals to reduce false positives
    if is_system_namespace(namespace) or is_system_principal(username):
        return False

    # Check if ingress has TLS configuration
    tls = deep_get(event.udm("requestObject"), "spec", "tls")

    # Alert if TLS is not configured (missing or empty)
    if not tls:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_INGRESS>"

    return f"[{username}] created Ingress [{namespace}/{name}] without TLS certificate"


def dedup(event):
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_INGRESS>"
    return f"k8s_ingress_no_tls_{namespace}_{name}"


def severity(event):
    """Increase severity based on ingress annotations and rules."""
    request_object = event.udm("requestObject") or {}
    metadata = request_object.get("metadata", {})
    annotations = metadata.get("annotations", {})

    # Check if this is an external-facing ingress (has external annotations)
    external_annotations = [
        "kubernetes.io/ingress.class",
        "cert-manager.io/cluster-issuer",
        "external-dns.alpha.kubernetes.io/hostname",
    ]

    if any(key in annotations for key in external_annotations):
        return "MEDIUM"

    return "DEFAULT"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    spec = request_object.get("spec", {})
    rules = spec.get("rules", [])
    metadata = request_object.get("metadata", {})
    annotations = metadata.get("annotations", {})

    # Extract hosts from ingress rules
    hosts = []
    for rule_entry in rules:
        host = rule_entry.get("host")
        if host:
            hosts.append(host)

    return k8s_alert_context(
        event,
        extra_fields={
            "ingress_name": event.udm("name"),
            "ingress_hosts": hosts,
            "annotations": annotations,
            "has_tls": False,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Ingress.NoTLS"
DisplayName: "Kubernetes Ingress Created Without TLS"
Enabled: true
Filename: k8s_ingress_without_tls.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Network Security
  - Encryption
  - Compliance
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for Ingress objects being created without TLS certificates configured. Ingresses
  without TLS expose services over unencrypted HTTP, allowing sensitive data like passwords, tokens, and PII
  to be transmitted in cleartext. This violates security best practices and compliance requirements like
  PCI-DSS and HIPAA, and enables man-in-the-middle attacks.
Runbook: |
  1. Review the ingress specification to determine if this is an internal-only service or if TLS is terminated at an external load balancer
  2. If TLS is required, work with the team to configure TLS certificates using cert-manager or manual certificate creation
  3. Search for other ingresses without TLS in the past 7 days to identify if this is a systemic configuration issue
Reports:
  MITRE ATT&CK:
    - TA0009:T1040 # Collection: Network Sniffing
    - TA0006:T1552.004 # Credential Access: Unsecured Credentials - Private Keys
Reference: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is ingresses
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • requestObject.spec.tls is empty

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
usernameis_not_null(no value, null check)excludes:username
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourceneingressesexcludes:resource field:"resource" value:"ingresses"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review the ingress specification to determine if this is an internal-only service or if TLS is terminated at an external load balancer

2. If TLS is required, work with the team to configure TLS certificates using cert-manager or manual certificate creation

3. Search for other ingresses without TLS in the past 7 days to identify if this is a systemic configuration issue

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "networking.k8s.io",
    "apiVersion": "v1",
    "name": "api-ingress",
    "namespace": "production",
    "resource": "ingresses"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "Ingress",
    "metadata": {
      "annotations": {
        "kubernetes.io/ingress.class": "nginx"
      },
      "name": "api-ingress",
      "namespace": "production"
    },
    "spec": {
      "rules": [
        {
          "host": "api.example.com",
          "http": {
            "paths": [
              {
                "backend": {
                  "serviceName": "api-service",
                  "servicePort": 80
                },
                "path": "/"
              }
            ]
          }
        }
      ]
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "developer@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Long-Lived Service Account Token Created

#
Status
Experimental
Severity
informational
Group by
name, namespace, username
Compliance
Stratus Red Team k8s.persistence.create-token
Log types
Amazon.EKS.Audit, Azure.MonitorActivity
Tags
Kubernetes, Persistence, Credential Access
Reference
stratus-red-team.cloud
Source
github.com/panther-labs/panther-analysis

Detects the creation of long-lived service account tokens via the serviceaccounts/token subresource. Kubernetes 1.24+ deprecated automatic token creation, but users with appropriate permissions can still manually create non-expiring tokens for service accounts. Attackers can abuse this to establish persistent access credentials that don't expire automatically. This technique is documented by Stratus Red Team as a persistence mechanism. Note: GCP GKE does not log TokenRequest API operations in Kubernetes audit logs.

MITRE ATT&CK coverage

TacticTechniques
Persistence
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check serviceaccount token subresource creation
    if verb != "create" or resource != "serviceaccounts" or subresource != "token":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals in system namespaces (legitimate operations)
    # but alert on system principals in user namespaces (malicious controllers)
    # and alert on users creating tokens in system namespaces (privilege escalation)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_SA>"

    return f"[{username}] created long-lived token for service account " f"[{namespace}/{name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_SA>"
    return f"k8s_token_created_{username}_{namespace}_{name}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "service_account": event.udm("name"),
            "namespace": event.udm("namespace"),
            "subresource": "token",
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.ServiceAccount.TokenCreated"
DisplayName: "Kubernetes Long-Lived Service Account Token Created"
Enabled: true
Status: Experimental
Filename: k8s_serviceaccount_token_created.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
Tags:
  - Kubernetes
  - Persistence
  - Credential Access
Severity: Info
Description: >
  Detects the creation of long-lived service account tokens via the serviceaccounts/token subresource. Kubernetes 1.24+
  deprecated automatic token creation, but users with appropriate permissions can still manually create non-expiring tokens
  for service accounts. Attackers can abuse this to establish persistent access credentials that don't expire automatically.
  This technique is documented by Stratus Red Team as a persistence mechanism.
  Note: GCP GKE does not log TokenRequest API operations in Kubernetes audit logs.
Runbook: |
  1. Immediately investigate the user creating the token and determine if they have a legitimate need for long-lived credentials
  2. Review the service account permissions to assess what access the token provides by checking RoleBindings and ClusterRoleBindings
  3. Search for API activity using this service account in the past 24 hours to identify any unauthorized operations
Reports:
  Stratus Red Team:
    - k8s.persistence.create-token
  MITRE ATT&CK:
    - TA0003:T1098 # Persistence: Account Manipulation
    - TA0006:T1552 # Credential Access: Unsecured Credentials
Reference: https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.persistence.create-token/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity events when all of the conditions below hold.

Condition

  • verb is create
  • resource is serviceaccounts
  • subresource is token
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourceneserviceaccountsexcludes:resource field:"resource" value:"serviceaccounts"
subresourcenetokenexcludes:subresource field:"subresource" value:"token"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Immediately investigate the user creating the token and determine if they have a legitimate need for long-lived credentials

2. Review the service account permissions to assess what access the token provides by checking RoleBindings and ClusterRoleBindings

3. Search for API activity using this service account in the past 24 hours to identify any unauthorized operations

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "persistence-sa",
    "namespace": "production",
    "resource": "serviceaccounts",
    "subresource": "token"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes NodePort Service Deployed

#
Severity
high
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Exploit Public-Facing Application, Initial Access, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for any Kubernetes service deployed with type NodePort. A NodePort service allows an attacker to expose a set of pods hosting the service to the internet by opening their port and redirecting traffic here. This can be used to bypass network controls and intercept traffic, creating a direct line to the outside network.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    response_status = event.udm("responseStatus")

    # Only check service creation events
    if verb != "create" or resource != "services":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Check if service type is NodePort
    service_type = event.udm("serviceType") or ""
    if service_type == "NodePort":
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN>"

    return f"[{username}] deployed NodePort service [{namespace}/{name}]"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "service_name": event.udm("name"),
            "service_type": event.udm("serviceType"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Service.NodePort.Deployed"
DisplayName: "Kubernetes NodePort Service Deployed"
Enabled: true
Filename: k8s_service_nodeport.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Severity: High
Description: >
  This detection monitors for any Kubernetes service deployed with type NodePort. A NodePort
  service allows an attacker to expose a set of pods hosting the service to the internet by
  opening their port and redirecting traffic here. This can be used to bypass network controls
  and intercept traffic, creating a direct line to the outside network.
Runbook: |
  1. Identify all services created by the username in the 48 hours before the alert to understand deployment patterns
  2. Check the NodePort service specification to determine if it exposes cluster-critical services or standard application workloads
  3. Review exposed ports and compare against documented internal services in the past 30 days to verify if this is authorized
Reference: https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/
Tags:
  - Kubernetes
  - Exploit Public-Facing Application
  - Initial Access
  - Unified Detection
Reports:
  MITRE ATT&CK:
    - TA0001:T1190 # Exploit Public-Facing Application

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is services
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • serviceType is NodePort

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Identify all services created by the username in the 48 hours before the alert to understand deployment patterns

2. Check the NodePort service specification to determine if it exposes cluster-critical services or standard application workloads

3. Review exposed ports and compare against documented internal services in the past 30 days to verify if this is authorized

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "test-ns",
    "namespace": "default",
    "resource": "services"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "apiVersion": "v1",
    "kind": "Service",
    "spec": {
      "ports": [
        {
          "port": 5678,
          "protocol": "TCP",
          "targetPort": 8080
        }
      ],
      "type": "NodePort"
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "user@example.com"
  },
  "userAgent": "kubectl/v1.28.2",
  "verb": "create"
}

Kubernetes Pod Attached To Host Network

#
Severity
medium
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Escape to Host, Unified Detection
Reference
- https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces - https://securitylabs.datadoghq.com/articles/kubernetes-security-fundamentals-part-6/
Source
github.com/panther-labs/panther-analysis

This detection monitors for the creation of pods which are attached to the host's network. This allows a pod to listen to all network traffic for all deployed compute on that particular node and communicate with other compute on the network namespace. Attackers can use this to capture secrets passed in arguments or connections.

MITRE ATT&CK coverage

TacticTechniques
Privilege Escalation

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check if hostNetwork is set to true in the request
    host_network = event.udm("hostNetwork")
    if host_network is True:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    return f"[{username}] created pod [{namespace}/{name}] with host network access "


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_host_network_{username}_{namespace}"


def alert_context(event):
    return k8s_alert_context(event, extra_fields=get_pod_context_fields(event))

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Host.Network"
DisplayName: "Kubernetes Pod Attached To Host Network"
Enabled: true
Filename: k8s_pod_host_network.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Escape to Host
  - Unified Detection
Severity: Medium
Description:
  This detection monitors for the creation of pods which are attached to the host's network.
  This allows a pod to listen to all network traffic for all deployed compute on that particular
  node and communicate with other compute on the network namespace. Attackers can use this to
  capture secrets passed in arguments or connections.
Reports:
  MITRE ATT&CK:
    - TA0004:T1611 # Escape to Host
Runbook: |
  1. Find all pod creation events by the username in the 24 hours before the alert to establish normal deployment behavior
  2. Check if the pod namespace indicates system infrastructure purpose (kube-system, kube-public) which may be legitimate
  3. Review all pods with hostNetwork by this user in the past 30 days to identify if this is an established pattern or anomalous activity
Reference: >
  - https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces
  - https://securitylabs.datadoghq.com/articles/kubernetes-security-fundamentals-part-6/

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • hostNetwork is true

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Find all pod creation events by the username in the 24 hours before the alert to establish normal deployment behavior

2. Check if the pod namespace indicates system infrastructure purpose (kube-system, kube-public) which may be legitimate

3. Review all pods with hostNetwork by this user in the past 30 days to identify if this is an established pattern or anomalous activity

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "nginx-test",
    "namespace": "default",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
      "name": "nginx-test",
      "namespace": "default"
    },
    "spec": {
      "containers": [
        {
          "image": "nginx",
          "name": "nginx"
        }
      ],
      "hostNetwork": true
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Pod Created in System Namespace

#
Status
Experimental
Severity
medium
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, Persistence, Defense Evasion, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for pods being created in system namespaces like kube-system, kube-public, gke-system, or kube-node-lease. These namespaces are reserved for Kubernetes control plane components and cluster infrastructure. Attackers who gain cluster access may create malicious pods in system namespaces to hide among legitimate system workloads, gain elevated privileges, or establish persistence mechanisms that are less likely to be noticed.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    SYSTEM_NAMESPACES,
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Only check direct pod creation, not subresources like eviction
    if subresource:
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals (legitimate operators/controllers)
    if is_system_principal(username):
        return False

    # Alert if pod is created in a system namespace
    if namespace in SYSTEM_NAMESPACES:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    return f"[{username}] created pod [{namespace}/{name}] in system namespace"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)
    return f"k8s_pod_system_ns_{username}_{namespace}_{name}"


def alert_context(event):
    return k8s_alert_context(event, extra_fields=get_pod_context_fields(event))

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.SystemNamespace"
DisplayName: "Kubernetes Pod Created in System Namespace"
Enabled: true
Status: Experimental
Filename: k8s_pod_created_in_system_namespace.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Privilege Escalation
  - Persistence
  - Defense Evasion
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for pods being created in system namespaces like kube-system, kube-public, gke-system,
  or kube-node-lease. These namespaces are reserved for Kubernetes control plane components and cluster infrastructure.
  Attackers who gain cluster access may create malicious pods in system namespaces to hide among legitimate system
  workloads, gain elevated privileges, or establish persistence mechanisms that are less likely to be noticed.
Runbook: |
  1. Review the pod specification to identify the container images being deployed and determine if this is a legitimate system component or malicious workload
  2. Identify all API operations by the creating user in the 2 hours before and after the alert to establish intent
  3. If unauthorized, immediately delete the pod and search for other suspicious pod creations or RBAC changes across all clusters in the past 7 days
Reports:
  MITRE ATT&CK:
    - TA0004:T1611 # Privilege Escalation: Escape to Host
    - TA0003:T1525 # Persistence: Implant Internal Image
    - TA0005:T1578.002 # Defense Evasion: Modify Cloud Compute Infrastructure
Reference: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • subresource is empty
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • namespace is one of kube-system, gke-system, kube-node-lease, kube-public

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
namespacein
  • gke-system
  • kube-node-lease
  • kube-public
  • kube-system
field:"namespace" kind:in
subresourceis_null
  • (no value, null check)
field:"subresource" kind:is_null
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Review the pod specification to identify the container images being deployed and determine if this is a legitimate system component or malicious workload

2. Identify all API operations by the creating user in the 2 hours before and after the alert to establish intent

3. If unauthorized, immediately delete the pod and search for other suspicious pod creations or RBAC changes across all clusters in the past 7 days

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "malicious-pod",
    "namespace": "kube-system",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "Pod",
    "metadata": {
      "name": "malicious-pod",
      "namespace": "kube-system"
    },
    "spec": {
      "containers": [
        {
          "image": "attacker/backdoor:latest",
          "name": "backdoor"
        }
      ]
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Pod Using Host IPC Namespace

#
Severity
medium
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Privilege Escalation, Container Escape, Unified Detection
Reference
- https://kubernetes.io/docs/concepts/security/pod-security-standards/ - https://www.fairwinds.com/blog/kubernetes-basics-tutorial-host-ipc-should-not-be-configured
Source
github.com/panther-labs/panther-analysis

This detection monitors for pods created with hostIPC set to true, which allows the pod to use the host's IPC namespace. This breaks isolation between the pod and the host system, giving the pod direct access to shared memory segments, semaphores, and message queues on the host. Attackers can abuse this to communicate with or interfere with processes on the host system or other containers using the same IPC namespace.

MITRE ATT&CK coverage

TacticTechniques
Privilege Escalation
Defense Impairment

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check if hostIPC is enabled
    host_ipc = event.udm("hostIPC")
    if host_ipc is True:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    return f"[{username}] created pod [{namespace}/{name}] with host IPC enabled "


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_host_ipc_{username}_{namespace}"


def alert_context(event):
    return k8s_alert_context(event, extra_fields=get_pod_context_fields(event))

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Host.IPC"
DisplayName: "Kubernetes Pod Using Host IPC Namespace"
Enabled: true
Filename: k8s_pod_host_ipc.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Privilege Escalation
  - Container Escape
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for pods created with hostIPC set to true, which allows the pod to
  use the host's IPC namespace. This breaks isolation between the pod and the host system,
  giving the pod direct access to shared memory segments, semaphores, and message queues on
  the host. Attackers can abuse this to communicate with or interfere with processes on the
  host system or other containers using the same IPC namespace.
Runbook: |
  1. Review all pod creation events by the username in the 24 hours before the alert to establish normal deployment patterns
  2. Determine if the hostIPC setting is required for legitimate inter-process communication needs for this specific workload
  3. Search for other pods with hostIPC enabled deployed by this user across all clusters in the past 30 days
Reports:
  MITRE ATT&CK:
    - TA0004:T1611 # Privilege Escalation: Escape to Host
    - TA0005:T1562 # Defense Evasion: Impair Defenses
Reference: >
  - https://kubernetes.io/docs/concepts/security/pod-security-standards/
  - https://www.fairwinds.com/blog/kubernetes-basics-tutorial-host-ipc-should-not-be-configured
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • hostIPC is true

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Review all pod creation events by the username in the 24 hours before the alert to establish normal deployment patterns

2. Determine if the hostIPC setting is required for legitimate inter-process communication needs for this specific workload

3. Search for other pods with hostIPC enabled deployed by this user across all clusters in the past 30 days

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "debug-pod",
    "namespace": "default",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "Pod",
    "metadata": {
      "name": "debug-pod",
      "namespace": "default"
    },
    "spec": {
      "containers": [
        {
          "image": "busybox",
          "name": "debug"
        }
      ],
      "hostIPC": true
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "developer@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Pod Using Host PID Namespace

#
Severity
medium
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Escape to Host, Deploy Container, Unified Detection
Reference
- https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces - https://medium.com/@chrispisano/limiting-pod-privileges-hostpid-57ce07b05896
Source
github.com/panther-labs/panther-analysis

This detection monitors for any pod creation or modification using the host PID namespace. The Host PID namespace enables a pod and its containers to have direct access and share the same view as the host's processes. This can offer a powerful escape hatch to the underlying host.

MITRE ATT&CK coverage

TacticTechniques
Execution
Privilege Escalation

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check if hostPID is set to true in the request
    host_pid = event.udm("hostPID")
    if host_pid is True:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    return f"[{username}] created pod [{namespace}/{name}] using host PID namespace"


def alert_context(event):
    return k8s_alert_context(event, extra_fields=get_pod_context_fields(event))

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Host.PID"
DisplayName: "Kubernetes Pod Using Host PID Namespace"
Enabled: true
Filename: k8s_pod_host_pid.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Escape to Host
  - Deploy Container
  - Unified Detection
Severity: Medium
Description:
  This detection monitors for any pod creation or modification using the host PID namespace.
  The Host PID namespace enables a pod and its containers to have direct access and share the
  same view as the host's processes. This can offer a powerful escape hatch to the underlying host.
Runbook: |
  1. Query API calls by the username in the 24 hours before and after the alert to understand the deployment context and related activity
  2. Check if the pod namespace indicates system or infrastructure purpose (kube-system, kube-monitoring) versus user workloads
  3. Search for other host PID pod creations by this user in the past 30 days to determine if this is an established pattern or new behavior
Reports:
  MITRE ATT&CK:
    - TA0004:T1611 # Escape to Host
    - TA0002:T1610 # Deploy Container
Reference: >
  - https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces
  - https://medium.com/@chrispisano/limiting-pod-privileges-hostpid-57ce07b05896

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • hostPID is true

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Query API calls by the username in the 24 hours before and after the alert to understand the deployment context and related activity

2. Check if the pod namespace indicates system or infrastructure purpose (kube-system, kube-monitoring) versus user workloads

3. Search for other host PID pod creations by this user in the past 30 days to determine if this is an established pattern or new behavior

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "nginx-test",
    "namespace": "default",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "Pod",
    "spec": {
      "containers": [
        {
          "image": "nginx",
          "name": "nginx"
        }
      ],
      "hostPID": true
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "verb": "create"
}

Kubernetes Pod with Dangerous Linux Capabilities

#
Severity
high
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Privilege Escalation, Container Escape, Unified Detection
Reference
- https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ - https://www.dynatrace.com/news/blog/kubernetes-security-essentials-container-misconfigurations-from-theory-to-exploitation/
Source
github.com/panther-labs/panther-analysis

This detection monitors for pods created with dangerous Linux capabilities such as SYS_ADMIN, NET_ADMIN, or BPF. These capabilities can enable privilege escalation, container escape, or unauthorized access to host resources. Attackers often add these capabilities to containers to bypass security restrictions and gain elevated privileges on the underlying host.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_base_helpers import deep_get
from panther_kubernetes_helpers import (
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)

# Dangerous Linux capabilities that enable privilege escalation or container escape
DANGEROUS_CAPABILITIES = {
    "SYS_ADMIN",  # Most powerful
    "NET_ADMIN",  # Network manipulation
    "BPF",  # eBPF programs
    "SYS_PTRACE",  # Process tracing
    "SYS_MODULE",  # Load kernel modules
    "DAC_READ_SEARCH",  # Bypass file read permission checks
    "DAC_OVERRIDE",  # Bypass file permission checks
}


def has_dangerous_capabilities(containers):
    """Check if any container has dangerous Linux capabilities."""
    if not containers:
        return []

    dangerous_caps_found = []

    for container in containers:
        added_caps = deep_get(container, "securityContext", "capabilities", "add", default=[])

        if added_caps:
            # Check for intersection with dangerous capabilities
            dangerous = set(added_caps) & DANGEROUS_CAPABILITIES
            if dangerous:
                dangerous_caps_found.extend(list(dangerous))

    return dangerous_caps_found


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system namespaces and system principals to reduce false positives
    if is_system_namespace(namespace) or is_system_principal(username):
        return False

    # Check for dangerous capabilities
    containers = event.udm("containers") or []
    dangerous_caps = has_dangerous_capabilities(containers)

    if dangerous_caps:
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    containers = event.udm("containers") or []
    dangerous_caps = has_dangerous_capabilities(containers)
    caps_str = ", ".join(sorted(set(dangerous_caps)))

    return (
        f"[{username}] created pod [{namespace}/{name}] with dangerous capabilities "
        f"[{caps_str}]"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_dangerous_caps_{username}_{namespace}"


def alert_context(event):
    containers = event.udm("containers") or []
    dangerous_caps = has_dangerous_capabilities(containers)

    context_fields = get_pod_context_fields(event)
    context_fields["dangerous_capabilities"] = sorted(set(dangerous_caps))
    return k8s_alert_context(event, extra_fields=context_fields)

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Dangerous.Capabilities"
DisplayName: "Kubernetes Pod with Dangerous Linux Capabilities"
Enabled: true
Filename: k8s_pod_dangerous_capabilities.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Security Control
  - Privilege Escalation
  - Container Escape
  - Unified Detection
Severity: High
Description: >
  This detection monitors for pods created with dangerous Linux capabilities such as SYS_ADMIN,
  NET_ADMIN, or BPF. These capabilities can enable privilege escalation, container escape, or
  unauthorized access to host resources. Attackers often add these capabilities to containers
  to bypass security restrictions and gain elevated privileges on the underlying host.
Runbook: |
  1. Review all pod creation events by the username in the 24 hours before the alert to determine if this is routine deployment activity
  2. Analyze the specific capabilities granted and their legitimate business justification for the workload
  3. Search for other pods with dangerous capabilities deployed by this user across all clusters in the past 30 days
Reports:
  MITRE ATT&CK:
    - TA0004:T1611 # Privilege Escalation: Escape to Host
    - TA0005:T1068 # Defense Evasion: Exploitation for Privilege Escalation
Reference: >
  - https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
  - https://www.dynatrace.com/news/blog/kubernetes-security-essentials-container-misconfigurations-from-theory-to-exploitation/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
usernameis_not_null(no value, null check)excludes:username
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Review all pod creation events by the username in the 24 hours before the alert to determine if this is routine deployment activity

2. Analyze the specific capabilities granted and their legitimate business justification for the workload

3. Search for other pods with dangerous capabilities deployed by this user across all clusters in the past 30 days

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "test-pod",
    "namespace": "default",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "Pod",
    "spec": {
      "containers": [
        {
          "image": "nginx",
          "name": "test",
          "securityContext": {
            "capabilities": {
              "add": [
                "SYS_ADMIN",
                "NET_RAW"
              ]
            }
          }
        }
      ]
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Pod With HostPath Volume Mount

#
Severity
medium
Group by
namespace, username
Compliance
Stratus Red Team k8s.privilege-escalation.hostpath-volume
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Privilege Escalation, Data Exfiltration, Unified Detection
Reference
- https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces - https://medium.com/@vincn.ledan/understanding-the-risks-injecting-malicious-pods-via-hostpath-in-kubernetes-due-to-83f54a1bef31
Source
github.com/panther-labs/panther-analysis

This detection monitors for pod creation with a hostPath volume mount. The attachment to a node's volume can allow for privilege escalation through underlying vulnerabilities or it can open up possibilities for data exfiltration or unauthorized file access. It is very rare to see this being a pod requirement. System service accounts in the kube-system namespace are excluded to prevent false positives from legitimate system components.

MITRE ATT&CK coverage

TacticTechniques
Privilege Escalation
Exfiltration

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    get_hostpath_paths,
    get_pod_context_fields,
    get_pod_name,
    has_hostpath_volume,
    is_failed_request,
    is_sensitive_hostpath,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check for hostPath volumes
    volumes = event.udm("volumes") or []
    if has_hostpath_volume(volumes):
        return True

    return False


def severity(event):
    volumes = event.udm("volumes") or []
    paths = get_hostpath_paths(volumes)

    for path in paths:
        if is_sensitive_hostpath(path):
            return "HIGH"

    return "DEFAULT"


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    volumes = event.udm("volumes") or []
    paths = get_hostpath_paths(volumes)
    paths_str = ", ".join(paths) if paths else "unknown"

    return (
        f"[{username}] created pod [{namespace}/{name}] with hostPath volume mount "
        f"[{paths_str}]"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"hostpath_volume_{username}_{namespace}"


def alert_context(event):
    volumes = event.udm("volumes") or []
    pod_context = get_pod_context_fields(event)

    return k8s_alert_context(
        event,
        extra_fields={
            **pod_context,
            "hostpath_paths": get_hostpath_paths(volumes),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.HostPath.Volume"
DisplayName: "Kubernetes Pod With HostPath Volume Mount"
Enabled: true
Filename: k8s_pod_hostpath_volume.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Severity: Medium
Description: >
  This detection monitors for pod creation with a hostPath volume mount. The attachment to a
  node's volume can allow for privilege escalation through underlying vulnerabilities or it can
  open up possibilities for data exfiltration or unauthorized file access. It is very rare to see
  this being a pod requirement. System service accounts in the kube-system namespace are excluded
  to prevent false positives from legitimate system components.
Runbook: |
  1. Review all pod creation events by the username in the 24 hours before the alert to establish baseline deployment behavior
  2. Check if the hostPath volume is mounted from sensitive paths (/, /var, /sys, /proc, /etc) which pose higher security risk
  3. Search for other hostPath volume mounts by this user in the past 30 days and compare the paths being accessed to identify patterns
Reference: >
  - https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces
  - https://medium.com/@vincn.ledan/understanding-the-risks-injecting-malicious-pods-via-hostpath-in-kubernetes-due-to-83f54a1bef31
Reports:
  Stratus Red Team:
    - k8s.privilege-escalation.hostpath-volume
  MITRE ATT&CK:
    - TA0010:T1041 # Exfiltration Over C2 Channel
    - TA0004:T1611 # Escape to Host
DedupPeriodMinutes: 360
Tags:
  - Kubernetes
  - Security Control
  - Privilege Escalation
  - Data Exfiltration
  - Unified Detection

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • volumes is present
  • any element of volumes matches:
    • volumes contains hostPath

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert deduplication
repeat matches within 6h group into one alert

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"
volumesis_not_null
  • (no value, null check)
field:"volumes" kind:is_not_null

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Review all pod creation events by the username in the 24 hours before the alert to establish baseline deployment behavior

2. Check if the hostPath volume is mounted from sensitive paths (/, /var, /sys, /proc, /etc) which pose higher security risk

3. Search for other hostPath volume mounts by this user in the past 30 days and compare the paths being accessed to identify patterns

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "test",
    "namespace": "default",
    "resource": "pods"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
      "name": "test",
      "namespace": "default"
    },
    "spec": {
      "containers": [
        {
          "image": "nginx",
          "name": "test",
          "volumeMounts": [
            {
              "mountPath": "/test",
              "name": "test-volume"
            }
          ]
        }
      ],
      "volumes": [
        {
          "hostPath": {
            "path": "/var/lib/kubelet",
            "type": "DirectoryOrCreate"
          },
          "name": "test-volume"
        }
      ]
    }
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "user": {
    "username": "user@company.com"
  },
  "userAgent": "kubectl/v1.28.2",
  "verb": "create"
}

Kubernetes Privileged Pod Created

#
Severity
high
Group by
username
Compliance
Stratus Red Team k8s.privilege-escalation.privileged-pod
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Security Control, Privilege Escalation, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

Detects creation of privileged pods across Kubernetes clusters. Privileged pods have full access to the host's namespace and devices, have the ability to exploit the kernel, have dangerous linux capabilities, and can be a powerful launching point for further attacks. In the event of a successful container escape where a user is operating with root privileges, the attacker retains this role on the node.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Kubernetescreate-pods: create pods

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    get_pod_context_fields,
    get_pod_name,
    is_failed_request,
    is_privileged_container,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check pod creation events
    if verb != "create" or resource != "pods":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals creating pods in system namespaces (legitimate)
    # but alert on system principals in user namespaces (malicious Deployments)
    # and alert on user-created pods in system namespaces (suspicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check request object for privileged containers
    containers = event.udm("containers") or []
    if is_privileged_container(containers):
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = get_pod_name(event)

    return f"[{username}] created a privileged pod [{namespace}/{name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    return f"privileged_pod_{username}"


def alert_context(event):
    return k8s_alert_context(event, extra_fields=get_pod_context_fields(event))

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.Privileged.Created"
DisplayName: "Kubernetes Privileged Pod Created"
Enabled: true
Filename: k8s_privileged_pod_created.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Severity: High
Description: >
  Detects creation of privileged pods across Kubernetes clusters. Privileged pods have full access
  to the host's namespace and devices, have the ability to exploit the kernel, have dangerous linux
  capabilities, and can be a powerful launching point for further attacks. In the event of a
  successful container escape where a user is operating with root privileges, the attacker retains
  this role on the node.
Runbook: |
  1. Check if the username who created the privileged pod has a history of deploying system infrastructure in the past 90 days
  2. Review all API calls by this username in the 6 hours before the alert to understand context and related activity
  3. Query for other privileged pods created by the same user in the past 30 days to identify if this is an established pattern
Reference: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
Reports:
  Stratus Red Team:
    - k8s.privilege-escalation.privileged-pod
  MITRE ATT&CK:
    - TA0004:T1548.003 # Abuse Elevation Control Mechanism: Sudo and Sudo Caching
DedupPeriodMinutes: 360
Tags:
  - Kubernetes
  - Security Control
  - Privilege Escalation
  - Unified Detection

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • containers is present

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert deduplication
repeat matches within 6h group into one alert

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
containersis_not_null
  • (no value, null check)
field:"containers" kind:is_not_null
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Check if the username who created the privileged pod has a history of deploying system infrastructure in the past 90 days

2. Review all API calls by this username in the 6 hours before the alert to understand context and related activity

3. Query for other privileged pods created by the same user in the past 30 days to identify if this is an established pattern

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "auditID": "abc-123",
  "kind": "Event",
  "level": "RequestResponse",
  "objectRef": {
    "apiVersion": "v1",
    "name": "test-privileged-pod",
    "namespace": "default",
    "resource": "pods"
  },
  "p_event_time": "2024-02-13 12:45:06.073",
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
      "name": "test-privileged-pod",
      "namespace": "default"
    },
    "spec": {
      "containers": [
        {
          "image": "nginx",
          "name": "nginx",
          "securityContext": {
            "privileged": true
          }
        }
      ]
    }
  },
  "requestURI": "/api/v1/namespaces/default/pods",
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "stage": "ResponseComplete",
  "user": {
    "groups": [
      "system:authenticated"
    ],
    "username": "john.doe@company.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Role With Node Proxy Permissions Created

#
Severity
high
Group by
name, username
Compliance
Stratus Red Team k8s.privilege-escalation.nodes-proxy
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, RBAC, Stratus Red Team, Unified Detection
Reference
stratus-red-team.cloud
Source
github.com/panther-labs/panther-analysis

This detection monitors for Roles or ClusterRoles being created with permissions to access node proxy endpoints (nodes/proxy or nodes/*). These permissions allow users to access the kubelet API through the Kubernetes API server proxy, enabling privilege escalation by executing commands on nodes, accessing container logs and filesystems, and potentially escaping to the underlying host. This technique is documented by Stratus Red Team as a privilege escalation vector.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check role/clusterrole creation events
    if verb != "create" or resource not in {"roles", "clusterroles"}:
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals to reduce false positives
    if is_system_principal(username):
        return False

    # Check request object for node/proxy permissions
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    for rule_config in rules:
        resources = rule_config.get("resources") or []
        verbs = rule_config.get("verbs") or []

        # Check for nodes/proxy or nodes/* permissions
        # These allow accessing the kubelet API through the API server proxy
        if "nodes/proxy" in resources or ("nodes/*" in resources and verbs):
            return True

        # Also check for wildcard on nodes with specific verbs that enable proxy access
        if "nodes" in resources and "*" in resources:
            return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"

    role_type = "ClusterRole" if resource == "clusterroles" else "Role"

    return f"[{username}] created {role_type} [{name}] with node proxy permissions"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    return f"k8s_node_proxy_{username}_{name}"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    # Extract rules with node/proxy permissions
    dangerous_rules = []
    for rule_config in rules:
        resources = rule_config.get("resources") or []
        if "nodes/proxy" in resources or "nodes/*" in resources or "nodes" in resources:
            dangerous_rules.append(rule_config)

    return k8s_alert_context(
        event,
        extra_fields={
            "role_name": event.udm("name"),
            "role_type": event.udm("resource"),
            "dangerous_rules": dangerous_rules,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Role.NodeProxyPermissions"
DisplayName: "Kubernetes Role With Node Proxy Permissions Created"
Enabled: true
Filename: k8s_role_node_proxy_permissions.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Privilege Escalation
  - RBAC
  - Stratus Red Team
  - Unified Detection
Severity: High
Description: >
  This detection monitors for Roles or ClusterRoles being created with permissions to access node proxy endpoints
  (nodes/proxy or nodes/*). These permissions allow users to access the kubelet API through the Kubernetes API server
  proxy, enabling privilege escalation by executing commands on nodes, accessing container logs and filesystems, and
  potentially escaping to the underlying host. This technique is documented by Stratus Red Team as a privilege
  escalation vector.
Runbook: |
  1. Query all RBAC operations by the username in the 2 hours before and after the alert to identify the scope of role creation activity
  2. Find all RoleBindings or ClusterRoleBindings that reference this role name in the 24 hours after creation to determine who was granted these permissions
  3. Search for API operations to nodes/proxy endpoints from the p_source_label cluster in the past 7 days to identify if these permissions have been exploited
Reports:
  Stratus Red Team:
    - k8s.privilege-escalation.nodes-proxy
  MITRE ATT&CK:
    - TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
    - TA0005:T1562.001 # Defense Evasion: Impair Defenses - Disable or Modify Tools
Reference: https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.privilege-escalation.nodes-proxy/
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - resource
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is one of roles, clusterroles
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
resourcein
  • clusterroles
  • roles
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Query all RBAC operations by the username in the 2 hours before and after the alert to identify the scope of role creation activity

2. Find all RoleBindings or ClusterRoleBindings that reference this role name in the 24 hours after creation to determine who was granted these permissions

3. Search for API operations to nodes/proxy endpoints from the p_source_label cluster in the past 7 days to identify if these permissions have been exploited

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "node-accessor",
    "resource": "clusterroles"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRole",
    "metadata": {
      "name": "node-accessor"
    },
    "rules": [
      {
        "apiGroups": [
          ""
        ],
        "resources": [
          "nodes/proxy"
        ],
        "verbs": [
          "get",
          "list",
          "create"
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Role With Pod Exec Permissions Created

#
Severity
high
Group by
name, resource, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, Lateral Movement, RBAC, Unified Detection
Reference
- https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources - https://seifrajhi.github.io/blog/kubernetes-rbac-privilege-escalation-mitigation/#%EF%B8%8F-rolebinding-permissions-in-kubernetes-implications-and-safeguards
Source
github.com/panther-labs/panther-analysis

This detection monitors for Roles or ClusterRoles being created that grant permissions to exec into pods. The pods/exec subresource allows executing arbitrary commands inside containers, which can be abused for lateral movement, credential theft, or container escape. Attackers who gain RBAC modification permissions often create roles with pods/exec to establish backdoor access for executing commands across the cluster.

MITRE ATT&CK coverage

TacticTechniques
Privilege Escalation
Lateral Movement

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check Role/ClusterRole creation events
    if verb != "create" or resource not in {"roles", "clusterroles"}:
        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 role grants pods/exec permissions
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    for rule_entry in rules:
        resources = rule_entry.get("resources") or []
        # Check for pods/exec subresource
        if "pods/exec" in resources:
            return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    namespace = event.udm("namespace") or "<CLUSTER_SCOPED>"

    role_type = "ClusterRole" if resource == "clusterroles" else "Role"

    if namespace != "<CLUSTER_SCOPED>":
        return f"[{username}] created {role_type} [{namespace}/{name}] with pods/exec permissions"

    return f"[{username}] created {role_type} [{name}] with pods/exec permissions "


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    return f"k8s_role_podexec_{username}_{resource}_{name}"


def severity(event):
    """ClusterRoles are more dangerous than namespaced Roles."""
    resource = event.udm("resource") or ""

    # Critical for ClusterRole (cluster-wide exec permissions)
    if resource == "clusterroles":
        return "CRITICAL"

    # High for namespaced Role (namespace-scoped exec permissions)
    return "HIGH"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    # Extract only the rules that grant pods/exec
    exec_rules = []
    for rule_entry in rules:
        if "pods/exec" in (rule_entry.get("resources") or []):
            exec_rules.append(rule_entry)

    return k8s_alert_context(
        event,
        extra_fields={
            "role_name": event.udm("name"),
            "role_type": event.udm("resource"),
            "exec_rules": exec_rules,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Role.PodExec"
DisplayName: "Kubernetes Role With Pod Exec Permissions Created"
Enabled: true
Filename: k8s_role_pod_exec.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Privilege Escalation
  - Lateral Movement
  - RBAC
  - Unified Detection
Severity: High
Description: >
  This detection monitors for Roles or ClusterRoles being created that grant permissions to exec into pods.
  The pods/exec subresource allows executing arbitrary commands inside containers, which can be abused for
  lateral movement, credential theft, or container escape. Attackers who gain RBAC modification permissions
  often create roles with pods/exec to establish backdoor access for executing commands across the cluster.
Runbook: |
  1. Review the role rules to identify what verbs and API groups were granted alongside pods/exec and determine if this role creation is expected
  2. Identify all API operations by the creating user in the 2 hours before and after the alert and search for any RoleBindings or ClusterRoleBindings that reference this role
  3. Search for all exec operations across the cluster in the past 24 hours to identify if this permission has already been abused
Reports:
  MITRE ATT&CK:
    - TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
    - TA0008:T1021 # Lateral Movement: Remote Services
Reference: >
  - https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources
  - https://seifrajhi.github.io/blog/kubernetes-rbac-privilege-escalation-mitigation/#%EF%B8%8F-rolebinding-permissions-in-kubernetes-implications-and-safeguards
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - resource
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is one of roles, clusterroles
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • any element of requestObject.rules matches:
    • requestObject.rules.resources contains pods/exec

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
resourcein
  • clusterroles
  • roles
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review the role rules to identify what verbs and API groups were granted alongside pods/exec and determine if this role creation is expected

2. Identify all API operations by the creating user in the 2 hours before and after the alert and search for any RoleBindings or ClusterRoleBindings that reference this role

3. Search for all exec operations across the cluster in the past 24 hours to identify if this permission has already been abused

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "pod-exec-role",
    "resource": "clusterroles"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRole",
    "metadata": {
      "name": "pod-exec-role"
    },
    "rules": [
      {
        "apiGroups": [
          ""
        ],
        "resources": [
          "pods/exec"
        ],
        "verbs": [
          "create",
          "get"
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Role With Wildcard Permissions Created

#
Status
Experimental
Severity
high
Group by
name, resource, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, Excessive Permissions, RBAC, Unified Detection
Reference
- https://kubernetes.io/docs/reference/access-authn-authz/rbac/#privilege-escalation-prevention-and-bootstrapping - https://hub.datree.io/built-in-rules/prevent-wildcards-role-clusterrole
Source
github.com/panther-labs/panther-analysis

This detection monitors for Roles or ClusterRoles being created with wildcard (*) permissions in resources or verbs. Wildcard permissions grant overly broad access, such as all operations on all resources, which violates the principle of least privilege. Attackers who gain RBAC modification permissions often create wildcard roles to maximize their access across the cluster without knowing specific resource names or API operations.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check Role/ClusterRole creation events
    if verb != "create" or resource not in {"roles", "clusterroles"}:
        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 role grants wildcard permissions
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    for rule_entry in rules:
        resources = rule_entry.get("resources") or []
        verbs = rule_entry.get("verbs") or []

        # Check for wildcard in resources or verbs
        if "*" in resources or "*" in verbs:
            return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    namespace = event.udm("namespace") or "<CLUSTER_SCOPED>"

    role_type = "ClusterRole" if resource == "clusterroles" else "Role"

    if namespace != "<CLUSTER_SCOPED>":
        return f"[{username}] created {role_type} [{namespace}/{name}] with wildcard permissions"

    return f"[{username}] created {role_type} [{name}] with wildcard permissions "


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    return f"k8s_role_wildcard_{username}_{resource}_{name}"


def severity(event):
    """ClusterRoles with wildcards are more dangerous than namespaced Roles."""
    resource = event.udm("resource") or ""

    # Critical for ClusterRole (cluster-wide wildcard permissions)
    if resource == "clusterroles":
        return "CRITICAL"

    # High for namespaced Role (namespace-scoped wildcard permissions)
    return "HIGH"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    # Extract only the rules that contain wildcards
    wildcard_rules = []
    for rule_entry in rules:
        resources = rule_entry.get("resources") or []
        verbs = rule_entry.get("verbs") or []
        if "*" in resources or "*" in verbs:
            wildcard_rules.append(rule_entry)

    return k8s_alert_context(
        event,
        extra_fields={
            "role_name": event.udm("name"),
            "role_type": event.udm("resource"),
            "wildcard_rules": wildcard_rules,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Role.Wildcard"
DisplayName: "Kubernetes Role With Wildcard Permissions Created"
Enabled: true
Status: Experimental
Filename: k8s_role_wildcard.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Privilege Escalation
  - Excessive Permissions
  - RBAC
  - Unified Detection
Severity: High
Description: >
  This detection monitors for Roles or ClusterRoles being created with wildcard (*) permissions in resources
  or verbs. Wildcard permissions grant overly broad access, such as all operations on all resources, which
  violates the principle of least privilege. Attackers who gain RBAC modification permissions often create
  wildcard roles to maximize their access across the cluster without knowing specific resource names or API operations.
Runbook: |
  1. Review the role rules to identify the scope of wildcard permissions and determine if this role creation is expected
  2. Identify all API operations by the creating user in the 2 hours before and after the alert and search for RoleBindings or ClusterRoleBindings that reference this role
  3. If unauthorized, immediately delete the role and any bindings, then audit all API activity in the cluster in the past 24 hours to assess impact
Reports:
  MITRE ATT&CK:
    - TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
    - TA0005:T1078.004 # Defense Evasion: Valid Accounts - Cloud Accounts
Reference: >
  - https://kubernetes.io/docs/reference/access-authn-authz/rbac/#privilege-escalation-prevention-and-bootstrapping
  - https://hub.datree.io/built-in-rules/prevent-wildcards-role-clusterrole
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - resource
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is one of roles, clusterroles
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • any element of requestObject.rules matches:
    • any of:
      • requestObject.rules.resources contains *
      • requestObject.rules.verbs contains *

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
resourcein
  • clusterroles
  • roles
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review the role rules to identify the scope of wildcard permissions and determine if this role creation is expected

2. Identify all API operations by the creating user in the 2 hours before and after the alert and search for RoleBindings or ClusterRoleBindings that reference this role

3. If unauthorized, immediately delete the role and any bindings, then audit all API activity in the cluster in the past 24 hours to assess impact

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "super-admin",
    "resource": "clusterroles"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRole",
    "metadata": {
      "name": "super-admin"
    },
    "rules": [
      {
        "apiGroups": [
          "*"
        ],
        "resources": [
          "*"
        ],
        "verbs": [
          "*"
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Role With Write Permissions Created

#
Status
Experimental
Severity
low
Group by
name, resource, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, RBAC, Configuration Management, Unified Detection
Reference
- https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole - https://www.paloaltonetworks.ca/cyberpedia/kubernetes-rbac#roles
Source
github.com/panther-labs/panther-analysis

This detection monitors for Roles or ClusterRoles being created with write permissions (create, update, patch, delete, deletecollection). While write permissions are common and often necessary for application operations, tracking role creation helps establish RBAC baselines and identify overly permissive configurations. Severity escalates for write access to sensitive resources like secrets or RBAC objects.

MITRE ATT&CK coverage

TacticTechniques
Defense Impairment

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context

# Write-related verbs that modify cluster state
WRITE_VERBS = {
    "create",
    "update",
    "patch",
    "delete",
    "deletecollection",
}


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check Role/ClusterRole creation events
    if verb != "create" or resource not in {"roles", "clusterroles"}:
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals to reduce noise from legitimate operators
    if is_system_principal(username):
        return False

    # Check if role grants write permissions
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    for rule_entry in rules:
        verbs = rule_entry.get("verbs") or []
        # Check if any write verb is present
        if any(verb in WRITE_VERBS for verb in verbs):
            return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    namespace = event.udm("namespace") or "<CLUSTER_SCOPED>"

    role_type = "ClusterRole" if resource == "clusterroles" else "Role"

    if namespace != "<CLUSTER_SCOPED>":
        return f"[{username}] created {role_type} [{namespace}/{name}] with write " f"permissions"

    return f"[{username}] created {role_type} [{name}] with write permissions " f""


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    return f"k8s_role_write_{username}_{resource}_{name}"


def severity(event):
    """Increase severity for dangerous combinations of write permissions."""
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []
    resource = event.udm("resource") or ""

    # Check for high-risk resource + write verb combinations
    for rule_entry in rules:
        resources_list = rule_entry.get("resources") or []
        verbs = rule_entry.get("verbs") or []

        # Critical: Write access to secrets or RBAC resources
        sensitive_resources = {
            "secrets",
            "clusterroles",
            "clusterrolebindings",
            "roles",
            "rolebindings",
        }
        if any(res in sensitive_resources for res in resources_list) and any(
            v in {"create", "update", "patch", "delete"} for v in verbs
        ):
            return "CRITICAL"

        # High: ClusterRole with write to pods or nodes
        if resource == "clusterroles" and any(
            res in {"pods", "nodes", "persistentvolumes"} for res in resources_list
        ):
            if any(v in {"create", "update", "patch", "delete"} for v in verbs):
                return "HIGH"

    # Medium: ClusterRole with general write permissions
    if resource == "clusterroles":
        return "MEDIUM"

    # Low: Namespaced Role with write permissions (common/expected)
    return "LOW"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    rules = request_object.get("rules") or []

    # Extract only the rules that contain write verbs
    write_rules = []
    for rule_entry in rules:
        verbs = rule_entry.get("verbs") or []
        if any(verb in WRITE_VERBS for verb in verbs):
            write_rules.append(rule_entry)

    return k8s_alert_context(
        event,
        extra_fields={
            "role_name": event.udm("name"),
            "role_type": event.udm("resource"),
            "write_rules": write_rules,
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Role.WritePermissions"
DisplayName: "Kubernetes Role With Write Permissions Created"
Enabled: true
Status: Experimental
Filename: k8s_role_write_permissions.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - RBAC
  - Configuration Management
  - Unified Detection
Severity: Low
Description: >
  This detection monitors for Roles or ClusterRoles being created with write permissions (create, update, patch,
  delete, deletecollection). While write permissions are common and often necessary for application operations,
  tracking role creation helps establish RBAC baselines and identify overly permissive configurations. Severity
  escalates for write access to sensitive resources like secrets or RBAC objects.
Runbook: |
  1. Review the role rules to identify what resources have write permissions and determine if this role creation is expected for the user or application
  2. Search for RoleBindings or ClusterRoleBindings that reference this role in the past 24 hours to identify who will receive these permissions
  3. If the role grants excessive permissions, work with the team to implement least-privilege access and replace with a more restrictive role
Reports:
  MITRE ATT&CK:
    - TA0005:T1222 # Defense Evasion: File and Directory Permissions Modification
Reference: >
  - https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
  - https://www.paloaltonetworks.ca/cyberpedia/kubernetes-rbac#roles
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - resource
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is one of roles, clusterroles
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
resourcein
  • clusterroles
  • roles
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Review the role rules to identify what resources have write permissions and determine if this role creation is expected for the user or application

2. Search for RoleBindings or ClusterRoleBindings that reference this role in the past 24 hours to identify who will receive these permissions

3. If the role grants excessive permissions, work with the team to implement least-privilege access and replace with a more restrictive role

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "secret-manager",
    "resource": "clusterroles"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRole",
    "metadata": {
      "name": "secret-manager"
    },
    "rules": [
      {
        "apiGroups": [
          ""
        ],
        "resources": [
          "secrets"
        ],
        "verbs": [
          "get",
          "list",
          "delete"
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 201
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "admin@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes Secret Access Denied

#
Status
Experimental
Severity
medium
Group by
namespace, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Credential Access, Enumeration, Secrets Management, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for failed attempts to read Kubernetes secrets. While occasional failed access attempts may indicate RBAC misconfigurations, repeated failures suggest enumeration or brute-force attempts by compromised accounts. With 15-minute deduplication, 20 or more failed attempts within this window indicates active secret enumeration and should be investigated immediately.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Kubernetesget-secrets: get secrets

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check secret get operations
    if verb != "get" or resource != "secrets":
        return False

    # Only alert on failed requests (access denied)
    if not is_failed_request(response_status):
        return False

    # Exclude system namespaces and system principals
    if is_system_namespace(namespace) or is_system_principal(username):
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_SECRET>"
    response_status = event.udm("responseStatus") or {}
    status_code = response_status.get("code", "UNKNOWN")

    return (
        f"[{username}] failed secret enumeration attempt "
        f"in [{namespace}/{name}] "
        f"(response: {status_code})"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_secret_denied_{username}_{namespace}"


def alert_context(event):
    response_status = event.udm("responseStatus") or {}

    return k8s_alert_context(
        event,
        extra_fields={
            "secret_name": event.udm("name"),
            "response_code": response_status.get("code"),
            "response_message": response_status.get("message"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Secret.AccessDenied"
DisplayName: "Kubernetes Secret Access Denied"
Enabled: true
Status: Experimental
Filename: k8s_secret_access_denied.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Credential Access
  - Enumeration
  - Secrets Management
  - Unified Detection
Severity: Medium
Description: >
  This detection monitors for failed attempts to read Kubernetes secrets. While occasional failed access attempts
  may indicate RBAC misconfigurations, repeated failures suggest enumeration or brute-force attempts by compromised
  accounts. With 15-minute deduplication, 20 or more failed attempts within this window indicates active secret
  enumeration and should be investigated immediately.
Runbook: |
  1. Review the total count of failed secret access attempts for this user in the 15-minute dedup window and identify all secrets they attempted to access
  2. Determine if the user or service account should have access to any secrets and check if this represents a misconfiguration or malicious enumeration
  3. Search for successful secret access by this user in the past 24 hours and review all other API operations to identify if the account is compromised
Reports:
  MITRE ATT&CK:
    - TA0006:T1552.007 # Credential Access: Unsecured Credentials - Container API
    - TA0007:T1613 # Discovery: Container and Resource Discovery
Reference: https://kubernetes.io/docs/concepts/configuration/secret/
DedupPeriodMinutes: 15
Threshold: 20
SummaryAttributes:
  - username
  - namespace
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is get
  • resource is secrets
  • responseStatus is present
  • any of:
    • responseStatus.code is at least 400
    • all of:
      • responseStatus.code is at least 1
      • responseStatus.code is at most 16
  • any of:
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert cadence
alerts after 20 matches within 15m

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
usernameis_not_null(no value, null check)excludes:username
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
resourcenesecretsexcludes:resource field:"resource" value:"secrets"
verbnegetexcludes:verb field:"verb" value:"get"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name
coderesponseStatus.code

Response runbook

1. Review the total count of failed secret access attempts for this user in the 15-minute dedup window and identify all secrets they attempted to access

2. Determine if the user or service account should have access to any secrets and check if this represents a misconfiguration or malicious enumeration

3. Search for successful secret access by this user in the past 24 hours and review all other API operations to identify if the account is compromised

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "db-password",
    "namespace": "production",
    "resource": "secrets"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "responseStatus": {
    "code": 403,
    "message": "Forbidden: User cannot get resource secrets in namespace production"
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "get"
}

Kubernetes Secret Enumeration by a User

#
Status
Experimental
Severity
medium
Group by
username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Credential Access
Source
github.com/panther-labs/panther-analysis

Detects when a single user accesses 15 or more distinct secrets within 30 minutes using list, get, or watch verbs. This may indicate secret enumeration to enable lateral or vertical movement and unauthorized access to critical resources. The threshold should be tuned to your environment.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context

SECRET_VERBS = {"list", "get", "watch"}


def rule(event):
    if event.udm("verb") not in SECRET_VERBS:
        return False
    if event.udm("resource") != "secrets":
        return False
    if is_system_principal(event.udm("username") or ""):
        return False
    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    return f"Kubernetes Secret Enumeration by [{username}]"


def dedup(event):
    return event.udm("username") or "<UNKNOWN_USER>"


def unique(event):
    secret_name = event.udm("name")
    if secret_name:
        return secret_name

    # List/watch requests target secret collections and often omit objectRef.name.
    verb = event.udm("verb")
    if verb in ("list", "watch"):
        namespace = event.udm("namespace")
        if namespace:
            return f"{verb}:{namespace}"
        request_uri = event.udm("requestURI")
        if request_uri:
            return f"{verb}:{request_uri}"
        return verb
    return None


def severity(event):
    if not is_failed_request(event.udm("responseStatus")):
        return "HIGH"
    return "DEFAULT"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "secret_name": event.udm("name"),
            "verb": event.udm("verb"),
            "user_agent": event.udm("userAgent"),
        },
    )

Rule specification

AnalysisType: rule
Filename: k8s_secret_enumeration.py
RuleID: "Kubernetes.BulkSecretAccess"
DisplayName: "Kubernetes Secret Enumeration by a User"
Status: Experimental
Enabled: false
Severity: Medium
DedupPeriodMinutes: 30
Threshold: 15
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Credential Access
Description: >
  Detects when a single user accesses 15 or more distinct secrets within 30 minutes
  using list, get, or watch verbs. This may indicate secret enumeration to enable
  lateral or vertical movement and unauthorized access to critical resources.
  The threshold should be tuned to your environment.
Reports:
  MITRE ATT&CK:
    - TA0006:T1552.007
Runbook: |
  1. Query Amazon.EKS.Audit for all secret access events by the username and userAgent in the 30 minutes around this alert to identify the full list of secrets accessed and the verbs used
  2. Determine if the user or service account has a legitimate reason to access this volume of secrets, and check if the responseStatus codes indicate successful reads or denied attempts
  3. Search for other suspicious API activity by this username in the past 24 hours, including privilege escalation attempts, role or clusterrole binding changes, or unusual resource access patterns

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is one of list, get, watch
  • resource is secrets
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert cadence
alerts after 15 matches within 30m

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
usernameis_not_null(no value, null check)excludes:username

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Query Amazon.EKS.Audit for all secret access events by the username and userAgent in the 30 minutes around this alert to identify the full list of secrets accessed and the verbs used

2. Determine if the user or service account has a legitimate reason to access this volume of secrets, and check if the responseStatus codes indicate successful reads or denied attempts

3. Search for other suspicious API activity by this username in the past 24 hours, including privilege escalation attempts, role or clusterrole binding changes, or unusual resource access patterns

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "db-credentials",
    "namespace": "production",
    "resource": "secrets"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "responseStatus": {
    "code": 200
  },
  "stage": "ResponseComplete",
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "get"
}

Kubernetes Service Account Token Theft from Pod

#
Severity
high
Group by
name, namespace, username
Compliance
Stratus Red Team k8s.credential-access.steal-serviceaccount-token
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Credential Access, Privilege Escalation, Lateral Movement, Unified Detection
Reference
- https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.credential-access.steal-serviceaccount-token/ - https://medium.com/google-cloud/from-whoami-to-whoarewe-with-gke-workload-identity-for-fleets-3795ee942187 - https://dev.to/piyushjajoo/understanding-kubernetes-projected-service-account-tokens-205f
Source
github.com/panther-labs/panther-analysis

This detection monitors for commands executed in pods that attempt to read service account tokens from /var/run/secrets/kubernetes.io/serviceaccount/token. Attackers who gain exec access to a pod can steal its service account token to authenticate as that service account to the Kubernetes API server. This enables privilege escalation and lateral movement within the cluster. This is a known attack technique documented by Stratus Red Team.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from urllib.parse import unquote

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)

# Paths related to service account tokens that attackers target
SERVICE_ACCOUNT_TOKEN_PATHS = {
    # Standard Kubernetes service account token
    "/var/run/secrets/kubernetes.io/serviceaccount/token",
    "/var/run/secrets/kubernetes.io/serviceaccount",
    # AWS EKS with IRSA (IAM Roles for Service Accounts)
    "/var/run/secrets/eks.amazonaws.com/serviceaccount/token",
    "/var/run/secrets/eks.amazonaws.com/serviceaccount",
    # Azure AKS with Workload Identity
    "/var/run/secrets/azure/tokens/azure-identity-token",
    "/var/run/secrets/azure/tokens",
    # GCP GKE with Workload Identity
    "/var/run/secrets/tokens/gcp-ksa/token",
    "/var/run/secrets/tokens/gcp-ksa",
    # Partial path match for various token access patterns
    "serviceaccount/token",
}


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    subresource = event.udm("subresource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check exec subresource operations
    if verb != "create" or resource != "pods" or subresource != "exec":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals in system namespaces (legitimate operations)
    # but alert on users stealing tokens from system namespaces (malicious)
    if is_system_principal(username) and is_system_namespace(namespace):
        return False

    # Check command in requestObject
    request_object = event.udm("requestObject") or {}
    command = request_object.get("command", [])
    command_str = " ".join(str(cmd) for cmd in command).lower()

    # Check if command references service account token paths
    if any(path.lower() in command_str for path in SERVICE_ACCOUNT_TOKEN_PATHS):
        return True

    # Check requestURI (may contain URL-encoded paths)
    request_uri = event.udm("requestURI") or ""
    # URL decode to handle encoded characters like %2F for /
    decoded_uri = unquote(request_uri).lower()

    if any(path.lower() in decoded_uri for path in SERVICE_ACCOUNT_TOKEN_PATHS):
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_POD>"

    return (
        f"[{username}] attempted to steal service account token from pod " f"[{namespace}/{name}]"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_POD>"
    return f"k8s_steal_token_{username}_{namespace}_{name}"


def alert_context(event):
    request_object = event.udm("requestObject") or {}
    command = request_object.get("command", [])
    request_uri = event.udm("requestURI") or ""

    return k8s_alert_context(
        event,
        extra_fields={
            "pod_name": event.udm("name"),
            "command": command,
            "request_uri": request_uri,
            "container": request_object.get("container"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.Pod.StealServiceAccountToken"
DisplayName: "Kubernetes Service Account Token Theft from Pod"
Enabled: true
Filename: k8s_steal_serviceaccount_token.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Credential Access
  - Privilege Escalation
  - Lateral Movement
  - Unified Detection
Severity: High
Description: >
  This detection monitors for commands executed in pods that attempt to read service account tokens from
  /var/run/secrets/kubernetes.io/serviceaccount/token. Attackers who gain exec access to a pod can steal
  its service account token to authenticate as that service account to the Kubernetes API server. This
  enables privilege escalation and lateral movement within the cluster. This is a known attack technique
  documented by Stratus Red Team.
Runbook: |
  1. Immediately investigate the user executing commands in the pod and determine if they are authorized to access this pod
  2. Review the service account permissions for this pod to assess what privileges the attacker could gain with the stolen token
  3. Search for API operations using the stolen service account token in the past 24 hours by filtering audit logs for this service account principal
Reports:
  Stratus Red Team:
    - k8s.credential-access.steal-serviceaccount-token
  MITRE ATT&CK:
    - TA0006:T1552.007 # Credential Access: Unsecured Credentials - Container API
    - TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
    - TA0008:T1550.001 # Lateral Movement: Use Alternate Authentication Material - Application Access Token
Reference: >
  - https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.credential-access.steal-serviceaccount-token/
  - https://medium.com/google-cloud/from-whoami-to-whoarewe-with-gke-workload-identity-for-fleets-3795ee942187
  - https://dev.to/piyushjajoo/understanding-kubernetes-projected-service-account-tokens-205f
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - namespace
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is create
  • resource is pods
  • subresource is exec
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
    • namespace is empty
    • namespace is not one of kube-system, gke-system, kube-node-lease, kube-public

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
usernamecontainsserviceaccountexcludes:username field:"username" value:"serviceaccount"
usernamestarts_withsystem:excludes:username field:"username" value:"system:"
usernameinaksService, masterclientexcludes:username field:"username" value:"aksService" field:"username" value:"masterclient"
namespaceingke-system, kube-node-lease, kube-public, kube-systemexcludes:namespace
namespaceis_not_null(no value, null check)excludes:namespace
usernameis_not_null(no value, null check)excludes:username
responseStatus.codege1excludes:responseStatus.code field:"responseStatus.code" value:"1"
responseStatus.codele16excludes:responseStatus.code field:"responseStatus.code" value:"16"
responseStatus.codege400excludes:responseStatus.code field:"responseStatus.code" value:"400"
responseStatusis_not_null(no value, null check)excludes:responseStatus
resourcenepodsexcludes:resource field:"resource" value:"pods"
subresourceneexecexcludes:subresource field:"subresource" value:"exec"
verbnecreateexcludes:verb field:"verb" value:"create"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Immediately investigate the user executing commands in the pod and determine if they are authorized to access this pod

2. Review the service account permissions for this pod to assess what privileges the attacker could gain with the stolen token

3. Search for API operations using the stolen service account token in the past 24 hours by filtering audit logs for this service account principal

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "name": "webapp-pod",
    "namespace": "production",
    "resource": "pods",
    "subresource": "exec"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "command": [
      "cat",
      "/var/run/secrets/kubernetes.io/serviceaccount/token"
    ],
    "container": "webapp",
    "stdin": false,
    "stdout": true,
    "tty": false
  },
  "requestURI": "/api/v1/namespaces/production/pods/webapp-pod/exec?command=cat&command=%2Fvar%2Frun%2Fsecrets%2Fkubernetes.io%2Fserviceaccount%2Ftoken&stdout=true",
  "responseStatus": {
    "code": 101
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "create"
}

Kubernetes System Principal Accessed from Non-Cloud Public IP

#
Status
Experimental
Severity
high
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Initial Access, Lateral Movement, Credential Access, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection identifies when Kubernetes system principals (service accounts with usernames starting with "system:", "eks:", or "aks:") are accessed from non-cloud provider public IP addresses. System principals should only operate from within the cluster (private IPs) or from legitimate cloud infrastructure. Access from external public IPs indicates potential service account token theft or compromise, often following initial access to a cluster.

MITRE ATT&CK coverage

Detection logic

from ipaddress import ip_address

from panther_ipinfo_helpers import get_ipinfo_asn
from panther_kubernetes_helpers import k8s_alert_context

# AWS-managed services that run as Lambdas and legitimately originate from public IPs
AWS_MANAGED_PRINCIPALS = {"eks:addon-manager", "eks:node-manager"}

# AKS-managed service accounts that legitimately make requests from public IPs
# These are CSI drivers and system controllers that run on nodes with public IPs
AKS_MANAGED_SERVICE_ACCOUNTS = {
    "system:serviceaccount:kube-system:csi-azuredisk-node-sa",
    "system:serviceaccount:kube-system:csi-azurefile-node-sa",
    "system:serviceaccount:kube-system:csi-secrets-store-provider-azure",
    "system:serviceaccount:kube-system:cloud-node-manager",
}

# Cloud provider ASN mappings for infrastructure IP detection
CLOUD_PROVIDER_ASNS = {
    "aws": ["AS16509"],
    "azure": ["AS8075"],
    "gcp": ["AS15169", "AS396982"],
}

# GKE control plane user agents that legitimately make anonymous requests from Google IPs
GKE_SYSTEM_USER_AGENTS = {
    "GoogleKubernetesEngineFrontend",
    "GoogleHC/1.0",
}

# GKE health check endpoints accessed by control plane
GKE_HEALTH_CHECK_ENDPOINTS = {"readyz", "livez", "healthz"}


def is_cloud_infrastructure_ip(event, cloud_provider):
    """Check if source IP is from cloud provider infrastructure using IPInfo ASN."""
    ipinfo_asn_data = get_ipinfo_asn(event)
    if not ipinfo_asn_data:
        return False

    # Get ASN from appropriate field based on log type
    log_type = event.get("p_log_type", "")
    if "GCP" in log_type:
        asn_value = ipinfo_asn_data.asn("callerIp")
    else:
        asn_value = ipinfo_asn_data.asn("sourceIPs")

    if (
        asn_value
        and len(asn_value) > 0
        and asn_value[0] in CLOUD_PROVIDER_ASNS.get(cloud_provider, [])
    ):
        return True

    return False


def _is_legitimate_eks_node(event):
    """Check if this is a legitimate EKS node based on username and user groups."""
    username = event.udm("username") or ""

    # Check if it's a system node
    if username.startswith("system:node:"):
        user_groups = event.deep_get("user", "groups", default=[])
        # Legitimate EKS nodes should be in system:nodes and system:authenticated groups
        return "system:nodes" in user_groups and "system:authenticated" in user_groups

    return False


def is_aws_managed_service(event):
    """Check if this is an AWS-managed EKS service like addon-manager or node-manager."""
    username = event.udm("username") or ""

    if username not in AWS_MANAGED_PRINCIPALS:
        return False

    # Verify it's actually from AWS Lambda (AWSWesleyClusterManagerLambda role)
    arn = event.deep_get("user", "extra", "arn", default=[""])[0]
    return ":assumed-role/AWSWesleyClusterManagerLambda" in arn


def _is_legitimate_eks_request(event):
    """Check if this is a legitimate EKS request."""
    if is_aws_managed_service(event):
        return True
    return _is_legitimate_eks_node(event) and is_cloud_infrastructure_ip(event, "aws")


def _is_legitimate_aks_request(event, username):
    """Check if this is a legitimate AKS request."""
    if username in AKS_MANAGED_SERVICE_ACCOUNTS:
        return True
    return username.startswith("system:node:") and is_cloud_infrastructure_ip(event, "azure")


def _is_legitimate_gke_request(event, username):
    """Check if this is a legitimate GKE request."""
    if username.startswith("system:node:") and is_cloud_infrastructure_ip(event, "gcp"):
        return True

    # GKE control plane makes anonymous health check requests from Google IPs
    if username == "system:anonymous":
        user_agent = event.deep_get(
            "protoPayload", "requestMetadata", "callerSuppliedUserAgent", default=""
        )
        if user_agent not in GKE_SYSTEM_USER_AGENTS:
            return False

        # Prefer ASN-based verification
        if is_cloud_infrastructure_ip(event, "gcp"):
            return True

        # Fallback: Health check endpoints are legitimate without ASN verification
        resource_name = event.deep_get("protoPayload", "resourceName", default="")
        request_uri = event.udm("requestURI") or ""

        # Use path-based matching to avoid false positives
        return any(
            f"/{endpoint}" in resource_name
            or resource_name.endswith(endpoint)
            or f"/{endpoint}" in request_uri
            or request_uri.endswith(endpoint)
            for endpoint in GKE_HEALTH_CHECK_ENDPOINTS
        )

    return False


def _is_legitimate_cloud_node(event, username, log_type):
    """Check if this is a legitimate cloud provider node."""
    if "Amazon.EKS" in log_type:
        return _is_legitimate_eks_request(event)
    if "Azure.MonitorActivity" in log_type:
        return _is_legitimate_aks_request(event, username)
    if "GCP.AuditLog" in log_type:
        return _is_legitimate_gke_request(event, username)
    return False


def rule(event):  # pylint: disable=too-many-return-statements
    username = event.udm("username") or ""
    source_ips = event.udm("sourceIPs") or []
    response_status = event.udm("responseStatus") or {}
    log_type = event.get("p_log_type", "")

    # Only check ResponseComplete stage (EKS/AKS have this field)
    stage = event.get("stage")
    if stage and stage != "ResponseComplete":
        return False

    # Exclude 403 responses (handled by k8s_multiple_403_public_ip rule)
    if response_status.get("code") == 403:
        return False

    # Check if this is a REAL system principal (service accounts, nodes, cloud-managed)
    # Excludes system:anonymous and system:unauthenticated (unauthenticated API access)
    if not (
        username.startswith("system:serviceaccount:")
        or username.startswith("system:node:")
        or username.startswith("eks:")
        or username.startswith("aks:")
    ):
        return False

    # Check if source IP is public
    if not source_ips:
        return False

    # If any source IP is private, this is a pod running on a node (which has both
    # public and private interfaces). Real external attackers only have public IPs.
    for ip_str in source_ips:
        try:
            ip_obj = ip_address(ip_str)
            if not ip_obj.is_global:
                return False
        except ValueError:
            continue  # Skip invalid IPs

    # Exclude legitimate cloud provider nodes
    if _is_legitimate_cloud_node(event, username, log_type):
        return False

    # Alert: system principal accessed from non-cloud-provider public IP
    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    verb = event.udm("verb") or "<UNKNOWN_VERB>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    namespace = event.udm("namespace")
    source_ips = event.udm("sourceIPs") or ["<UNKNOWN_IP>"]
    source_ip = source_ips[0] if source_ips else "<UNKNOWN_IP>"

    # Handle cluster-scoped resources (no namespace)
    if namespace:
        namespace_str = f"in namespace [{namespace}] "
    else:
        namespace_str = "(cluster-scoped) "

    return (
        f"System principal [{username}] executed [{verb}] for resource [{resource}] "
        f"{namespace_str}from non-cloud public IP [{source_ip}]"
    )


def dedup(event):
    source_ips = event.udm("sourceIPs") or ["<UNKNOWN_IP>"]
    source_ip = source_ips[0] if source_ips else "<UNKNOWN_IP>"
    return f"k8s_system_principal_{source_ip}"


def alert_context(event):
    source_ips = event.udm("sourceIPs") or []
    ipinfo_asn_data = get_ipinfo_asn(event)

    extra_fields = {
        "source_ip": source_ips[0] if source_ips else None,
        "asn_info": None,
    }

    # Add ASN information if available
    if ipinfo_asn_data:
        log_type = event.get("p_log_type", "")
        if "GCP" in log_type:
            asn_value = ipinfo_asn_data.asn("callerIp")
            domain_value = ipinfo_asn_data.domain("callerIp")
        else:
            asn_value = ipinfo_asn_data.asn("sourceIPs")
            domain_value = ipinfo_asn_data.domain("sourceIPs")

        if asn_value and asn_value[0]:
            extra_fields["asn_info"] = {
                "asn": asn_value[0],
                "domain": domain_value[0] if domain_value else None,
            }

    return k8s_alert_context(event, extra_fields=extra_fields)

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.System.Principal.PublicIP"
DisplayName: "Kubernetes System Principal Accessed from Non-Cloud Public IP"
Enabled: true
Status: Experimental
Filename: k8s_system_principal_public_ip.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Initial Access
  - Lateral Movement
  - Credential Access
  - Unified Detection
Severity: High
Description: >
  This detection identifies when Kubernetes system principals (service accounts with usernames
  starting with "system:", "eks:", or "aks:") are accessed from non-cloud provider public IP
  addresses. System principals should only operate from within the cluster (private IPs) or
  from legitimate cloud infrastructure. Access from external public IPs indicates potential
  service account token theft or compromise, often following initial access to a cluster.
Runbook: |
  1. Find all Kubernetes API requests from the sourceIPs address in the 24 hours before and after the alert to identify targeted resources and operations
  2. Query for authentication and service account token events by this username in the 48 hours before the alert to determine if the token was recently compromised
  3. Search for other system principal alerts from the same sourceIPs address across all clusters in the past 7 days to assess campaign scope
Reference: https://kubernetes.io/docs/concepts/security/rbac-good-practices/
Reports:
  MITRE ATT&CK:
    - TA0001:T1190 # Initial Access: Exploit Public-Facing Application
    - TA0006:T1528 # Credential Access: Steal Application Access Token
    - TA0008:T1021.007 # Lateral Movement: Remote Services: Cloud Services
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - p_any_ip_addresses
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • any of:
    • stage is empty
    • stage is ResponseComplete
  • responseStatus.code is not 403
  • any of:
    • username starts with system:serviceaccount:
    • username starts with system:node:
    • username starts with eks:
    • username starts with aks:
  • sourceIPs is present
  • any of:
    • p_log_type does not contain Amazon.EKS
    • all of:
      • username is not one of eks:addon-manager, eks:node-manager
      • any of:
        • username does not start with system:node:
        • user.groups does not contain system:nodes
        • user.groups does not contain system:authenticated
  • any of:
    • p_log_type contains Amazon.EKS
    • p_log_type does not contain Azure.MonitorActivity
    • all of:
      • username is not one of system:serviceaccount:kube-system:csi-azuredisk-node-sa, system:serviceaccount:kube-system:csi-azurefile-node-sa, system:serviceaccount:kube-system:csi-secrets-store-provider-azure, system:serviceaccount:kube-system:cloud-node-manager
      • username does not start with system:node:
  • any of:
    • p_log_type contains Amazon.EKS
    • p_log_type contains Azure.MonitorActivity
    • p_log_type does not contain GCP.AuditLog
    • all of:
      • username does not start with system:node:
      • any of:
        • username is not system:anonymous
        • protoPayload.requestMetadata.callerSuppliedUserAgent is not one of GoogleKubernetesEngineFrontend, GoogleHC/1.0
        • all of:
          • protoPayload.resourceName does not end with readyz
          • requestURI does not end with readyz
          • protoPayload.resourceName does not end with livez
          • requestURI does not end with livez
          • protoPayload.resourceName does not end with healthz
          • requestURI does not end with healthz

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
protoPayload.resourceNameends_withhealthzexcludes:protoPayload.resourceName field:"protoPayload.resourceName" value:"healthz"
protoPayload.resourceNameends_withlivezexcludes:protoPayload.resourceName field:"protoPayload.resourceName" value:"livez"
protoPayload.resourceNameends_withreadyzexcludes:protoPayload.resourceName field:"protoPayload.resourceName" value:"readyz"
requestURIends_withhealthzexcludes:requestURI field:"requestURI" value:"healthz"
requestURIends_withlivezexcludes:requestURI field:"requestURI" value:"livez"
requestURIends_withreadyzexcludes:requestURI field:"requestURI" value:"readyz"
protoPayload.requestMetadata.callerSuppliedUserAgentinGoogleHC/1.0, GoogleKubernetesEngineFrontendexcludes:protoPayload.requestMetadata.callerSuppliedUserAgent field:"protoPayload.requestMetadata.callerSuppliedUserAgent" value:"GoogleHC/1.0" field:"protoPayload.requestMetadata.callerSuppliedUserAgent" value:"GoogleKubernetesEngineFrontend"
usernameeqsystem:anonymousexcludes:username field:"username" value:"system:anonymous"
usernamestarts_withsystem:node:excludes:username field:"username" value:"system:node:"
p_log_typecontainsAmazon.EKSexcludes:p_log_type field:"p_log_type" value:"Amazon.EKS"
p_log_typecontainsAzure.MonitorActivityexcludes:p_log_type field:"p_log_type" value:"Azure.MonitorActivity"
p_log_typecontainsGCP.AuditLogexcludes:p_log_type field:"p_log_type" value:"GCP.AuditLog"
user.groupscontainssystem:authenticatedexcludes:user.groups field:"user.groups" value:"system:authenticated"
user.groupscontainssystem:nodesexcludes:user.groups field:"user.groups" value:"system:nodes"
usernameineks:addon-manager, eks:node-managerexcludes:username field:"username" value:"eks:addon-manager" field:"username" value:"eks:node-manager"
usernameinsystem:serviceaccount:kube-system:cloud-node-manager, system:serviceaccount:kube-system:csi-azuredisk-node-sa, system:serviceaccount:kube-system:csi-azurefile-node-sa, system:serviceaccount:kube-system:csi-secrets-store-provider-azureexcludes:username
stageis_not_null(no value, null check)excludes:stage
stageneResponseCompleteexcludes:stage field:"stage" value:"ResponseComplete"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
p_log_typecontains
  • Amazon.EKS
  • Azure.MonitorActivity
field:"p_log_type" kind:contains
responseStatus.codene
  • 403 transforms: number
field:"responseStatus.code" kind:ne value:"403"
sourceIPsis_not_null
  • (no value, null check)
field:"sourceIPs" kind:is_not_null
usernamestarts_with
  • aks:
  • eks:
  • system:node:
  • system:serviceaccount:
field:"username" kind:starts_with

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label

Response runbook

1. Find all Kubernetes API requests from the sourceIPs address in the 24 hours before and after the alert to identify targeted resources and operations

2. Query for authentication and service account token events by this username in the 48 hours before the alert to determine if the token was recently compromised

3. Search for other system principal alerts from the same sourceIPs address across all clusters in the past 7 days to assess campaign scope

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiVersion": "v1",
    "resource": "endpointslices"
  },
  "p_enrichment": {
    "ipinfo_asn": {
      "sourceIPs": [
        {
          "asn": "AS12345",
          "domain": "example-isp.com",
          "name": "Example ISP",
          "p_match": "1.2.3.4",
          "type": "isp"
        }
      ]
    }
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "1.2.3.4"
  ],
  "stage": "ResponseComplete",
  "user": {
    "groups": [
      "system:serviceaccounts",
      "system:authenticated"
    ],
    "username": "system:serviceaccount:kube-system:coredns"
  },
  "verb": "get"
}

Kubernetes System Role Modified or Deleted

#
Status
Experimental
Severity
critical
Group by
name, username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Privilege Escalation, Defense Evasion, Persistence, RBAC, Unified Detection
Reference
kubernetes.io
Source
github.com/panther-labs/panther-analysis

This detection monitors for modifications or deletions of system ClusterRoles/Roles (those starting with "system:"). These are built-in Kubernetes roles for control plane components like kube-scheduler, kube-controller-manager, and system:admin. Tampering with system roles can break cluster functionality, create privilege escalation backdoors, or disable security controls. Legitimate modifications to system roles are extremely rare outside of cluster upgrades.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context

# System roles that are expected to change during normal operations
# Users can extend this list for their environment
ALLOWED_SYSTEM_ROLE_MODIFICATIONS = {
    "system:coredns",
    "system:managed-certificate-controller",
}


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    username = event.udm("username")
    response_status = event.udm("responseStatus")
    name = event.udm("name") or ""

    # Only check role/clusterrole modification/deletion events
    if verb not in {"update", "patch", "delete"} or resource not in {
        "roles",
        "clusterroles",
    }:
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Exclude system principals to reduce false positives
    if is_system_principal(username):
        return False

    # Check if role name starts with "system:" or "eks:" (EKS system roles)
    if not (name.startswith("system:") or name.startswith("eks:")):
        return False

    # Exclude roles that are expected to change
    if name in ALLOWED_SYSTEM_ROLE_MODIFICATIONS:
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    verb = event.udm("verb") or "<UNKNOWN_VERB>"
    resource = event.udm("resource") or "<UNKNOWN_RESOURCE>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    namespace = event.udm("namespace") or "<CLUSTER_SCOPED>"

    role_type = "ClusterRole" if resource == "clusterroles" else "Role"
    action = "deleted" if verb == "delete" else "modified"

    if namespace != "<CLUSTER_SCOPED>":
        return f"[{username}] {action} system {role_type} [{namespace}/{name}]"

    return f"[{username}] {action} system {role_type} [{name}]"


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    name = event.udm("name") or "<UNKNOWN_ROLE>"
    return f"k8s_system_role_{username}_{name}"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "role_name": event.udm("name"),
            "role_type": event.udm("resource"),
            "modification_type": event.udm("verb"),
        },
    )

Rule specification

AnalysisType: rule
RuleID: "Kubernetes.SystemRole.Modified"
DisplayName: "Kubernetes System Role Modified or Deleted"
Enabled: true
Status: Experimental
Filename: k8s_system_role_modified.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Tags:
  - Kubernetes
  - Privilege Escalation
  - Defense Evasion
  - Persistence
  - RBAC
  - Unified Detection
Severity: Critical
Description: >
  This detection monitors for modifications or deletions of system ClusterRoles/Roles (those starting with "system:").
  These are built-in Kubernetes roles for control plane components like kube-scheduler, kube-controller-manager, and
  system:admin. Tampering with system roles can break cluster functionality, create privilege escalation backdoors,
  or disable security controls. Legitimate modifications to system roles are extremely rare outside of cluster upgrades.
Runbook: |
  1. Immediately review the changes made to the system role and determine if this represents a cluster upgrade or unauthorized tampering
  2. If unauthorized, revert the role to its original state using kubectl or restore from backup, then revoke credentials for the modifying user
  3. Search for all RBAC modifications by this user across all clusters in the past 7 days and audit all API operations to identify other malicious changes
Reports:
  MITRE ATT&CK:
    - TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
    - TA0005:T1222 # Defense Evasion: File and Directory Permissions Modification
    - TA0003:T1098 # Persistence: Account Manipulation
Reference: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings
DedupPeriodMinutes: 60
SummaryAttributes:
  - username
  - resource
  - name
  - p_source_label

Stages and Predicates

Fires on Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog events when all of the conditions below hold.

Condition

  • verb is one of update, patch, delete
  • resource is one of roles, clusterroles
  • any of:
    • responseStatus is empty
    • all of:
      • responseStatus.code is less than 400
      • any of:
        • responseStatus.code is less than 1
        • responseStatus.code is greater than 16
  • any of:
    • username is empty
    • all of:
      • username is not one of masterclient, aksService
      • any of:
        • username does not start with system:
        • username contains serviceaccount
  • any of:
    • name starts with system:
    • name starts with eks:
  • name is not one of system:coredns, system:managed-certificate-controller

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
namestarts_with
  • eks:
  • system:
field:"name" kind:starts_with
resourcein
  • clusterroles
  • roles
field:"resource" kind:in
usernamecontains
  • serviceaccount
field:"username" kind:contains value:"serviceaccount"
verbin
  • delete
  • patch
  • update
field:"verb" kind:in

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label
name

Response runbook

1. Immediately review the changes made to the system role and determine if this represents a cluster upgrade or unauthorized tampering

2. If unauthorized, revert the role to its original state using kubectl or restore from backup, then revoke credentials for the modifying user

3. Search for all RBAC modifications by this user across all clusters in the past 7 days and audit all API operations to identify other malicious changes

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "apiVersion": "audit.k8s.io/v1",
  "kind": "Event",
  "objectRef": {
    "apiGroup": "rbac.authorization.k8s.io",
    "apiVersion": "v1",
    "name": "system:node",
    "resource": "clusterroles"
  },
  "p_log_type": "Amazon.EKS.Audit",
  "p_source_label": "eks-cluster",
  "requestObject": {
    "kind": "ClusterRole",
    "metadata": {
      "name": "system:node"
    },
    "rules": [
      {
        "apiGroups": [
          "*"
        ],
        "resources": [
          "*"
        ],
        "verbs": [
          "*"
        ]
      }
    ]
  },
  "responseStatus": {
    "code": 200
  },
  "sourceIPs": [
    "203.0.113.42"
  ],
  "user": {
    "username": "attacker@example.com"
  },
  "userAgent": "kubectl/v1.28.0",
  "verb": "update"
}