Detect Kubernetes Cryptojacking — Cloud Compute Hijacking via Malicious Pod Deployment in CrowdStrike LogScale
Cryptojacking crews (TeamTNT, Kinsing, WatchDog, Kiss-a-dog, and opportunistic campaigns like RBAC Buster/SCARLETEEL follow-on activity) systematically scan the internet for exposed Kubernetes control planes: anonymous-auth API servers, unauthenticated kubelet read-only ports (10255/10250), exposed dashboards (Weave Scope, Kubeflow), or leaked/over-privileged service account tokens harvested from a prior web-app compromise. Once inside, the attacker uses `kubectl create`/`apply` or a direct API call to deploy a DaemonSet or high-replica Deployment across every node in the cluster, pulling a miner image (XMRig, kdevtmpfsi, kinsing) from a public or attacker-controlled registry, frequently disguised behind a legitimate-looking image name or tag. Pods routinely request the maximum CPU the node scheduler will allow (or omit resource limits entirely), starving legitimate workloads and driving the victim's cloud compute bill up dramatically — a single hijacked node-pool can run into thousands of dollars per day on AWS EKS, Azure AKS, or GCP GKE. Many variants also set `hostPID`/`hostNetwork: true` or mount `/var/run/docker.sock`/hostPath `/` to escape the container and persist directly on the underlying node, then disable or kill competing miners and security agents (Falco, cloud-native EDR) before establishing outbound connections to a mining pool over Stratum protocol. Detection requires correlating Kubernetes audit logs (anomalous pod/DaemonSet creation, especially from a ServiceAccount or anonymous identity with no prior deployment history), container runtime/stdout logs (miner banner strings), and node-level network telemetry (outbound to known Stratum ports and mining-pool domains).
MITRE ATT&CK
- Tactic
- Impact
LogScale Detection Query
// Falcon sensor does not natively parse Kubernetes audit events; assumes kube-audit and container stdout ingested into LogScale via a Fluentd/Fluent Bit collector into #repo=k8s_audit
#repo=k8s_audit
("xmrig" OR "kinsing" OR "kdevtmpfsi" OR "cryptonight" OR "monero" OR ("hostNetwork" AND "true") OR ("hostPID" AND "true") OR "stratum+tcp" OR "donate-level" OR "randomx")
| Indicator := case {
/(?i)(xmrig|kinsing|kdevtmpfsi|cryptonight|monero)/ => "SuspiciousWorkloadCreate" ;
/(?i)(hostnetwork.*true|hostpid.*true)/ => "SuspiciousWorkloadCreate" ;
/(?i)(stratum\+tcp|donate-level|randomx)/ => "MinerBannerInLogs" ;
* => "Other"
}
| groupBy([#repo, Indicator], function=[count(as=EventCount), collect(@rawstring, limit=20)], limit=max)
| sort(EventCount, order=desc) CrowdStrike LogScale query over Kubernetes audit and container log events ingested into a dedicated LogScale repository (not Falcon sensor telemetry directly, since audit events originate from the control plane, not an endpoint agent). For node-level network connections, pair with a Falcon sensor-based LogScale query against #event_simpleName=NetworkConnectIP4 filtering RemotePort in the known Stratum port set, since Falcon sensors deployed on Kubernetes worker nodes do capture process-level network telemetry.
Data Sources
Required Tables
False Positives & Tuning
- Legitimate blockchain/crypto research workloads sandboxed in a dedicated namespace
- Batch/ML training workloads that omit CPU limits by design
- CNI/service-mesh DaemonSets requiring hostNetwork:true
- CI/CD self-hosted runner DaemonSets requiring hostPID
Other platforms for THREAT-K8s-CryptojackingResourceHijack
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Simulated Miner DaemonSet Deployment via kubectl (Lab Only)
Expected signal: Kubernetes audit log (kube-audit) records a 'create' event for objectRef.resource=daemonsets with objectRef.name=xmrig-test-457, requestObject containing hostNetwork:true, hostPID:true, and the image name 'xmrig-test-457' in metadata/labels.
- Test 2Simulated Miner Banner in Container Logs (Lab Only)
Expected signal: ContainerLogV2/kube:container:log records the literal banner string containing 'XMRig', 'stratum+tcp', 'cryptonight', and 'donate-level' for the pod miner-banner-test-457.
- Test 3Simulated Outbound Connection to Stratum Mining Port (Lab Only)
Expected signal: Node-level network telemetry (DeviceNetworkEvents / netflow) records 4 ConnectionSuccess events to RemotePort=3333 from the test host within a single 10-minute window.
References (7)
- https://attack.mitre.org/techniques/T1496/001/
- https://attack.mitre.org/techniques/T1610/
- https://attack.mitre.org/techniques/T1611/
- https://attack.mitre.org/tactics/TA0040/
- https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/
- https://sysdig.com/blog/tor-teamtnt-mining-orchestration/
- https://www.crowdstrike.com/en-us/blog/kinsing-malware-attacks-targeting-container-environments/
Response Playbook
Triage
- Identify the workload: `kubectl get pods,daemonsets,deployments -A -o wide` filtered to the namespace/name from the alert, and pull the full pod spec with `kubectl get pod <name> -n <ns> -o yaml` to confirm the container image, resource requests/limits, and any hostNetwork/hostPID/hostPath settings
- Check node CPU utilization via cluster metrics (Container Insights, Prometheus/Grafana, or `kubectl top nodes`) — cryptojacking pods typically drive affected nodes to sustained 90-100% CPU well outside the workload's normal baseline
- Identify the creating identity from the Kubernetes audit event (`user.username` — is it a legitimate CI/CD service account, a human user, or an anonymous/system:unauthenticated identity indicating an exposed API server or kubelet?)
- Pull container logs (`kubectl logs <pod> -n <ns>`) to confirm miner banner strings and identify the configured mining pool address/wallet, which can be used for threat-intel pivoting and takedown reporting
- Check whether the pod has `hostPID: true`, `hostNetwork: true`, or a hostPath mount of `/`, `/var/run/docker.sock`, or `/proc` — these indicate an attempted or successful container escape onto the underlying node, which changes the response from 'delete the pod' to 'reimage the node'
- Review recent RBAC bindings and ServiceAccount token usage in the namespace for signs of privilege escalation that enabled the deployment (e.g. an over-privileged default ServiceAccount, or a stolen token used from an unfamiliar source IP)
Containment
- Immediately scale the malicious DaemonSet/Deployment to zero replicas and then delete it (`kubectl delete daemonset/deployment <name> -n <ns>`) rather than deleting individual pods, since the controller will otherwise recreate them
- Cordon and, if hostPID/hostNetwork/hostPath escape indicators are present, drain and reimage every node the workload ran on (`kubectl cordon <node>` then `kubectl drain <node> --ignore-daemonsets`) — do not trust node-level integrity after a confirmed or suspected container escape
- Revoke and rotate the ServiceAccount token or user credentials that created the workload; if the API server or kubelet was reachable anonymously, immediately disable anonymous-auth and restrict network access to the control plane and kubelet ports (10250/10255) to trusted CIDRs only
- Block outbound network egress to the identified mining pool IPs/domains at the cluster's NetworkPolicy or cloud provider NSG/security-group level, and apply a default-deny egress NetworkPolicy to the affected namespace(s) if one is not already enforced
- Audit and remove any persistence the attacker may have added — new ClusterRoleBindings, additional ServiceAccounts with elevated permissions, CronJobs that could redeploy the miner, or modified admission webhooks
Evidence Collection
- Full Kubernetes audit log entries for the create/update/patch events, including the complete request and response bodies, for chain-of-custody and to identify every resource the attacker touched
- The full pod/DaemonSet/Deployment manifest (`kubectl get ... -o yaml`) before deletion, including image digest (not just tag) for threat-intel correlation
- Container stdout/stderr logs capturing the mining pool address, wallet ID, and any donate-level configuration
- Node-level network flow records showing the full set of destination IPs/ports contacted during the incident window
- A snapshot of the affected node's process list and any suspicious binaries dropped outside the container filesystem if a host escape is suspected (e.g. via a forensic node image before reimaging)
- Cloud billing/cost-anomaly data (AWS Cost Explorer, Azure Cost Management, GCP Billing) showing the compute cost spike, useful for both scoping and any insurer/vendor engagement
Escalation Criteria
- !Any workload creation from an anonymous (`system:unauthenticated`) or unrecognized identity, indicating an exposed control plane or kubelet requiring immediate network-level remediation
- !Confirmed or suspected container escape (hostPID/hostNetwork/hostPath-to-root mount combined with miner banner evidence) — treat the underlying node as compromised, not just the pod
- !The same miner image or mining pool address observed across multiple clusters or namespaces, indicating a broader campaign rather than an isolated misconfiguration
- !Evidence that the attacker disabled or deleted security tooling (Falco rules, Defender for Containers agents) prior to or immediately after deployment, indicating deliberate evasion rather than opportunistic scanning
- !A sustained cloud compute cost spike exceeding normal variance for more than one billing cycle, which may require finance/vendor engagement in addition to security remediation
Investigation Guide
Related Techniques
Forensic Artifacts
- >
Kubernetes audit log (kube-audit): full request/response bodies for every create/update/patch/delete against pods, daemonsets, deployments, replicasets, and RBAC objects during the incident window - >
Container runtime logs (containerd/CRI-O journal, or ContainerLogV2/kube:container:log): stdout/stderr including miner startup banners with pool address and wallet ID - >
Node-level process and network telemetry (Defender for Endpoint, Falco, auditd): the actual miner process (commonly disguised as a legitimate-looking binary name) and its outbound connections - >
ServiceAccount token audit trail and RBAC bindings active in the namespace at the time of the incident, to reconstruct the privilege path used - >
Cloud provider IAM/CloudTrail-equivalent logs if the cluster's node pool or control plane was provisioned/modified (e.g. a new node pool scaled up by the attacker to maximize hashrate) - >
Cloud cost/billing anomaly reports correlating the compute spike window with the workload's creation and deletion timestamps
Tuning Guidance
The single highest-value tuning step is maintaining an allowlist of known-legitimate hostNetwork/hostPID DaemonSets (CNI plugins, service mesh, approved monitoring agents) by name/namespace, since these are common and otherwise drown the SuspiciousWorkloadCreate signal in noise — most clusters have fewer than 10 such DaemonSets and the list changes rarely. For the network signal, exclude your organization's own approved mining-adjacent research namespaces (if any) and confirm the Stratum port list against current threat intelligence, since some campaigns rotate to non-standard ports or tunnel over 443/8443 to blend with HTTPS traffic — in that case, rely more heavily on the audit-log and container-log signals. Treat any workload creation from `system:unauthenticated` or an unexpected anonymous identity as a standing high-severity alert regardless of image name, since a properly configured cluster should never accept anonymous writes to the API server. Cross-reference cost-anomaly alerts from the cloud provider's billing system as a lagging but very high-confidence corroborating signal — a genuine cryptojacking incident will almost always show up as an unexplained compute cost spike within 24 hours of the workload's creation.
Hunting Queries
Baseline hunt across 14 days of Kubernetes audit logs for any DaemonSet/Deployment created with hostNetwork or hostPID enabled and no CPU resource limits set — a strong combined indicator of either a misconfigured internal workload (worth fixing regardless) or a cryptojacking/container-escape attempt worth investigating further.
// Hunt: DaemonSets/Deployments created in the last 14 days with no CPU limit set and hostNetwork or hostPID true
AKSAudit
| where TimeGenerated > ago(14d)
| where Verb == "create" and ObjectRef.resource in ("daemonsets", "deployments")
| extend RequestBody = tostring(RequestObject)
| where (RequestBody has "hostNetwork" and RequestBody has "true") or (RequestBody has "hostPID" and RequestBody has "true")
| where RequestBody !has "resources" or RequestBody !has "limits"
| project TimeGenerated, User = tostring(User.username), Namespace = tostring(ObjectRef.namespace), ObjectName = tostring(ObjectRef.name)
| order by TimeGenerated desc index=k8s_audit sourcetype="kube:audit" verb="create" (objectRef.resource="daemonsets" OR objectRef.resource="deployments")
((requestObject="*hostNetwork*true*") OR (requestObject="*hostPID*true*"))
NOT (requestObject="*limits*")
| table _time, user.username, objectRef.namespace, objectRef.name
| sort - _time Wider network-only hunt across 7 days that does not depend on Kubernetes audit visibility — useful when audit logging was not enabled at the time of compromise, or to catch mining activity from a compromised VM/node outside the cluster entirely.
// Hunt: nodes with sustained 3+ connections/10min to known Stratum mining ports over the last 7 days
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where ActionType == "ConnectionSuccess"
| where RemotePort in (3333, 4444, 5555, 7777, 8080, 14444, 45700, 14433)
| summarize ConnectionCount = count(), DistinctRemoteIPs = dcount(RemoteIP) by DeviceName, bin(TimeGenerated, 10m)
| where ConnectionCount >= 3
| order by ConnectionCount desc index=network sourcetype="netflow" (dest_port=3333 OR dest_port=4444 OR dest_port=5555 OR dest_port=7777 OR dest_port=8080 OR dest_port=14444 OR dest_port=45700 OR dest_port=14433)
| bin _time span=10m
| stats count as ConnectionCount, dc(dest_ip) as DistinctRemoteIPs by src_ip, _time
| where ConnectionCount>=3
| sort - ConnectionCount Atomic Red Team Tests
Replicates the core cryptojacking deployment pattern: creating a cluster-wide DaemonSet referencing a miner-pattern image name with hostNetwork enabled and no resource limits. Uses a harmless busybox image with a fake tag to avoid actually running miner software — only the audit-log fingerprint is tested. WARNING: run only against a disposable lab cluster/namespace, never a production cluster.
Command
kubectl create namespace atomic-test-457 && kubectl apply -n atomic-test-457 -f - <<'EOF'
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: xmrig-test-457
spec:
selector:
matchLabels: {app: xmrig-test-457}
template:
metadata:
labels: {app: xmrig-test-457}
spec:
hostNetwork: true
hostPID: true
containers:
- name: xmrig-test-457
image: busybox:1.36
command: ["sleep", "300"]
EOF Cleanup
kubectl delete namespace atomic-test-457 Expected Telemetry
Kubernetes audit log (kube-audit) records a 'create' event for objectRef.resource=daemonsets with objectRef.name=xmrig-test-457, requestObject containing hostNetwork:true, hostPID:true, and the image name 'xmrig-test-457' in metadata/labels.
Expected Detection
KQL/SPL SuspiciousWorkloadCreate signal fires on the miner-name-pattern match ('xmrig') in the request body plus the hostNetwork/hostPID:true condition.
Writes XMRig-style startup banner text to container stdout to validate the log-based detection signal without running actual mining software or contacting a real pool.
Command
kubectl run miner-banner-test-457 -n atomic-test-457 --image=busybox:1.36 --restart=Never -- sh -c "echo 'XMRig 6.21.0 - donate-level 0, cryptonight, randomx algo initialized, connecting stratum+tcp://pool.example.lab:3333' && sleep 60" Cleanup
kubectl delete pod miner-banner-test-457 -n atomic-test-457 Expected Telemetry
ContainerLogV2/kube:container:log records the literal banner string containing 'XMRig', 'stratum+tcp', 'cryptonight', and 'donate-level' for the pod miner-banner-test-457.
Expected Detection
KQL/SPL MinerBannerInLogs signal fires on the banner text match.
Opens repeated TCP connections from a lab node to a disposable internal listener on a known Stratum mining port (3333) to validate the network-based detection signal without contacting any real mining pool infrastructure.
Command
nc -l -p 3333 & for i in 1 2 3 4; do timeout 1 bash -c 'echo test > /dev/tcp/127.0.0.1/3333'; sleep 1; done Cleanup
pkill -f 'nc -l -p 3333' || true Expected Telemetry
Node-level network telemetry (DeviceNetworkEvents / netflow) records 4 ConnectionSuccess events to RemotePort=3333 from the test host within a single 10-minute window.
Expected Detection
KQL/SPL MiningPoolConnection signal fires once the 3-connections-per-10-minutes threshold to a known Stratum port is crossed.
Related Detections
Tactic Hub
Detection Variants (1)
Different telemetry and tradecraft for the same technique — pick the one that matches the data you collect.