Skip to content

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.

Crypto-Mining in Kubernetes: Detect It in Audit Logs

Detect crypto-mining in Kubernetes clusters from audit logs: miner images and arguments, unusual registries, CronJob and DaemonSet persistence, and clean-up.

Published on 5 min read

TL;DR. A miner in a cluster is a workload, and every workload is created through the API server. In the audit log, look for workload writes whose image or arguments name a miner (XMRig and friends) or a mining pool (stratum+tcp://, --donate-level), images pulled from registries you do not use, and the persistence around them: CronJobs, DaemonSets, often in kube-system. Then find the identity that created them: the miner is the symptom, the stolen credential is the incident. Resource hijacking is ATT&CK T1496.

Crypto-mining is the most common way an intrusion into a cluster becomes visible, because it costs money and CPU. It is also the least interesting part of the investigation: whoever deployed the miner had enough access to do much worse, and may have.

A documented pattern

Microsoft described one such campaign in 2020: Kubeflow dashboards exposed to the internet through a load balancer let anyone deploy containers, and attackers used that to run an image that ran XMRig on tens of clusters, most of them on AKS (Microsoft Security blog, "Misconfigured Kubeflow workloads are a security risk"). The same post notes why such clusters are attractive: ML nodes are powerful and sometimes have GPUs.

The shape is common: an exposed dashboard or an over-privileged token as entry point, then workloads created with the cluster's own credentials. The fictional incident walkthrough on this site follows the same shape.

What the audit log records

At Request or RequestResponse level for workload writes (the default on EKS and GKE, see the export guide), the requestObject of a Deployment, DaemonSet, Job or CronJob contains the pod template: images, commands, arguments, resources. A fictional CronJob:

{
  "verb": "create",
  "user": { "username": "system:serviceaccount:kube-system:node-health" },
  "objectRef": { "resource": "cronjobs", "namespace": "kube-system", "name": "kube-state-sync", "apiGroup": "batch" },
  "requestObject": {
    "spec": {
      "schedule": "*/5 * * * *",
      "jobTemplate": { "spec": { "template": { "spec": {
        "containers": [{
          "name": "sync",
          "image": "registry.k8s-mirror.example:5000/xmrig/xmrig:6.21.0",
          "args": ["--url=pool.minexmr.example:4444", "--donate-level=0", "--cpu-max-threads-hint=75"],
          "resources": { "limits": { "cpu": "2" } }
        }]
      } } } }
    }
  }
}

Everything you need is in one event: the miner, the pool, a name imitating a Kubernetes add-on, a namespace chosen to blend in, a registry that is not yours, a schedule that recreates the pods if someone deletes them, and a CPU limit meant to stay under the radar.

Detections

RuleSeverityLogic
Crypto-mining image or commandCriticalImage contains a miner marker (xmrig, xmr-stak, minerd, cpuminer, nicehash, kinsing, c3pool, moneroocean…), or container command/args contain stratum+tcp, stratum+ssl, --donate-level, xmrig, minerd; also Falco alerts whose command matches
Image from an unusual registryLowRegistry outside a list of common public and cloud registries (docker.io, registry.k8s.io, gcr.io, ghcr.io, quay.io, mcr.microsoft.com, public.ecr.aws, *.amazonaws.com, *.pkg.dev, *.azurecr.io…)
CronJob createdMediumAny CronJob written by a non-system identity
DaemonSet created in kube-systemHighSee privileged pods and container escape

The miner rule is the only one in the analyzer that fires whoever created the pod, including controllers: a miner started by a CronJob appears as pods created by the Job controller, and you still want to see it. Grouping is by image, so one finding covers all the replicas.

The registry rule is low severity on purpose. Your own private registry will trigger it until you add it to the trusted list; after that, anything else stands out.

Evasion you should expect

Name matching catches lazy attackers. Others:

  • rename the binary and the image (alpine, nginx, busybox with a downloaded payload);
  • start from a legitimate image and fetch the miner in the command (curl … | sh);
  • mine over TLS to a proxy on port 443 instead of a well-known pool;
  • run the miner on the node after a container escape, where no API call is made at all.

So also look at the context the audit log does record: new workloads by identities that never deploy, images from new registries, command fields with download-and-execute patterns, CronJobs and DaemonSets created outside your deployment pipeline, and workloads created right after a new cluster-admin binding.

Signals outside the audit log

Mining is noisy on other channels, which is often how it is found first:

  • CPU and node metrics: nodes pinned at their limit, cluster autoscaler adding nodes for no business reason.
  • Egress traffic: connections to mining pools, typically on ports such as 3333, 4444 or 5555, or to unusual TLS endpoints.
  • Cloud billing: a jump in compute, sometimes in regions you do not use.
  • Runtime detection: Falco can flag mining behaviour at the process and network level; the project describes the approach in Cryptomining Detection Using Falco. Load Falco JSON alerts next to the audit logs in the analyzer to put both on one timeline.

Clean-up that actually works

Deleting the pods is not enough: the CronJob or DaemonSet recreates them within minutes.

  1. Preserve: save the specs (kubectl get cronjob,daemonset,deployment -A -o yaml), the audit logs and, if a container escape is possible, node disk snapshots.
  2. Find every copy: search all namespaces for the image, the pool address and the command, not just the one you found.
  3. Remove the controllers first (CronJobs, DaemonSets, Deployments, Jobs), then leftover pods.
  4. Remove the access: the binding and the service account used to create them, and the original entry point.
  5. Rebuild nodes if the miner ran privileged or with host mounts.
  6. Block egress to mining pools and restrict outbound traffic with network policies.
  7. Restrict registries with an admission policy (ValidatingAdmissionPolicy, Kyverno or Gatekeeper), ideally with signature verification.
  8. Check billing and the cloud audit log for anything created outside the cluster with the same credentials.

Related articles

Spot container escape preparation in Kubernetes audit logs: privileged pods, hostPID, hostNetwork, hostPath of / or runtime sockets, kube-system DaemonSets.
Find Kubernetes RBAC privilege escalation in audit logs: cluster-admin bindings, escalate, bind and impersonate verbs, impersonated calls, anonymous grants.
Detect Kubernetes secrets theft and stolen service account tokens in audit logs: cluster-wide lists, read bursts, TokenRequest, public-IP replay, can-i recon.

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.