Kubernetes Pod With HostPath Volume Mount


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.

Query · python

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

Analyst notes

  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
Raw source Kubernetes Pod With HostPath Volume Mount · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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
Tests:
  - Name: EKS Pod With Suspicious HostPath
    ExpectedResult: true
    Log:
      {
        "kind": "Event",
        "apiVersion": "audit.k8s.io/v1",
        "auditID": "abc-123",
        "verb": "create",
        "user": {"username": "user@company.com"},
        "sourceIPs": ["1.2.3.4"],
        "userAgent": "kubectl/v1.28.2",
        "objectRef": {
          "resource": "pods",
          "namespace": "default",
          "name": "test",
          "apiVersion": "v1"
        },
        "responseStatus": {"code": 201},
        "requestObject": {
          "kind": "Pod",
          "apiVersion": "v1",
          "metadata": {"name": "test", "namespace": "default"},
          "spec": {
            "containers": [{
              "name": "test",
              "image": "nginx",
              "volumeMounts": [{"mountPath": "/test", "name": "test-volume"}]
            }],
            "volumes": [{
              "name": "test-volume",
              "hostPath": {
                "path": "/var/lib/kubelet",
                "type": "DirectoryOrCreate"
              }
            }]
          }
        },
        "p_log_type": "Amazon.EKS.Audit",
        "p_source_label": "eks-cluster"
      }
  - Name: AKS Pod With HostPath
    ExpectedResult: true
    Log:
      {
        "p_log_type": "Azure.MonitorActivity",
        "category": "kube-audit",
        "operationName": "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read",
        "properties": {
          "log": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"verb\":\"create\",\"user\":{\"username\":\"admin@example.com\"},\"sourceIPs\":[\"10.0.0.1\"],\"objectRef\":{\"resource\":\"pods\",\"namespace\":\"default\",\"name\":\"test\"},\"responseStatus\":{\"code\":201},\"requestObject\":{\"kind\":\"Pod\",\"spec\":{\"containers\":[{\"name\":\"nginx\",\"volumeMounts\":[{\"mountPath\":\"/data\",\"name\":\"vol\"}]}],\"volumes\":[{\"name\":\"vol\",\"hostPath\":{\"path\":\"/mnt/data\"}}]}}}"
        },
        "p_source_label": "aks-cluster"
      }
  - Name: GCP GKE Pod With HostPath
    ExpectedResult: true
    Log:
      {
        "logName": "projects/test-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload": {
          "authenticationInfo": {"principalEmail": "user@company.com"},
          "authorizationInfo": [{
            "granted": true,
            "permission": "io.k8s.core.v1.pods.create",
            "resource": "core/v1/namespaces/default/pods/test"
          }],
          "methodName": "io.k8s.core.v1.pods.create",
          "request": {
            "@type": "core.k8s.io/v1.Pod",
            "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"
              }]
            }
          },
          "requestMetadata": {"callerIP": "1.2.3.4"},
          "resourceName": "core/v1/namespaces/default/pods/test",
          "serviceName": "k8s.io"
        },
        "resource": {
          "labels": {
            "cluster_name": "gke-cluster",
            "project_id": "test-project"
          },
          "type": "k8s_cluster"
        },
        "p_log_type": "GCP.AuditLog",
        "p_source_label": "gke-cluster",
        "timestamp": "2024-02-16 11:48:22.742"
      }
  - Name: Pod Without HostPath
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "create",
        "objectRef": {"resource": "pods", "namespace": "default"},
        "responseStatus": {"code": 201},
        "requestObject": {
          "spec": {
            "containers": [{"name": "nginx"}],
            "volumes": [{
              "name": "config",
              "configMap": {"name": "app-config"}
            }]
          }
        },
        "p_log_type": "Amazon.EKS.Audit"
      }
  - Name: Pod Creation Failed
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "create",
        "objectRef": {"resource": "pods", "namespace": "default"},
        "responseStatus": {"code": 403, "status": "Failure"},
        "requestObject": {
          "spec": {
            "volumes": [{
              "hostPath": {"path": "/var/lib/kubelet"}
            }]
          }
        },
        "p_log_type": "Amazon.EKS.Audit"
      }
  - Name: System Namespace Pod (Excluded)
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "create",
        "user": {"username": "system:serviceaccount:kube-system:controller"},
        "objectRef": {
          "resource": "pods",
          "namespace": "kube-system",
          "name": "network-agent"
        },
        "responseStatus": {"code": 201},
        "requestObject": {
          "spec": {
            "volumes": [{
              "hostPath": {"path": "/var/lib/cni"}
            }]
          }
        },
        "p_log_type": "Amazon.EKS.Audit"
      }


# ------ paired body: k8s_pod_hostpath_volume.py ------

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

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.