Detection rules › Kusto

CTE - Possible Company-Wide C2 Campaign (Multiple Devices, Same Destination)

Severity
high
Time window
1d
Group by
Pattern, TenantId, Url
Source
github.com/Azure/Azure-Sentinel

Multiple distinct devices within the same tenant were blocked attempting to reach the same URL/domain within a 24-hour window. Isolated blocks are usually just one user's browsing; several independent devices converging on the same destination is a strong signal of a coordinated malware/C2 campaign spreading across the company (e.g. a phishing link opened by several employees, or malware propagating internally).

MITRE ATT&CK coverage

TacticTechniques
Lateral Movement
Command & Control

Rule body

id: ec08477d-87a1-498d-8f34-72b8ec8f3758
name: CTE - Possible Company-Wide C2 Campaign (Multiple Devices, Same Destination)
description: |
  Multiple distinct devices within the same tenant were blocked attempting to reach the same URL/domain within a 24-hour window. Isolated blocks are usually just one user's browsing; several independent devices converging on the same destination is a strong signal of a coordinated malware/C2 campaign spreading across the company (e.g. a phishing link opened by several employees, or malware propagating internally).
severity: High
requiredDataConnectors:
  - connectorId: cybereinforce_cte
    dataTypes:
      - CybereinforceCTE_CL
queryFrequency: 30m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - LateralMovement
relevantTechniques:
  - T1071
  - T1570
query: |
  CybereinforceCTE_CL
  | where EventType == "block.url"
  | extend d = parse_json(Details)
  | extend
      DeviceId = tostring(d.deviceId),
      HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
      Pattern = tostring(d.pattern),
      Url = tostring(d.url),
      IsIoc = tobool(d.ioc),
      ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
  | summarize DeviceCount=dcount(DeviceId), AffectedHosts=make_set(HostName, 50), SampleIps=make_set(ClientIp, 20), AnyIoc=max(IsIoc), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Pattern, Url, TenantId
  | where DeviceCount >= 3
  | extend ClientIp = tostring(SampleIps[0])
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CybereinforceCTE_CL

Stage 2: where

| where EventType == "block.url"

Stage 3: extend

| extend d = parse_json(Details)

Stage 4: extend

| extend
    DeviceId = tostring(d.deviceId),
    HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
    Pattern = tostring(d.pattern),
    Url = tostring(d.url),
    IsIoc = tobool(d.ioc),
    ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
HostName =
if/* macro: isnotempty(tostring(d.hostname)) */tostring(d.hostname)
elsestrcat("cte-device-", substring(EntityId, 0, 8))

Stage 5: summarize

| summarize DeviceCount=dcount(DeviceId), AffectedHosts=make_set(HostName, 50), SampleIps=make_set(ClientIp, 20), AnyIoc=max(IsIoc), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Pattern, Url, TenantId

Stage 6: where

| where DeviceCount >= 3

Stage 7: extend

| extend ClientIp = tostring(SampleIps[0])

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AffectedHostssummarize
AnyIocsummarize
DeviceCountsummarize
FirstSeensummarize
LastSeensummarize
Patternsummarize
SampleIpssummarize
TenantIdsummarize
Urlsummarize
ClientIpextend