On this page

In December, I published a post on securing the container supply chain โ€” SBOM generation, image signing, and build provenance with GitHub Actions. That covered build-time security: making sure the image you ship is the image you built.

But what happens after deployment? Once a container is running on AKS, how do you detect a compromised workload, block an attacker from dropping a cryptominer binary, or prevent a vulnerable image from ever reaching the cluster?

That’s where runtime security comes in. Microsoft has shipped three features in Defender for Containers that, together, form a layered runtime defense stack:

LayerFeatureStatusWhat It Does
Deploy-time gateGated DeploymentGAEvaluates images at admission; configured rules can audit or deny matching images
Runtime detectionBinary DriftGADetects external executables and can alert on or block them within the documented support constraints
Runtime protectionContainer Anti-MalwareGADetects and blocks malware inside running containers

This post walks through preparing all three on AKS with Defender for Cloud, building Sentinel detections and a workbook for documented runtime alerts, and reviewing gated-deployment decisions in Admission Monitoring.

Revision boundary: the current hardened source was validated with Bicep compilation, PowerShell parsing, and mocked safety/rollback tests. It was not freshly deployed to Azure. Portal captures and observed alerts below are historical evidence from an earlier lab run, not proof that the current pinned revision was live-deployed or that a feature will behave identically in every subscription, region, cluster, or sensor version.

Hands-on Lab: All Bicep templates, KQL queries, and deployment scripts are in the reviewed companion revision.


Why Runtime Security Matters

Build-time controls (scanning, signing, attestation) are necessary but not sufficient. Here’s why:

  • Containers drift. Attackers use kubectl exec to drop binaries, install tools, or modify configs inside running containers. The image was clean at deploy time; the runtime is not.
  • Zero-days bypass scanners. A vulnerability unknown at build time can be exploited in production before the next scan runs.
  • Supply chain attacks target runtime. Compromised base images, malicious init containers, and sidecar injection all happen after the image passes CI/CD gates.
  • Legitimate images can be weaponized. An attacker who gains cluster access can deploy a clean ubuntu image and use it as a beachhead โ€” no CVEs, no signatures to catch.

Microsoft’s own threat intelligence found that 51% of workload identities were completely inactive โ€” representing dormant attack vectors that threat actors exploit for lateral movement, making runtime defense essential.

MITRE ATT&CK Mapping

TechniqueIDDetection Layer
Exploit Public-Facing ApplicationT1190Gated Deployment, Anti-Malware
Command and Scripting InterpreterT1059Binary Drift
Ingress Tool TransferT1105Binary Drift, Anti-Malware
Deploy ContainerT1610Gated Deployment
Native APIT1106Binary Drift
Impair DefensesT1562Binary Drift (sensor tampering)

Architecture

The lab deploys a three-layer defense architecture on AKS:

Architecture diagram showing Container Registry, Defender for Cloud with Gated Deployment, Binary Drift Detection, and Anti-Malware Protection, AKS Cluster with Defender Sensor DaemonSet monitoring pods, and Log Analytics feeding into Microsoft Sentinel

Three-layer defense architecture: Gated Deployment evaluates images at admission and reports decisions in Admission Monitoring; the Defender sensor monitors runtime binary drift and malware; documented runtime alerts and Kubernetes audit logs feed Sentinel for SOC visibility.

Components

  1. AKS Cluster โ€” Single-node cluster with workload identity and Azure CNI
  2. Defender for Containers โ€” Plan enabled at subscription level
  3. Defender Sensor โ€” DaemonSet deployed through the lab bundle’s tested Helm chart pin (0.11.4), with its anti-malware collector explicitly enabled; verify that pin against Microsoft’s current supported sensor line before deployment
  4. Log Analytics Workspace โ€” Collects SecurityAlert, ContainerLogV2, and KubePodInventory tables
  5. Sentinel โ€” Analytics rules and workbook for SOC visibility

Because the sensor is Helm-managed, the operator owns chart review and upgrades. Microsoft notes that the portal can label a Helm-installed subscription not supported for Gated deployment; if the rule flow offers to auto-install components, choose Skip to avoid a conflicting managed deployment. Gated deployment also requires Kubernetes 1.31 or later, OIDC on AKS, Defender sensor with Security Gating, Registry access with Security findings, and vulnerability findings artifacts for evaluated images. The lab deploys AKS 1.35 with OIDC, but those service-side prerequisites still need to be verified before attributing an admission result to the policy.


Lab Deployment

Prerequisites

  • Azure subscription with Owner or Contributor + User Access Administrator role
  • Azure CLI v2.60+
  • kubectl compatible with AKS Kubernetes 1.35
  • Helm v3.12+
  • PowerShell 7 (for Deploy-Lab.ps1)
  • Permission to read the lab Log Analytics workspace ID and shared key for Defender sensor telemetry

One-Command Deploy

git clone https://github.com/j-dahl7/aks-runtime-security-lab.git
cd aks-runtime-security-lab
git checkout 424b49c737f4837ea8a763b9c589003a57a44cf7
# Preview only โ€” no Azure, kubeconfig, Kubernetes, Helm, or secret-file mutations
./scripts/Deploy-Lab.ps1 -Location "eastus" -WhatIf

# If the required paid subscription settings are not already enabled, review
# the active subscription and pricing before explicitly authorizing the change.
$env:CONFIRM_SUBSCRIPTION_SCOPE = "ENABLE-DEFENDER-FOR-CONTAINERS"

# Deploy everything (the confirmation is unnecessary when settings already match)
./scripts/Deploy-Lab.ps1 -Location "eastus"

# Infrastructure only (skip Sentinel rules)
./scripts/Deploy-Lab.ps1 -Location "eastus" -SkipSentinel

The script creates:

  1. AKS 1.35 cluster (single node, Standard_D4s_v3) via Bicep
  2. Log Analytics workspace with Container Insights
  3. Defender for Containers plan enablement (with AntiMalware extension)
  4. Defender sensor via the lab bundle’s tested Helm chart pin (0.11.4) with anti-malware collector; verify current Microsoft support and cluster compatibility first
  5. 3 Sentinel analytics rules for binary drift, malware, and kubectl exec
  6. 1 Sentinel workbook

Portal steps required before testing: Configure the binary drift policy in Defender for Cloud > Environment Settings > Containers drift policy. The default is “Ignore drift detection”; change it to Drift detection alert, or use the GA Drift detection blocking action only after confirming the current cluster, cloud, sensor, policy, and network prerequisites. Review the anti-malware policy and Alert/Block action; policy updates can take up to 30 minutes to reach sensors. Then create a gated-deployment vulnerability-assessment rule under Environment Settings > Security Rules. Start with Audit, validate decisions in Gated deployment > Admission Monitoring, and move to Deny only when the intended scope and thresholds are correct. The deployment script does not create these policies.

After reviewing the three tracked queries, explicitly enable the disabled Sentinel rules and then run the bounded test helper:

./scripts/Deploy-Lab.ps1 -Location "eastus" -EnableSentinelRules
./scripts/Test-RuntimeSecurity.ps1

Deployment safety update โ€” July 25, 2026: -WhatIf exits after a preview-only summary. Before changing protection, the bundled deployment script checks for the known auto-provision policy, conflicting Defender Helm releases, and exact stale cluster-scoped resources. Helm runs with --atomic; credentials are passed through an owner-only temporary values file and deleted in finally; and a failed installation restores the captured Defender workspace/profile and exact prior exclusion-tag state. The bounded regression harness is pwsh ./tests/Test-DeployLabSafety.ps1.

Supported Deployment Path

Use Deploy-Lab.ps1 for the entire lifecycle. Direct Bicep deployment or a raw az security pricing write bypasses the manifest, collision preflight, shared setting snapshot, Helm rollback, and Sentinel ownership checks that make reruns and cleanup safe.

Cleanup

./scripts/Deploy-Lab.ps1 -Destroy

The guarded cleanup requires the exact manifest, validates the resource-group owner token, deletes the owned resource group synchronously, and restores the captured pre-lab Defender pricing and extensions only when the current shared state still exactly matches what the lab wrote. Do not delete the resource group directly; that would bypass the rollback transaction.


Layer 1: Gated Deployment

Gated deployment evaluates container images during Kubernetes admission. A matching security rule can record an Audit admission event or, when its action is explicitly set to Deny, prevent the workload from reaching the cluster.

How Gated Deployment Works

  1. When a pod creation request hits the Kubernetes API server, the Defender admission webhook intercepts it
  2. The webhook checks the image digest against Defender for Cloud’s vulnerability assessment database
  3. If the image has unresolved critical/high CVEs that match your security rules, the deployment is denied
  4. In Audit mode, the deployment proceeds and Defender creates an admission event for review; Deny rejects matching deployments

Configuration

Enable gated deployment in the Defender for Cloud portal:

  1. Navigate to Environment Settings > your subscription > Settings & Monitoring
  2. Under Containers, enable the Defender Sensor (with Security Gating) and Registry Access (with Security Findings) extensions
  3. Go to Environment Settings > Security Rules > Vulnerability Assessment tab
  4. Select Add Rule:
    • Action: Start with Audit (allows deployment and records an admission event), switch to Deny after testing
    • Scope: Select your AKS cluster or apply subscription-wide
    • Conditions: Block images with Critical or High severity CVEs with available fixes

Testing Gated Deployment

# Use Microsoft's documented gated-deployment test image
kubectl run vuln-test --image=mcr.microsoft.com/mdc/dev/defender-admission-controller/test-images:one-high --restart=Never

# In Audit mode: the request is allowed; review Admission Monitoring
# In Deny mode: pod creation is rejected

An effective matching Deny rule returns an admission-webhook error. An allowed request can mean Audit mode, no matching rule, a missing vulnerability findings artifact, or an incomplete prerequisite. Review the authoritative timestamp, action, image digest, violations, triggered rule, and exemptions under **Defender for Cloud > Environment settings > Security rules > Gated deployment

Admission Monitoring**. Microsoft does not document a corresponding Sentinel SecurityAlert AlertType named GatedDeployment, so the lab does not invent one.


Layer 2: Binary Drift Detection

Binary drift is a valuable runtime signal. Container images are designed to be immutable, so a process whose executable was added after deployment can indicate compromise or an unauthorized operational change. On a supported, protected cluster with a configured drift policy, Defender can alert on or block that external process. Blocking has additional cluster, cloud, sensor, and Helm-version prerequisites, so verify the current support matrix before enabling it.

How Binary Drift Works

Microsoft documents the capability in terms of its behavioral contract rather than its internal implementation:

  1. Observe runtime processes โ€” The Defender sensor supplies process activity from supported, protected clusters.
  2. Identify an external process โ€” Defender determines that the executable did not originate in the container’s original image.
  3. Evaluate policy โ€” Configured binary-drift rules are evaluated in priority order, and the first matching rule supplies the outcome.
  4. Apply the configured action โ€” The outcome is GA drift-detection alerting, GA drift-detection blocking where supported, or ignore. Do not assume undocumented syscall interception, hash-comparison, or process-termination mechanics beyond that published contract.

Configuring Drift Policy

Navigate to Defender for Cloud > Environment Settings > Containers drift policy:

SettingValue
Rule nameBlock drift in production namespaces
ActionDrift detection alert or Drift detection blocking (GA within documented support constraints)
Scope โ€” Namespaceequals: default, production, app
Allow list/usr/bin/apt-get, /usr/bin/dpkg (if needed for init scripts)

Warning: The drift policy defaults to Ignore drift detection โ€” no alerts are generated until you explicitly change this setting. There is currently no REST API for drift policy configuration; it must be set in the portal.

Tip: Start with alerting on the intended namespaces. Consider blocking only after reviewing measured alerts, confirming current support and prerequisites, and validating exclusions and rollback behavior in a non-production scope.

Testing Binary Drift

# Deploy a clean nginx container
kubectl run drift-test --image=nginx:1.27-alpine --restart=Never

# Wait for pod to be running
kubectl wait --for=condition=Ready pod/drift-test --timeout=60s

# Exec in and create a binary that doesn't exist in the original image
kubectl exec drift-test -- /bin/sh -c \
  "echo '#!/bin/sh' > /tmp/notinimage.sh && chmod +x /tmp/notinimage.sh && /tmp/notinimage.sh"

When the feature, sensor, policy, and alert-ingestion path are healthy, Defender can generate a Binary drift detected alert; this source review does not promise a fixed latency or outcome:

  • Alert severity: Medium (detect) or High (block)
  • Alert data: Container name, pod name, namespace, cluster, the drifted binary path
  • MITRE mapping: T1105 (Ingress Tool Transfer), T1059 (Command and Scripting Interpreter)
Defender for Cloud alert showing binary drift detected in a container

Defender for Cloud flags the drifted binary with full container context โ€” pod name, namespace, cluster, and MITRE tactic mapping.

KQL โ€” Binary Drift Alerts

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend DriftedBinary = tostring(ExtProps["Suspicious Process"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, ClusterName,
    Namespace, PodName, ContainerName, DriftedBinary
| sort by TimeGenerated desc

KQL โ€” Binary Drift Trend (Last 30 Days)

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ClusterName = CompromisedEntity
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| where isnotempty(ClusterName)
| summarize DriftCount = count() by bin(TimeGenerated, 1d), ClusterName, Namespace
| render timechart

Layer 3: Container Anti-Malware

Container antimalware was introduced in preview in February 2026 and its detection and blocking capabilities are now generally available. Microsoft’s current Defender for Cloud support documentation lists the supported clouds and cluster constraints; verify those boundaries and the sensor version before deployment. This layer catches what binary drift alone does not classify: executables that Defender identifies as malware at runtime.

Version and compatibility boundary: This lab enables antimalware in two places: (1) the subscription-level ContainerSensor extension has AntiMalwareEnabled: True, and (2) the tested lab bundle pins Defender Helm chart 0.11.4 and sets microsoft-defender-for-containers-sensor.antimalwareCollector.enabled=true. That pin records what the bundle tested; it is not a claim that 0.11.4 is Microsoft’s latest sensor patch. Microsoft’s current sensor changelog lists the 0.11 line as GA, records 0.11.4 as a July 2026 GA release, and lists 0.11.5 as the August 2026 GA patch. Confirm the latest supported sensor/chart version and AKS compatibility for your environment before deployment. The deployment prevents the managed AKS Defender profile and Helm-managed sensor from coexisting.

How Container Anti-Malware Works

Microsoft documents the capability at its control boundary:

  1. Defender sensor and anti-malware collector โ€” The lab bundle tested chart pin 0.11.4; confirm a currently supported, cluster-compatible version before deployment.
  2. Executable launch โ€” Runtime anti-malware evaluates when a container runs an executable that the system identifies as malicious. Writing a file alone is not the documented trigger.
  3. Ordered policy evaluation โ€” Rules can scope by cluster, image, namespace, pod, container, or labels. The first matching rule supplies the action; default workload and host rules apply when no higher-priority rule matches.
  4. Configured action โ€” The documented actions are Ignore Malware, Alert on Malware, and Block Malware. Treat a process exit code as supporting evidence only; verify the policy decision and resulting security alert.

Configuring Anti-Malware Rules

Navigate to Defender for Cloud > Environment Settings > Security rules > Antimalware:

SettingValue
Rule nameBlock malware in all namespaces
ActionBlock Malware or Alert on Malware (start with alerting while validating scope)
Scope โ€” Clusterequals: aks-runtime-lab
Scope โ€” Namespaceall

Testing Anti-Malware

The EICAR test file is the industry-standard way to test malware detection without using real malware:

# Deploy a test pod
kubectl run malware-test --image=nginx:1.27-alpine --restart=Never
kubectl wait --for=condition=Ready pod/malware-test --timeout=60s

# Write, mark executable, and attempt to execute the EICAR string
kubectl exec malware-test -- /bin/sh -c \
  "echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -d > /tmp/eicar.com && chmod +x /tmp/eicar.com && /tmp/eicar.com"

EICAR is a harmless test string. A nonzero container command does not by itself prove Defender blocked execution: the text file can fail for unrelated runtime reasons. Inspect the configured anti-malware action and Defender security alerts. Microsoft states that policy updates can take up to 30 minutes to reach sensors, and alert delivery has variable latency.

KQL โ€” Anti-Malware Alerts

SecurityAlert
| where AlertType has "MalwareDetected"
    or AlertName has_any ("malware", "Malicious file")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend MalwareName = tostring(ExtProps["Malware Name"])
| extend FilePath = tostring(ExtProps["Suspicious Process"])
| extend ActionTaken = tostring(ExtProps["Action Taken"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, MalwareName,
    FilePath, ActionTaken, ClusterName, Namespace, PodName, ContainerName
| sort by TimeGenerated desc

Sentinel Analytics Rules

Historical Microsoft Defender for Cloud security alerts showing binary drift and container threat detections

Historical earlier-revision evidence from the lab cluster: binary drift, malware execution, reverse shell, network scanning, C2 communication, and Defender agent termination alerts. This is not current-revision deployment evidence.

Three analytics rules provide SOC coverage for documented runtime alerts and Kubernetes audit activity. Each is configured for a five-minute schedule and a one-hour lookback, but all three deploy disabled until -EnableSentinelRules is explicitly supplied.

Note: Rules 1-2 query the SecurityAlert table, which is populated by the Defender for Cloud data connector; Rule 3 queries AzureDiagnostics Kubernetes audit data. Alert and table availability vary by tenant, region, connector state, and ingestion path. Disabled rules do not fire automatically, so validate each query and data path before enabling it explicitly. Gated-deployment decisions remain in Admission Monitoring rather than an invented SecurityAlert type.

Rule 1: Binary Drift in Production Namespace

Fires when a drift alert resolves to a container in a namespace literally named default, production, or kube-system. Those names are a starter scope, not proof that a workload is production; tune the list and approved-workflow exclusions before enabling the rule.

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend DriftedBinary = tostring(ExtProps["Suspicious Process"])
| where Namespace in ("default", "production", "kube-system")
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, ClusterName,
    Namespace, PodName, ContainerName, DriftedBinary
  • Severity: High
  • MITRE: T1105 (Ingress Tool Transfer), T1059 (Command and Scripting Interpreter)

Rule 2: Container Malware Detected

Fires on any anti-malware detection across all clusters. Includes the malware name and action taken (detected vs. blocked).

SecurityAlert
| where AlertType has "MalwareDetected"
    or AlertName has_any ("malware", "Malicious file")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend MalwareName = tostring(ExtProps["Malware Name"])
| extend FilePath = tostring(ExtProps["Suspicious Process"])
| extend ActionTaken = tostring(ExtProps["Action Taken"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, MalwareName,
    FilePath, ActionTaken, ClusterName, Namespace,
    PodName, ContainerName
  • Severity: High
  • MITRE: T1105 (Ingress Tool Transfer), T1204 (User Execution)

Rule 3: Suspicious kubectl exec into Container

Detects Kubernetes audit requests to the pod exec subresource outside kube-system. The request can launch any command, not only an interactive shell, so review the user, source IP, target pod, and command context.

AzureDiagnostics
| where Category == "kube-audit"
| extend RequestObject = parse_json(log_s)
| extend Verb = tostring(RequestObject.verb)
| extend RequestURI = tostring(RequestObject.requestURI)
| extend UserAgent = tostring(RequestObject.userAgent)
| extend SourceIP = tostring(RequestObject.sourceIPs[0])
| extend Username = tostring(RequestObject.user.username)
| where Verb in ("create", "get")
| where RequestURI has "/exec"
| where RequestURI !has "kube-system"
| extend PodName = extract(@"/pods/([^/]+)/exec", 1, RequestURI)
| extend Namespace = extract(@"namespaces/([^/]+)/", 1, RequestURI)
| project TimeGenerated, Username, SourceIP,
    PodName, Namespace, UserAgent, RequestURI
  • Severity: Medium
  • MITRE: T1609 (Container Administration Command)

Hunting Queries

Beyond automated detection, three hunting queries support proactive investigation:

Hunt 1: Container Processes Not in Original Image (Extended)

Broader drift hunt that surfaces all unknown processes, not just those that trigger alerts:

SecurityAlert
| where ProductName == "Microsoft Defender for Cloud"
| where AlertType has_any ("DriftDetection", "BinaryDrift",
    "SuspectProcess", "CryptoMiner", "ToolExecution")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ProcessName = tostring(ExtProps["Suspicious Process"])
| where isnotempty(ContainerName)
| summarize AlertCount = count(),
    AlertTypes = make_set(AlertType),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by ContainerName, ProcessName, Namespace
| sort by AlertCount desc

Hunt 2: Cluster Admin Actions from Unexpected Users

Surfaces cluster-admin level operations from users who aren’t in the expected admin list:

let KnownAdmins = dynamic(["clusterAdmin", "aksService", "masterClient"]);
AzureDiagnostics
| where Category == "kube-audit"
| extend RequestObject = parse_json(log_s)
| extend Username = tostring(RequestObject.user.username)
| extend Groups = tostring(RequestObject.user.groups)
| extend Verb = tostring(RequestObject.verb)
| extend Resource = tostring(RequestObject.objectRef.resource)
| extend Namespace = tostring(RequestObject.objectRef.namespace)
| where Groups has "system:masters" or Username has "admin"
| where not(Username has_any (KnownAdmins))
| where Verb in ("create", "delete", "patch", "update")
| project TimeGenerated, Username, Verb, Resource,
    Namespace, Groups
| summarize ActionCount = count(),
    Actions = make_set(strcat(Verb, ":", Resource)),
    Namespaces = make_set(Namespace)
    by Username
| sort by ActionCount desc

Hunt 3: Network Connections from Containers to External IPs

Detects containers making outbound connections to unexpected destinations โ€” useful for catching C2 callbacks and data exfiltration:

SecurityAlert
| where ProductName == "Microsoft Defender for Cloud"
| where AlertType has_any ("NetworkActivity",
    "SuspiciousConnection", "C2Connection")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "ip"
| extend DestinationIP = tostring(Entity.Address)
| extend ContainerName = tostring(ExtProps["Container Name"])
| where isnotempty(DestinationIP)
| where not(ipv4_is_private(DestinationIP))
| project TimeGenerated, ContainerName,
    DestinationIP, AlertName, AlertSeverity
| sort by TimeGenerated desc

Full KQL for all hunting queries is in the reviewed companion source.

Bonus: Microsoft Attack Simulation Tool

Microsoft provides an official Defender for Cloud Attack Simulation tool that generates realistic container threat scenarios. It covers reconnaissance, lateral movement, secrets gathering, crypto mining, and web shell deployment:

SIMULATION_COMMIT="90f5524e3e6d43aef397de91fb2eff77f4322f0a"
SIMULATION_SHA256="4d6c3e91d2fec7a5186a24cefe3c79348623bc01df3c986ea7873fd29fb19dcc"
curl -sL "https://raw.githubusercontent.com/microsoft/Defender-for-Cloud-Attack-Simulation/${SIMULATION_COMMIT}/simulation.py" \
  -o simulation.py
echo "${SIMULATION_SHA256}  simulation.py" | sha256sum -c -
python3 simulation.py

In a supported, correctly onboarded environment, the simulator is designed to exercise several MITRE ATT&CK techniques. Review its pinned source and safety scope before execution, and verify actual alerts in your tenant; this audit does not guarantee that it will generate a particular count or populate the workbook.


Sentinel Workbook

The lab deploys a workbook with four panels providing a unified view of container runtime security:

  • Runtime Alert Timeline โ€” Timechart of documented binary-drift and anti-malware alerts over time, colored by alert type
  • Binary Drift by Namespace โ€” Bar chart showing which namespaces have the most drift events, highlighting areas that need immutability enforcement
  • Top Drifted Binaries โ€” Table of the most frequently seen unauthorized executables across all clusters
  • kubectl exec Audit Trail โ€” Log of all interactive container sessions with user, source IP, and target pod

The workbook uses the same KQL patterns as the three analytics rules. Gated-deployment decisions are reviewed separately in Defender for Cloud Admission Monitoring.


Key Takeaways

  1. Build-time and runtime security are complementary โ€” Your CI/CD pipeline catches known vulnerabilities before deployment. Runtime defense catches everything that happens after.
  2. Binary drift is a high-value investigation signal โ€” An executable not in the original image warrants review, but legitimate operational tooling can also create drift. Start with alerting; consider blocking only after measuring normal behavior and confirming current support, exclusions, prerequisites, and rollback constraints.
  3. Container anti-malware evaluates executable launch โ€” Use EICAR only as a harmless test input, execute it, and verify the actual policy decision and alert rather than inferring a block from the command exit code.
  4. Gated deployment can be a last line of prevention โ€” A matching, correctly scoped rule in Deny mode can prevent a workload from reaching the cluster. Audit allows the request and records an admission event for review in Admission Monitoring.
  5. Audit interactive access โ€” Collect and query kube-audit telemetry for kubectl exec; whether a Sentinel rule produces an alert depends on connector coverage, query logic, rule state, schedule, and data availability.
  6. Layer your defenses โ€” No single feature catches everything. The combination of gated deployment + binary drift + anti-malware + kube-audit monitoring creates defense in depth.

Resources

Jerrad Dahlager

Jerrad Dahlager, CISSP, CCSP

Cloud Security Architect ยท Adjunct Instructor

Marine Corps veteran and firm believer that the best security survives contact with reality.

Have thoughts on this post? I'd love to hear from you.