GCP K8S Pod Create Or Modify Host Path 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_gcp_helpers import gcp_alert_context, is_gke_system_namespace, is_gke_system_principal

SUSPICIOUS_PATHS = [
    "/var/run/docker.sock",
    "/var/run/crio/crio.sock",
    "/var/lib/kubelet",
    "/var/lib/kubelet/pki",
    "/var/lib/docker/overlay2",
    "/etc/kubernetes",
    "/etc/kubernetes/manifests",
    "/etc/kubernetes/pki",
    "/home/admin",
]


def rule(event):
    # Check basic conditions
    if event.deep_get("protoPayload", "response", "status") == "Failure" or event.deep_get(
        "protoPayload", "methodName"
    ) not in (
        "io.k8s.core.v1.pods.create",
        "io.k8s.core.v1.pods.update",
        "io.k8s.core.v1.pods.patch",
    ):
        return False

    # Check if volume mount path is suspicious
    volume_mount_path = event.deep_walk(
        "protoPayload", "request", "spec", "volumes", "hostPath", "path"
    )

    has_suspicious_path = volume_mount_path and (
        volume_mount_path in SUSPICIOUS_PATHS
        or any(path in SUSPICIOUS_PATHS for path in volume_mount_path)
    )

    if not has_suspicious_path:
        return False

    # Check if this is a known GKE system service account or system namespace
    principal_email = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default=""
    )
    resource_name = event.deep_get("protoPayload", "resourceName", default="")

    if is_gke_system_principal(principal_email) or is_gke_system_namespace(resource_name):
        return False

    # Check authorization
    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if (
            auth.get("permission")
            in (
                "io.k8s.core.v1.pods.create",
                "io.k8s.core.v1.pods.update",
                "io.k8s.core.v1.pods.patch",
            )
            and auth.get("granted") is True
        ):
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    pod_name = event.deep_get("protoPayload", "resourceName", default="<RESOURCE_NOT_FOUND>")
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return (
        f"[GCP]: [{actor}] created k8s pod [{pod_name}] with a hostPath volume mount "
        f"in project [{project_id}]"
    )


def dedup(event):
    return event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )


def alert_context(event):
    context = gcp_alert_context(event)
    volume_mount_path = event.deep_walk(
        "protoPayload", "request", "spec", "volumes", "hostPath", "path"
    )
    context["volume_mount_path"] = volume_mount_path
    return context

Analyst notes

Investigate the reason of adding hostPath volume mount. Advise that it is discouraged practice. Create ticket if appropriate.

Raw source GCP K8S Pod Create Or Modify Host Path Volume Mount · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
RuleID: "GCP.K8S.Pod.Create.Or.Modify.Host.Path.Volume.Mount"
DisplayName: "GCP K8S Pod Create Or Modify Host Path Volume Mount"
Enabled: false
Status: Deprecated
LogTypes:
  - GCP.AuditLog
Severity: High
Tags:
  - Deprecated
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: |
  Investigate the reason of adding hostPath volume mount. Advise that it is discouraged practice.
  Create ticket if appropriate.
Reference: https://kubernetes.io/docs/concepts/security/pod-security-standards/#host-namespaces
Reports:
  MITRE ATT&CK:
    - TA0010:T1041 # Exfiltration Over C2 Channel 
    - TA0004:T1611 # Escape to Host 
Filename: gcp_k8s_pod_create_or_modify_host_path_vol_mount.py
DedupPeriodMinutes: 360
Tests:
  - Name: Pod With Suspicious Volume Mount Created
    ExpectedResult: true
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.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",
                          "imagePullPolicy": "Always",
                          "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",
                "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3",
              },
            "resourceName": "core/v1/namespaces/default/pods/test",
            "response":
              {
                "spec":
                  {
                    "containers":
                      [
                        {
                          "image": "nginx",
                          "imagePullPolicy": "Always",
                          "name": "test",
                          "volumeMounts":
                            [{ "mountPath": "/test", "name": "test-volume" }],
                        },
                      ],
                    "volumes":
                      [
                        {
                          "hostPath":
                            {
                              "path": "/var/lib/kubelet",
                              "type": "DirectoryOrCreate",
                            },
                          "name": "test-volume",
                        },
                      ],
                  },
                "status": { "phase": "Pending", "qosClass": "BestEffort" },
              },
          },
        "receiveTimestamp": "2024-02-16 11:48:43.531373988",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-16 11:48:22.742154000",
      }
  - Name: Pod With Non-Suspicious Volume Mount Created
    ExpectedResult: false
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.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",
                          "imagePullPolicy": "Always",
                          "name": "test",
                          "volumeMounts":
                            [{ "mountPath": "/test", "name": "test-volume" }],
                        },
                      ],
                    "volumes":
                      [
                        {
                          "hostPath":
                            { "path": "/data", "type": "DirectoryOrCreate" },
                          "name": "test-volume",
                        },
                      ],
                  },
              },
            "requestMetadata":
              {
                "callerIP": "1.2.3.4",
                "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3",
              },
            "resourceName": "core/v1/namespaces/default/pods/test",
            "response":
              {
                "spec":
                  {
                    "containers":
                      [
                        {
                          "image": "nginx",
                          "imagePullPolicy": "Always",
                          "name": "test",
                          "volumeMounts":
                            [{ "mountPath": "/test", "name": "test-volume" }],
                        },
                      ],
                    "volumes":
                      [
                        {
                          "hostPath":
                            { "path": "/data", "type": "DirectoryOrCreate" },
                          "name": "test-volume",
                        },
                      ],
                  },
                "status": { "phase": "Pending", "qosClass": "BestEffort" },
              },
          },
        "receiveTimestamp": "2024-02-16 11:48:43.531373988",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-16 11:48:22.742154000",
      }
  - Name: Pod Not Created
    ExpectedResult: False
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.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",
                          "imagePullPolicy": "Always",
                          "name": "test",
                          "volumeMounts":
                            [{ "mountPath": "/test", "name": "test-volume" }],
                        },
                      ],
                    "volumes":
                      [
                        {
                          "hostPath":
                            {
                              "path": "/var/lib/kubelet",
                              "type": "DirectoryOrCreate",
                            },
                          "name": "test-volume",
                        },
                      ],
                  },
                "status": {},
              },
            "resourceName": "core/v1/namespaces/default/pods/test",
            "response": { "status": "Failure" },
          },
        "receiveTimestamp": "2024-02-16 12:55:17.003485190",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-16 12:55:00.510160000",
      }


# ------ paired body: gcp_k8s_pod_create_or_modify_host_path_vol_mount.py ------

from panther_gcp_helpers import gcp_alert_context, is_gke_system_namespace, is_gke_system_principal

SUSPICIOUS_PATHS = [
    "/var/run/docker.sock",
    "/var/run/crio/crio.sock",
    "/var/lib/kubelet",
    "/var/lib/kubelet/pki",
    "/var/lib/docker/overlay2",
    "/etc/kubernetes",
    "/etc/kubernetes/manifests",
    "/etc/kubernetes/pki",
    "/home/admin",
]


def rule(event):
    # Check basic conditions
    if event.deep_get("protoPayload", "response", "status") == "Failure" or event.deep_get(
        "protoPayload", "methodName"
    ) not in (
        "io.k8s.core.v1.pods.create",
        "io.k8s.core.v1.pods.update",
        "io.k8s.core.v1.pods.patch",
    ):
        return False

    # Check if volume mount path is suspicious
    volume_mount_path = event.deep_walk(
        "protoPayload", "request", "spec", "volumes", "hostPath", "path"
    )

    has_suspicious_path = volume_mount_path and (
        volume_mount_path in SUSPICIOUS_PATHS
        or any(path in SUSPICIOUS_PATHS for path in volume_mount_path)
    )

    if not has_suspicious_path:
        return False

    # Check if this is a known GKE system service account or system namespace
    principal_email = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default=""
    )
    resource_name = event.deep_get("protoPayload", "resourceName", default="")

    if is_gke_system_principal(principal_email) or is_gke_system_namespace(resource_name):
        return False

    # Check authorization
    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if (
            auth.get("permission")
            in (
                "io.k8s.core.v1.pods.create",
                "io.k8s.core.v1.pods.update",
                "io.k8s.core.v1.pods.patch",
            )
            and auth.get("granted") is True
        ):
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    pod_name = event.deep_get("protoPayload", "resourceName", default="<RESOURCE_NOT_FOUND>")
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return (
        f"[GCP]: [{actor}] created k8s pod [{pod_name}] with a hostPath volume mount "
        f"in project [{project_id}]"
    )


def dedup(event):
    return event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )


def alert_context(event):
    context = gcp_alert_context(event)
    volume_mount_path = event.deep_walk(
        "protoPayload", "request", "spec", "volumes", "hostPath", "path"
    )
    context["volume_mount_path"] = volume_mount_path
    return context

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.