Container runtime and audit log hunting

Two hunts: Kubernetes API server audit log queries for control plane anomalies, and Falco rules for runtime signals not covered in the evasion section.

Kubernetes audit log: privileged pod creation

Hypothesis: an attacker with API server access is creating pods with host-level privileges as a container escape or lateral movement step.

Data source: Kubernetes API server audit log (JSON format).

# extract privileged pod creation events
grep '"verb":"create"' /var/log/kubernetes/audit.log | \
  jq -c 'select(
    .objectRef.resource == "pods" and
    .verb == "create" and
    (
      .requestObject.spec.containers[]?.securityContext.privileged == true or
      .requestObject.spec.hostPID == true or
      .requestObject.spec.hostNetwork == true or
      .requestObject.spec.hostIPC == true
    )
  ) | {
    time:      .requestReceivedTimestamp,
    user:      .user.username,
    namespace: .objectRef.namespace,
    pod:       .objectRef.name,
    sourceIP:  .sourceIPs[0]
  }'

Kubernetes audit log: exec and anomalous API calls

Hypothesis: a service account is being used for interactive access or API operations outside its documented scope.

# pod exec events
grep '"subresource":"exec"' /var/log/kubernetes/audit.log | \
  jq -c 'select(.verb == "create") | {
    time:      .requestReceivedTimestamp,
    user:      .user.username,
    namespace: .objectRef.namespace,
    pod:       .objectRef.name,
    sourceIP:  .sourceIPs[0]
  }'

# RBAC modification events
grep -E '"resource":"(clusterrolebindings|rolebindings|clusterroles)"' \
  /var/log/kubernetes/audit.log | \
  jq -c 'select(.verb | test("create|update|patch|delete")) | {
    time:      .requestReceivedTimestamp,
    user:      .user.username,
    verb:      .verb,
    resource:  .objectRef.resource,
    name:      .objectRef.name,
    namespace: (.objectRef.namespace // "cluster")
  }'

# secret access from unexpected service accounts
# adjust the exclusion pattern for known legitimate consumers
grep '"resource":"secrets"' /var/log/kubernetes/audit.log | \
  jq -c 'select(
    .objectRef.resource == "secrets" and
    (.verb == "get" or .verb == "list") and
    (.user.username | startswith("system:serviceaccount:")) and
    (.user.username | test("cert-manager|vault|external-secrets") | not)
  ) | {
    time:      .requestReceivedTimestamp,
    account:   .user.username,
    verb:      .verb,
    namespace: .objectRef.namespace
  }' | sort | uniq -c | sort -rn | head -30

Falco: fileless execution and memory inspection

The following rules cover patterns not in evasion/containers.md. Add to a custom Falco rules file alongside the shell spawn and package manager rules.

# lists referenced by the rules below; extend as needed for the environment
- list: known_memfd_users
  items: []

- list: known_debugger_procs
  items: [gdb, lldb, strace, ltrace]

# memfd_create: fileless execution staging without writing to the filesystem
- rule: Fileless execution via memfd in container
  desc: memfd_create used to stage an in-memory payload inside a container
  condition: >
    evt.type = memfd_create
    and container
    and not proc.name in (known_memfd_users)
  output: >
    memfd_create in container (user=%user.name container=%container.name
    image=%container.image.repository proc=%proc.name cmdline=%proc.cmdline)
  priority: WARNING
  tags: [container, mitre_defense_evasion]

# ptrace from a non-debugger process: memory inspection or injection
- rule: Unexpected ptrace in container
  desc: ptrace syscall from a process with no legitimate debugging purpose
  condition: >
    evt.type = ptrace
    and container
    and evt.dir = >
    and not proc.name in (known_debugger_procs)
  output: >
    ptrace in container (user=%user.name container=%container.name
    image=%container.image.repository proc=%proc.name pid=%proc.pid)
  priority: WARNING
  tags: [container, mitre_credential_access]

# write to credential or configuration paths inside a container
- rule: Write to sensitive path in container
  desc: Process wrote to a path that should be read-only in a production container
  condition: >
    (evt.type = open or evt.type = openat)
    and evt.is_open_write = true
    and container
    and (
      fd.name startswith "/etc/passwd" or
      fd.name startswith "/etc/shadow" or
      fd.name startswith "/etc/sudoers" or
      fd.name startswith "/root/.ssh" or
      fd.name startswith "/proc/sys"
    )
  output: >
    Sensitive path written in container (user=%user.name container=%container.name
    image=%container.image.repository path=%fd.name proc=%proc.name)
  priority: ERROR
  tags: [container, mitre_persistence]

Correlating runtime and control plane events

A shell spawn inside a pod followed by API calls from that pod’s service account is a high-confidence escalation indicator. Given a pod name and namespace from a Falco alert:

POD_NS="default"
POD_NAME="web-app-xyz"
ALERT_TIME="2026-01-15T14:22:00Z"
WINDOW_END="2026-01-15T14:52:00Z"

# find the service account for this pod
SA=$(kubectl get pod "$POD_NAME" -n "$POD_NS" \
  -o jsonpath='{.spec.serviceAccountName}')

# find all API calls from that service account in the 30 minutes after the alert
grep "system:serviceaccount:${POD_NS}:${SA}" /var/log/kubernetes/audit.log | \
  jq -c --arg start "$ALERT_TIME" --arg end "$WINDOW_END" \
  'select(
    .requestReceivedTimestamp >= $start and
    .requestReceivedTimestamp <= $end
  ) | {
    time:      .requestReceivedTimestamp,
    verb:      .verb,
    resource:  .objectRef.resource,
    name:      .objectRef.name,
    namespace: (.objectRef.namespace // "cluster")
  }'

A service account from an application namespace making RBAC modifications or secret access calls in the window following a Falco shell spawn from the same pod is the escalation chain worth treating as a confirmed incident rather than an alert requiring further triage.