T1613

Container and Resource Discovery

Discovery Last updated:

This detection identifies adversaries performing container and resource discovery within Docker and Kubernetes environments. Attackers who gain initial access to a container or cluster node often enumerate running containers, pods, services, nodes, namespaces, and cluster configuration to understand the environment and plan lateral movement. Common methods include executing Docker CLI commands (docker ps, docker inspect, docker images), Kubernetes CLI commands (kubectl get pods/nodes/namespaces/services), querying the Docker daemon socket or Kubernetes API server programmatically, scanning for kubelets with tools like masscan, and using offensive tools such as Peirates. Detection focuses on process execution of enumeration commands—especially from unexpected parent processes, non-administrative accounts, or container contexts—as well as anomalous API query patterns against the Kubernetes API server.

What is T1613 Container and Resource Discovery?

Container and Resource Discovery (T1613) maps to the Discovery tactic — the adversary is trying to figure out your environment in MITRE ATT&CK.

This page provides production-ready detection logic for Container and Resource Discovery, covering the data sources and telemetry it touches: Microsoft Defender for Endpoint. The queries below are rated medium severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Discovery
Technique
T1613 Container and Resource Discovery
Canonical reference
https://attack.mitre.org/techniques/T1613/
Microsoft Sentinel / Defender
kusto
let ContainerDiscoveryCommands = dynamic(["docker ps", "docker inspect", "docker images", "docker info", "docker stats", "docker network ls", "docker volume ls", "docker container ls"]);
let KubeDiscoveryPatterns = dynamic(["get pods", "get nodes", "get namespaces", "get services", "get deployments", "get secrets", "get configmaps", "describe pod", "describe node", "cluster-info", "get all", "get sa", "get serviceaccount"]);
let CrictlCommands = dynamic(["crictl ps", "crictl pods", "crictl images", "crictl info"]);
let SuspiciousParents = dynamic(["bash", "sh", "dash", "zsh", "python", "python3", "perl", "ruby", "nc", "ncat", "socat"]);
DeviceProcessEvents
| where TimeGenerated > ago(1d)
| where (
    // Docker enumeration commands
    (ProcessCommandLine has "docker" and (
        ProcessCommandLine has "ps" or
        ProcessCommandLine has "inspect" or
        ProcessCommandLine has "images" or
        ProcessCommandLine has "info" or
        ProcessCommandLine has "stats" or
        ProcessCommandLine has "network ls" or
        ProcessCommandLine has "volume ls" or
        ProcessCommandLine has "container ls"
    ))
    or
    // kubectl enumeration
    (FileName =~ "kubectl" and (
        ProcessCommandLine has "get pods" or
        ProcessCommandLine has "get nodes" or
        ProcessCommandLine has "get namespaces" or
        ProcessCommandLine has "get services" or
        ProcessCommandLine has "get deployments" or
        ProcessCommandLine has "get secrets" or
        ProcessCommandLine has "get configmaps" or
        ProcessCommandLine has "get all" or
        ProcessCommandLine has "cluster-info" or
        ProcessCommandLine has "describe" or
        ProcessCommandLine has "get sa" or
        ProcessCommandLine has "get serviceaccount"
    ))
    or
    // crictl (containerd CLI) enumeration
    (FileName =~ "crictl" and (
        ProcessCommandLine has "ps" or
        ProcessCommandLine has "pods" or
        ProcessCommandLine has "images" or
        ProcessCommandLine has "info"
    ))
    or
    // ctr (containerd) enumeration
    (FileName =~ "ctr" and (
        ProcessCommandLine has "containers list" or
        ProcessCommandLine has "images list" or
        ProcessCommandLine has "tasks list"
    ))
    or
    // Peirates and similar offensive tools
    (FileName =~ "peirates")
    or
    // curl/wget against Docker socket or Kubernetes API
    ((FileName in~ ("curl", "wget")) and (
        ProcessCommandLine has "/var/run/docker.sock" or
        ProcessCommandLine has ":8080/api" or
        ProcessCommandLine has ":6443/api" or
        ProcessCommandLine has ":10250" or
        ProcessCommandLine has "/api/v1/pods" or
        ProcessCommandLine has "/api/v1/nodes" or
        ProcessCommandLine has "/api/v1/namespaces"
    ))
)
| extend RiskScore = case(
    InitiatingProcessFileName in~ (SuspiciousParents), 80,
    AccountName !in~ ("root", "system") and FileName =~ "kubectl", 60,
    ProcessCommandLine has "get secrets", 90,
    ProcessCommandLine has "/var/run/docker.sock", 85,
    ProcessCommandLine has ":10250", 80,
    40
)
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, RiskScore
| where RiskScore >= 40
| order by RiskScore desc, TimeGenerated desc

Detects process execution of Docker, kubectl, crictl, and ctr enumeration commands used to discover containers, pods, nodes, namespaces, services, secrets, and cluster configuration. Also detects curl/wget queries against the Docker Unix socket or Kubernetes API endpoints (including the kubelet API on port 10250). Assigns risk scores based on parent process suspiciousness, sensitive resource types (secrets), and direct socket/API access.

medium severity medium confidence

Data Sources

Microsoft Defender for Endpoint

Required Tables

DeviceProcessEvents

False Positives

  • Legitimate DevOps engineers and SREs routinely run kubectl get pods/nodes and docker ps for operational monitoring and troubleshooting
  • CI/CD pipeline agents (Jenkins, GitLab Runner, GitHub Actions self-hosted) execute container enumeration commands as part of automated build, test, and deploy workflows
  • Kubernetes operators, admission controllers, and monitoring tools (Prometheus node-exporter, Datadog agent, Falco) query the kubelet API and Kubernetes API server continuously for health data
  • Container security scanners (Trivy, Anchore, Snyk) enumerate images and running containers during scheduled vulnerability assessments

Sigma rule & cross-platform mapping

The detection logic for Container and Resource Discovery (T1613) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  category: process_creation
  product: windows

Browse the community-maintained Sigma rules for this technique:


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.

  1. Test 1Docker Container Enumeration via CLI

    Expected signal: Linux auditd execve syscall events or Sysmon EventCode=1 (ProcessCreate) showing docker binary executions with arguments: ps, images, network ls, volume ls, info, inspect. Parent process will be the invoking shell.

  2. Test 2Kubernetes Cluster Enumeration via kubectl

    Expected signal: Process creation events showing kubectl binary with arguments: get nodes, get pods, get namespaces, get services, get deployments, get serviceaccounts, cluster-info, get clusterrolebindings, auth can-i. Multiple events in rapid succession.

  3. Test 3Kubernetes Secrets Enumeration via kubectl and Direct API

    Expected signal: Process creation events for kubectl with 'get secrets' argument (risk score 90 in detection). If curl is used: process creation for curl with Kubernetes API URL pattern and bearer token in command line. In-container token file access may also appear in auditd open/read syscall logs.


Response Playbook

Triage

  1. Step 1: Identify the executing account — run 'kubectl get rolebindings,clusterrolebindings -A | grep <username>' to determine what RBAC permissions the account has. A service account with cluster-admin or get secrets permissions is high severity.
  2. Step 2: Examine the parent process chain — if kubectl/docker was invoked from a shell spawned by a web server, database process, or unknown binary (not a terminal emulator or CI agent), treat this as likely post-exploitation activity.
  3. Step 3: Check the specific resources being enumerated — 'kubectl get secrets' and 'kubectl get serviceaccounts' are significantly higher risk than 'kubectl get pods', as they indicate credential harvesting intent.
  4. Step 4: Correlate with prior events — search for container escape indicators in the 30 minutes before this event (T1611: nsenter, unshare, cgroup writes, privileged container creation) that may have preceded the discovery phase.
  5. Step 5: Identify whether enumeration occurred from inside a container — run 'docker inspect <container_id>' or check if /proc/1/cgroup contains container runtime strings. Enumeration from within a container is more suspicious than from a host.
  6. Step 6: Check network connections from the affected host during the same window — look for outbound connections to unusual IPs that may indicate C2 communication alongside discovery activity.
  7. Step 7: Query audit logs for Kubernetes API server — run 'kubectl logs -n kube-system <apiserver-pod> | grep <username>' or review kube-apiserver audit logs at /var/log/kubernetes/audit.log for the full scope of API calls made.

Containment

  1. Revoke the compromised service account or user token immediately: 'kubectl delete secret <token-secret> -n <namespace>' and rotate the associated credentials in Vault or the secrets manager.
  2. If enumeration originated from within a running container, isolate it immediately: 'docker network disconnect <network> <container_id>' to remove network access, then capture the container state before stopping.
  3. Apply a NetworkPolicy to restrict egress from the affected namespace: 'kubectl apply -f deny-all-egress.yaml -n <namespace>' to prevent data exfiltration while investigation is ongoing.
  4. If a node is suspected to be compromised, cordon it to prevent new pod scheduling: 'kubectl cordon <node-name>' and drain non-critical workloads to other nodes.
  5. Rotate all service account tokens in namespaces that were enumerated, prioritizing any accounts with elevated permissions identified during triage.
  6. If Kubernetes secrets were accessed, rotate all secrets that were readable by the compromised identity immediately — treat all as compromised.

Evidence Collection

  1. Export Kubernetes API server audit logs: 'kubectl logs -n kube-system -l component=kube-apiserver --since=2h > kube-apiserver-audit.log' — these contain every API call with user identity, resource type, and timestamp.
  2. Collect Docker daemon logs: 'journalctl -u docker --since "2 hours ago" > docker-daemon.log' and the Docker daemon event stream via 'docker events --since 2h > docker-events.log'.
  3. Capture the process tree from the affected host: 'ps auxf > process-tree.txt' and 'cat /proc/*/cmdline | tr "\0" " " > all-cmdlines.txt' to identify all active processes.
  4. Export container inspection output for all running containers on the affected node: 'docker ps -q | xargs -I{} docker inspect {} > all-containers-inspect.json'.
  5. Collect the shell history of the account used: 'cat /home/<user>/.bash_history', '/home/<user>/.zsh_history', and '/root/.bash_history' — look for the full enumeration session.
  6. Retrieve Kubernetes RBAC state snapshot: 'kubectl get clusterrolebindings,rolebindings -A -o json > rbac-snapshot.json' to document what permissions existed at time of incident.
  7. If tools like Peirates were found, capture the binary hash: 'sha256sum /path/to/peirates > tool-hash.txt' and submit to VirusTotal for threat intelligence correlation.

Escalation Criteria

  • ! Escalate immediately if 'kubectl get secrets' was executed successfully — the adversary may now have credentials to cloud providers, databases, or other internal services stored as Kubernetes secrets.
  • ! Escalate if enumeration was performed by a service account that should not have interactive access (e.g., application service accounts running discovery commands indicates token theft or container compromise).
  • ! Escalate if container or resource discovery is followed within 15 minutes by any lateral movement indicators: new pod creation, exec into existing pods ('kubectl exec'), or node-to-node connections.
  • ! Escalate if the Kubernetes API audit log shows the adversary queried '/api/v1/namespaces' across all namespaces or '/apis/rbac.authorization.k8s.io/v1/clusterroles' — this indicates mapping of the entire cluster for follow-on exploitation.
  • ! Escalate if Peirates, kube-hunter (in exploit mode), or any other known Kubernetes offensive tool binary is identified on any node.

Investigation Guide

Forensic Artifacts

  • > Kubernetes API server audit log (/var/log/kubernetes/audit.log or cloud provider equivalent) — contains every API call with verb, resource, user, and response code
  • > Docker daemon log (/var/log/docker.log or journalctl -u docker) — records container start/stop events and API calls
  • > Shell history files (/root/.bash_history, /home/<user>/.bash_history, /home/<user>/.zsh_history) — manual enumeration commands
  • > Process accounting logs (/var/log/pacct if enabled) — records all executed commands with user and timestamp
  • > Linux auditd logs (/var/log/audit/audit.log) with execve syscall auditing enabled — captures exact command arguments
  • > Kubernetes etcd data — if adversary accessed etcd directly (port 2379), all cluster state including secrets would be exposed
  • > Container filesystem overlay (/var/lib/docker/overlay2/) — may contain tool binaries or downloaded scripts used for enumeration
  • > Network flow logs (VPC flow logs, eBPF-based tools like Cilium Hubble) — outbound connections to C2 during enumeration phase
  • > /proc/<pid>/environ for suspicious processes — may reveal injected environment variables or stolen credentials used for API access

Tuning Guidance

Start by creating an allowlist of legitimate kubectl/docker users: authorized DevOps accounts, CI/CD service account names, and monitoring tool service accounts. Filter these from the base detection using AccountName !in~ (allowlist). For kubectl discovery, the most reliable high-fidelity signal is 'get secrets' — prioritize those alerts regardless of the account. For docker socket curl access, whitelist known monitoring containers (Datadog agent, Prometheus node-exporter container IDs/names). For kubelet scanning detection, whitelist your Kubernetes control plane node IPs as legitimate sources of kubelet health checks. If running Falco in the cluster, correlate its container-level process events with host-level Defender events for higher-confidence detections — Falco can identify the specific container executing discovery commands, eliminating false positives from host-level administrative sessions. Consider increasing severity to high for any discovery that occurs within 30 minutes of a new container spawn or a process execution in a container that doesn't match the container's expected workload.


Hunting Queries

Hunts for accounts that enumerated 3 or more distinct Kubernetes resource types within the past 7 days, indicating systematic cluster discovery rather than routine operational lookups. High unique_resources counts suggest reconnaissance for lateral movement planning.

Hunting — KQL
kql
// Hunt for Kubernetes API enumeration via kubectl with broad resource scope
// Finds accounts that enumerated 3+ different resource types, indicating systematic discovery
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where FileName =~ "kubectl"
| extend ResourceType = extract(@"get\s+(\w+)", 1, ProcessCommandLine)
| where isnotempty(ResourceType)
| summarize
    UniqueResources = dcount(ResourceType),
    ResourceTypes = make_set(ResourceType),
    CommandCount = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by DeviceName, AccountName
| where UniqueResources >= 3
| extend DiscoveryBreadth = case(
    UniqueResources >= 7, "Comprehensive cluster enumeration",
    UniqueResources >= 5, "Broad enumeration",
    UniqueResources >= 3, "Targeted enumeration",
    "Single resource"
)
| order by UniqueResources desc
Hunting — SPL
spl
index=* (sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="linux_secure" OR sourcetype="auditd") earliest=-7d
| eval cmd=coalesce(CommandLine, command, "")
| eval process=coalesce(Image, process_name, comm, "")
| where match(process, "(?i)kubectl$")
| rex field=cmd "get\s+(?<resource_type>\w+)"
| where isnotnull(resource_type)
| stats dc(resource_type) as unique_resources, values(resource_type) as resource_types, count as cmd_count, min(_time) as first_seen, max(_time) as last_seen by host, user
| where unique_resources >= 3
| eval discovery_breadth=case(unique_resources >= 7, "comprehensive", unique_resources >= 5, "broad", 1==1, "targeted")
| sort - unique_resources

Hunts for programmatic access to the Docker Unix socket via curl/wget or custom tools, bypassing the Docker CLI. This pattern is used by malware (TeamTNT, Hildegard) that queries the Docker API directly to enumerate containers and find credential files or pivot opportunities without triggering docker-binary-based detections.

Hunting — KQL
kql
// Hunt for Docker socket access via raw HTTP from non-standard tools
// Detects scripted/programmatic container enumeration bypassing the Docker CLI
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where LocalPort == 0 and RemotePort == 0  // Unix socket events
| join kind=inner (
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where ProcessCommandLine has "/var/run/docker.sock"
    | project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
) on DeviceName
| union (
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where (ProcessCommandLine has "--unix-socket" or ProcessCommandLine has "-S /var/run/docker.sock")
        and (ProcessCommandLine has "/containers/json" or ProcessCommandLine has "/images/json" or ProcessCommandLine has "/info" or ProcessCommandLine has "/version")
    | project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
)
| summarize Count = count(), Commands = make_set(ProcessCommandLine, 10) by DeviceName, AccountName, InitiatingProcessFileName
| order by Count desc
Hunting — SPL
spl
index=* (sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="linux_secure" OR sourcetype="auditd") earliest=-7d
| eval cmd=coalesce(CommandLine, command, "")
| eval process=coalesce(Image, process_name, comm, "")
| where match(cmd, "(?i)(\/var\/run\/docker\.sock|\/containers\/json|\/images\/json)")
    AND NOT match(process, "(?i)(dockerd|containerd|docker-proxy)$")
| stats count as api_calls, values(cmd) as commands, values(process) as processes, min(_time) as first_seen, max(_time) as last_seen by host, user
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - api_calls

Hunts for hosts making connections to multiple kubelet API endpoints (port 10250) or the read-only kubelet port (10255), which is the signature behavior of Hildegard malware using masscan to discover all kubelets in the cluster subnet. Two or more distinct kubelet targets from one source indicates reconnaissance scanning.

Hunting — KQL
kql
// Hunt for kubelet API (port 10250) scanning or unauthenticated access
// Detects Hildegard/masscan-style discovery of kubelets across the cluster
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where RemotePort == 10250 or RemotePort == 10255  // kubelet API and read-only port
| summarize
    TargetCount = dcount(RemoteIP),
    TargetIPs = make_set(RemoteIP, 20),
    ConnectionCount = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName
| where TargetCount >= 2  // Connecting to multiple kubelets indicates scanning
| extend Verdict = case(
    TargetCount >= 10, "Mass kubelet scanning — high confidence reconnaissance",
    TargetCount >= 5, "Multi-node kubelet enumeration",
    TargetCount >= 2, "Targeted kubelet access",
    "Single kubelet access"
)
| order by TargetCount desc
Hunting — SPL
spl
index=* (sourcetype="stream:tcp" OR sourcetype="pan:traffic" OR sourcetype="cisco:asa" OR sourcetype="firewall") earliest=-7d dest_port=10250 OR dest_port=10255
| stats dc(dest_ip) as target_count, values(dest_ip) as target_ips, count as connection_count, min(_time) as first_seen, max(_time) as last_seen by src_ip, src_host, process
| where target_count >= 2
| eval verdict=case(target_count >= 10, "mass_scanning", target_count >= 5, "multi_node_enum", 1==1, "targeted_access")
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - target_count

Atomic Red Team Tests

Test 1 Docker Container Enumeration via CLI
linux

Simulates an adversary using Docker CLI commands to enumerate running containers, images, networks, and volumes — replicating TeamTNT discovery behavior documented in the wild.

Command

bash
docker ps -a --format '{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}' && docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' && docker network ls && docker volume ls && docker info | grep -E '(Containers|Images|Server Version|Operating System)' && docker inspect $(docker ps -q | head -1) 2>/dev/null | python3 -m json.tool | head -50

Cleanup

bash
# No cleanup needed — read-only enumeration commands

Expected Telemetry

Linux auditd execve syscall events or Sysmon EventCode=1 (ProcessCreate) showing docker binary executions with arguments: ps, images, network ls, volume ls, info, inspect. Parent process will be the invoking shell.

Expected Detection

Alert: Container and Resource Discovery — Docker enumeration commands executed. Risk score 40-60 depending on parent process context.

Test 2 Kubernetes Cluster Enumeration via kubectl
linux

Simulates systematic Kubernetes cluster discovery across multiple resource types as an adversary would perform after gaining initial access with a kubeconfig or service account token.

Command

bash
kubectl get nodes -o wide && kubectl get pods --all-namespaces && kubectl get namespaces && kubectl get services --all-namespaces && kubectl get deployments --all-namespaces && kubectl get serviceaccounts --all-namespaces && kubectl cluster-info && kubectl get clusterrolebindings | head -20 && kubectl auth can-i --list 2>/dev/null

Cleanup

bash
# No cleanup needed — read-only enumeration commands

Expected Telemetry

Process creation events showing kubectl binary with arguments: get nodes, get pods, get namespaces, get services, get deployments, get serviceaccounts, cluster-info, get clusterrolebindings, auth can-i. Multiple events in rapid succession.

Expected Detection

Alert: Container and Resource Discovery — kubectl enumerating 5+ resource types. Risk score 60+. Hunting query should flag this account for broad discovery breadth.

Test 3 Kubernetes Secrets Enumeration via kubectl and Direct API
linux

Simulates high-value container discovery targeting Kubernetes secrets, which may contain cloud provider credentials, database passwords, and API keys. Also demonstrates direct API server access using a service account bearer token.

Command

bash
# Method 1: kubectl secrets enumeration
kubectl get secrets --all-namespaces -o json | python3 -c "import json,sys; data=json.load(sys.stdin); [print(item['metadata']['namespace'], item['metadata']['name'], item['type']) for item in data['items']]" 2>/dev/null

# Method 2: Direct Kubernetes API access with service account token (simulates in-container access)
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || echo "TOKEN_NOT_AVAILABLE")
KUBE_API=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || echo "https://kubernetes.default.svc")
if [ "$SA_TOKEN" != "TOKEN_NOT_AVAILABLE" ]; then
  curl -sk -H "Authorization: Bearer $SA_TOKEN" $KUBE_API/api/v1/namespaces 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30
  curl -sk -H "Authorization: Bearer $SA_TOKEN" $KUBE_API/api/v1/pods 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30
fi

Cleanup

bash
# No cleanup needed — read-only enumeration commands

Expected Telemetry

Process creation events for kubectl with 'get secrets' argument (risk score 90 in detection). If curl is used: process creation for curl with Kubernetes API URL pattern and bearer token in command line. In-container token file access may also appear in auditd open/read syscall logs.

Expected Detection

Alert: Container and Resource Discovery — kubectl get secrets executed (highest risk score 90). Immediate escalation criteria met. If curl API method used: secondary alert for programmatic API enumeration.

Related Detections

Tactic Hub