Kubernetes Suspicious Daemon Set Or Cron Job Creation
Query
let Lookback = 30d;
let DetectWindow = 1d;
let AllowedAutomation = dynamic([
"system:serviceaccount:flux-system:kustomize-controller",
"system:serviceaccount:argocd:argocd-application-controller"]);
let Persist = CloudAuditEvents
| where Timestamp > ago(Lookback)
| where DataSource =~ "Kubernetes Audit"
| extend Verb = tolower(tostring(RawEventData.verb)),
Resource = tolower(tostring(RawEventData.objectRef.resource)),
SubResource = tolower(tostring(RawEventData.objectRef.subresource)),
Namespace = tostring(RawEventData.objectRef.namespace),
Actor = tostring(RawEventData.user.username),
Code = toint(RawEventData.responseStatus.code),
RequestUri = tostring(RawEventData.requestURI),
Req = RawEventData.requestObject
| where Resource in ("daemonsets", "cronjobs") and Verb in ("create", "update") and isempty(SubResource)
| where Code between (200 .. 299) and not(RequestUri has "dryRun")
| where not(Actor startswith "system:") or Actor startswith "system:serviceaccount:"
| where not(Actor startswith "system:serviceaccount:kube-system:");
let Baseline = Persist
| where Timestamp < ago(DetectWindow)
| distinct Actor, AzureResourceId, Resource;
Persist
| where Timestamp >= ago(DetectWindow)
| where Actor !in (AllowedAutomation)
| join kind=leftanti Baseline on Actor, AzureResourceId, Resource
| extend ObjName = coalesce(tostring(RawEventData.objectRef.name), tostring(Req.metadata.name)),
PodSpec = iff(Resource == "cronjobs", Req.spec.jobTemplate.spec.template.spec, Req.spec.template.spec),
Schedule = tostring(Req.spec.schedule),
SourceIp = tostring(RawEventData.sourceIPs[0])
| extend HostAccess = tobool(PodSpec.hostPID) or tobool(PodSpec.hostNetwork) or tobool(PodSpec.hostIPC)
or tostring(PodSpec.volumes) has "hostPath"
or tostring(PodSpec.containers) has "\"privileged\":true",
HighFrequency = Schedule matches regex @"^(\*|\*/[1-5])\s",
NameMimicry = ObjName matches regex @"^(kube-|azure-|aks-|calico-|coredns|konnectivity|csi-|ama-|omsagent|cloud-node-manager|microsoft-defender)"
| mv-expand C = PodSpec.containers
| extend Image = tostring(C.image), Cmd = tolower(strcat(tostring(C.command), " ", tostring(C.args)))
| extend SuspiciousCmd = Cmd has_any ("curl", "wget", "base64", "/dev/tcp", "socat", "ncat", "nsenter", "xmrig", "chmod +x", "python -c", "perl -e")
or Cmd matches regex @"\|\s*(ba)?sh\b"
| summarize Images = make_set(Image, 10), Commands = make_set(Cmd, 10), SuspiciousCmd = max(toint(SuspiciousCmd))
by Timestamp, Actor, SourceIp, AzureResourceId, Resource, Verb, Namespace, ObjName, Schedule,
HostAccess = toint(HostAccess), HighFrequency = toint(HighFrequency), NameMimicry = toint(NameMimicry)
| extend Score = 1
+ SuspiciousCmd * 3
+ HostAccess * 2
+ iff(Resource == "daemonsets" and HostAccess == 1, 1, 0)
+ HighFrequency
+ NameMimicry * 2
+ iff(Namespace == "kube-system", 2, 0)
| where Score >= 3
| extend Severity = iff(Score >= 5, "High", "Medium")
| order by Score descAbout this query
Kubernetes Suspicious DaemonSet or CronJob Creation*
Query Information
MITRE ATT&CK Technique(s)
| Technique ID | Title | Link |
|---|---|---|
| T1053.007 | Container Orchestration Job | https://attack.mitre.org/techniques/T1053/007 |
| T1609 | Container Administration Command | https://attack.mitre.org/techniques/T1609 |
| T1611 | Escape to Host | https://attack.mitre.org/techniques/T1611 |
Description
Detects the creation or update of Kubernetes DaemonSets or CronJobs that exhibit suspicious characteristics, such as the use of privileged containers, hostPath mounts, host networking, or execution of common adversary-used command-line tools. The rule implements a scoring mechanism based on behavioral indicators including name mimicry, frequent execution schedules, and the namespace context, filtering out known automation service accounts.
Author <Optional>
- Name: Benjamin Zulliger
- Github: https://github.com/benscha/KQLAdvancedHunting
- LinkedIn: https://www.linkedin.com/in/benjamin-zulliger/
Defender XDR
Explanation
This query is designed to detect potentially suspicious activities in a Kubernetes environment, specifically focusing on the creation or update of DaemonSets or CronJobs. Here's a simplified breakdown of what the query does:
-
Time Frame: It looks back over the last 30 days of Kubernetes audit logs but focuses on events from the last day for detection.
-
Exclusions: It excludes known automation service accounts that are allowed to perform these actions, such as those used by Flux and Argo CD.
-
Filtering: The query filters for events where DaemonSets or CronJobs are created or updated, ensuring these actions are not part of a dry run and have a successful response code (200-299). It also excludes actions performed by system accounts, except for certain service accounts.
-
Baseline Comparison: It establishes a baseline of actors (users or service accounts) who have performed similar actions in the past, to identify new or unusual actors.
-
Suspicious Characteristics: The query checks for several suspicious characteristics:
- Host Access: Use of privileged containers, hostPath mounts, or host networking.
- High Frequency: CronJobs scheduled to run very frequently.
- Name Mimicry: Names that mimic common Kubernetes or cloud service names.
- Suspicious Commands: Use of command-line tools often associated with adversarial actions, such as
curl,wget, or shell commands.
-
Scoring System: Each detected event is scored based on the presence of these suspicious characteristics. The score is calculated by assigning different weights to each characteristic.
-
Severity Classification: Events with a score of 3 or higher are flagged, with scores of 5 or more classified as "High" severity and others as "Medium."
-
Output: The results are ordered by score, with the most suspicious activities listed first.
In essence, this query helps identify potentially malicious activities in a Kubernetes environment by looking for unusual or suspicious patterns in the creation or modification of DaemonSets and CronJobs.