Detection rules › Kusto

Kusto rules: 1a

ADFS Database Named Pipe Connection

#
Status
available
Severity
medium
Time window
1d
Group by
Computer
Source
github.com/Azure/Azure-Sentinel

This detection uses Sysmon telemetry to detect suspicious local connections via a named pipe to the AD FS configuration database (Windows Internal Database). In order to use this query you need to be collecting Sysmon EventIdD 18 (Pipe Connected). If you do not have Sysmon data in your workspace this query will raise an error stating: Failed to resolve scalar expression named "[@Name]"

MITRE ATT&CK Coverage

TacticTechniques
Collection

Telemetry Coverage

Rule Body

id: dcdf9bfc-c239-4764-a9f9-3612e6dff49c
name: ADFS Database Named Pipe Connection
description: |
  'This detection uses Sysmon telemetry to detect suspicious local connections via a named pipe to the AD FS configuration database (Windows Internal Database).
  In order to use this query you need to be collecting Sysmon EventIdD 18 (Pipe Connected).
  If you do not have Sysmon data in your workspace this query will raise an error stating:
  Failed to resolve scalar expression named "[@Name]"'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Collection
relevantTechniques:
  - T1005
tags:
  - Solorigate
  - NOBELIUM
  - SimuLand
query: |
  // Adjust this to use a longer timeframe to identify ADFS servers
  //let lookback = 6d;
  // Adjust this to adjust the key export detection  timeframe
  //let timeframe = 1d;
  // Start be identifying ADFS servers to reduce FP chance
  let ADFS_Servers = (
  Event
  //| where TimeGenerated > ago(timeframe+lookback)
  | where Source == "Microsoft-Windows-Sysmon"
  | where EventID == 18
  | extend EventData = parse_xml(EventData).DataItem.EventData.Data
  | mv-expand bagexpansion=array EventData
  | evaluate bag_unpack(EventData)
  | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
  | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, MG, ManagementGroupName, _ResourceId)
  | extend Image = column_ifexists("Image", "")
  | extend process = split(Image, '\\', -1)[-1]
  | where process =~ "Microsoft.IdentityServer.ServiceHost.exe"
  | summarize by Computer);
  // Look for ADFS servers where Named Pipes event are present
  Event
  //| where TimeGenerated > ago(timeframe)
  | where Source == "Microsoft-Windows-Sysmon"
  | where EventID == 18
  | where Computer in~ (ADFS_Servers)
  | extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
  | extend EventData = parse_xml(EventData).DataItem.EventData.Data
  | mv-expand bagexpansion=array EventData
  | evaluate bag_unpack(EventData)
  | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
  | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
  | extend RuleName = column_ifexists("RuleName", ""),
      TechniqueId = column_ifexists("TechniqueId", ""),
      TechniqueName = column_ifexists("TechniqueName", ""),
      Image = column_ifexists("Image", ""),
      PipeName = column_ifexists("PipeName", ""),
      EventType = column_ifexists("EventType", "")
  | parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName
  // Look for Pipe related to querying the WID
  | where PipeName == "\\MICROSOFT##WID\\tsql\\query"
  | extend process = split(Image, '\\', -1)[-1]
  // Exclude expected processes
  | where process !in ("Microsoft.IdentityServer.ServiceHost.exe", "Microsoft.Identity.Health.Adfs.PshSurrogate.exe", "AzureADConnect.exe", "Microsoft.Tri.Sensor.exe", "wsmprovhost.exe","mmc.exe", "sqlservr.exe")
  | extend Operation = RenderedDescription
  | project-reorder TimeGenerated, EventType, Operation, process, Image, Computer, UserName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(UserName, @'\')[1]), AccountNTDomain = tostring(split(UserName, @'\')[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: ADFS_Servers used in Stages 1, 5

let ADFS_Servers = (
Event
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 18
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, MG, ManagementGroupName, _ResourceId)
| extend Image = column_ifexists("Image", "")
| extend process = split(Image, '\\', -1)[-1]
| where process =~ "Microsoft.IdentityServer.ServiceHost.exe"
| summarize by Computer);

Stage 1: source

let ADFS_Servers

Stage 2: source

Event

Stage 3: where

| where Source == "Microsoft-Windows-Sysmon"

Stage 4: where

| where EventID == 18

Stage 5: where

| where Computer in~ (ADFS_Servers)

Stage 6: extend

| extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])

Stage 7: extend

| extend EventData = parse_xml(EventData).DataItem.EventData.Data

Stage 8: mv-expand

| mv-expand bagexpansion=array EventData

Stage 9: evaluate

| evaluate bag_unpack(EventData)

Stage 10: extend

| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")

Stage 11: evaluate

| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)

Stage 12: extend

| extend RuleName = column_ifexists("RuleName", ""),
    TechniqueId = column_ifexists("TechniqueId", ""),
    TechniqueName = column_ifexists("TechniqueName", ""),
    Image = column_ifexists("Image", ""),
    PipeName = column_ifexists("PipeName", ""),
    EventType = column_ifexists("EventType", "")

Stage 13: parse

| parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName

Stage 14: where

| where PipeName == "\\MICROSOFT##WID\\tsql\\query"

Stage 15: extend

| extend process = split(Image, '\\', -1)[-1]

Stage 16: where

| where process !in ("Microsoft.IdentityServer.ServiceHost.exe", "Microsoft.Identity.Health.Adfs.PshSurrogate.exe", "AzureADConnect.exe", "Microsoft.Tri.Sensor.exe", "wsmprovhost.exe","mmc.exe", "sqlservr.exe")

Stage 17: extend

| extend Operation = RenderedDescription

Stage 18: project-reorder

| project-reorder TimeGenerated, EventType, Operation, process, Image, Computer, UserName

Stage 19: extend (3 consecutive steps)

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(UserName, @'\')[1]), AccountNTDomain = tostring(split(UserName, @'\')[0])

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
processinAzureADConnect.exe, Microsoft.Identity.Health.Adfs.PshSurrogate.exe, Microsoft.IdentityServer.ServiceHost.exe, Microsoft.Tri.Sensor.exe, mmc.exe, sqlservr.exe, wsmprovhost.exeexcludes:process

Output Fields

These fields are emitted when the rule matches.

FieldSource
RenderedDescriptionextend
EventDataextend
Keyextend
Valueextend
EventTypeextend
Imageextend
PipeNameextend
RuleNameextend
TechniqueIdextend
TechniqueNameextend
processextend
Operationextend
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend

Contrast ADR - Exploited Attack in Production

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'Detects successful exploitation of security vulnerabilities in production environments as identified by Contrast ADR. This rule triggers on confirmed exploited attacks in production systems, requiring immediate incident response and remediation.'

MITRE ATT&CK Coverage

Rule Body

id: 31417149-f3a2-4db4-9e5f-85e0a464f6a1
name: Contrast ADR - Exploited Attack in Production
description: |
  'Detects successful exploitation of security vulnerabilities in production environments as identified by Contrast ADR. This rule triggers on confirmed exploited attacks in production systems, requiring immediate incident response and remediation.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: ContrastADRCCF
    dataTypes:
      - ContrastADRAttackEvents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - DefenseEvasion
  - LateralMovement
  - CommandAndControl
relevantTechniques:
  - T1190
  - T1059
  - T1055
  - T1210
  - T1008
query: |
  ContrastADRAttackEvents_CL
  | where result =~ "exploited" and environment =~ "production"
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: sourceIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: host_hostname
alertDetailsOverride:
  alertDisplayNameFormat: '{{result}} {{rule}} from {{sourceIp}} in Production'
  alertDescriptionFormat: '{{result}}  on {{request_headers_referer}}  endpoint of {{application_name}} '
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT30M
    matchingMethod: Selected
    groupByEntities:
      - IP
      - Host
eventGroupingSettings:
  aggregationKind: AlertPerResult
kind: Scheduled
version: 1.0.1

Stages and Predicates

Stage 1: source

ContrastADRAttackEvents_CL

Stage 2: where

| where result =~ "exploited" and environment =~ "production"

Indicators

These rows show field, operator, and value matches.

CTE - Compromised Device Suspected (High-Volume IOC Blocks)

#
Severity
high
Time window
1h
Group by
DeviceId, DeviceOs, DeviceUser, DeviceVersion, HostName, TenantId
Source
github.com/Azure/Azure-Sentinel

A single device is generating an unusually high volume of threat-intel-matched (IOC) blocks in a short window. This pattern is more consistent with malware/beaconing behavior than manual browsing and should be investigated for device compromise. Note: the 5-per-hour threshold below is a starting point based on limited sample data - tune it once a real baseline is established.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control
Exfiltration

Rule Body

id: 59246163-4cc0-4e96-a0f0-f098a83cf425
name: CTE - Compromised Device Suspected (High-Volume IOC Blocks)
description: |
  A single device is generating an unusually high volume of threat-intel-matched (IOC) blocks in a short window. This pattern is more consistent with malware/beaconing behavior than manual browsing and should be investigated for device compromise. Note: the 5-per-hour threshold below is a starting point based on limited sample data - tune it once a real baseline is established.
severity: High
requiredDataConnectors:
  - connectorId: cybereinforce_cte
    dataTypes:
      - CybereinforceCTE_CL
queryFrequency: 15m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1071
  - T1041
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))),
      DeviceUser = tostring(d.deviceUser),
      DeviceOs = tostring(d.deviceOs),
      DeviceVersion = tostring(d.deviceVersion),
      Url = tostring(d.url),
      IsIoc = tobool(d.ioc),
      ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
  | where IsIoc == true
  | summarize IOC_BlockCount=count(), SampleUrls=make_set(Url, 20), SampleIps=make_set(ClientIp, 20), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by DeviceId, HostName, DeviceUser, DeviceOs, DeviceVersion, TenantId
  | where IOC_BlockCount >= 5
  | extend Url = tostring(SampleUrls[0]), ClientIp = tostring(SampleIps[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
  - 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))),
    DeviceUser = tostring(d.deviceUser),
    DeviceOs = tostring(d.deviceOs),
    DeviceVersion = tostring(d.deviceVersion),
    Url = tostring(d.url),
    IsIoc = tobool(d.ioc),
    ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
HostName =
ifisnotempty(tostring(d.hostname))tostring(d.hostname)
elsestrcat("cte-device-", substring(EntityId, 0, 8))

Stage 5: where

| where IsIoc == true

Stage 6: summarize

| summarize IOC_BlockCount=count(), SampleUrls=make_set(Url, 20), SampleIps=make_set(ClientIp, 20), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by DeviceId, HostName, DeviceUser, DeviceOs, DeviceVersion, TenantId

Stage 7: where

| where IOC_BlockCount >= 5

Stage 8: extend

| extend Url = tostring(SampleUrls[0]), ClientIp = tostring(SampleIps[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdsummarize
DeviceOssummarize
DeviceUsersummarize
DeviceVersionsummarize
FirstSeensummarize
HostNamesummarize
IOC_BlockCountsummarize
LastSeensummarize
SampleIpssummarize
SampleUrlssummarize
TenantIdsummarize
ClientIpextend
Urlextend

Cyble Vision Alerts Malicious Ads Detected

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Generates an incident when Cyble Intelligence detects a malicious advertisement, malvertising activity, or redirect attempting to impersonate a legitimate brand.'

MITRE ATT&CK Coverage

Rule Body

id: cf0a9691-5716-42e0-bfa1-49b35d3a7892
name: Cyble Vision Alerts Malicious Ads Detected
description: |
  'Generates an incident when Cyble Intelligence detects a malicious advertisement, malvertising activity, or redirect attempting to impersonate a legitimate brand.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5M
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- InitialAccess
- Execution
relevantTechniques:
- T1189
- T1566
- T1059
query: |
  Alerts_malicious_ads
  | where Service == "malicious_ads"
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: Malicious Ad Detected  {{MA_DomainName_Nested}}
  alertDescriptionFormat: |
    Cyble Intelligence detected a malicious advertisement or suspicious redirect.
    Domain {{MA_Domain_URL}}
    Brand/Keyword {{MA_Keyword_Nested}}
    Status Code {{MA_StatusCode}}
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  Domain: MA_Domain_URL
  Keyword: MA_Keyword_Nested
  IsLive: MA_IsLive
entityMappings:
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: MA_Domain_URL
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: MA_DomainName_Nested
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_malicious_ads

Stage 2: where

| where Service == "malicious_ads"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Serviceeq
  • malicious_ads
field:"Service" kind:eq value:"malicious_ads"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Dataverse - Login from IP in the block list

#
Status
available
Severity
high
Time window
1d
Group by
ClientIp, InstanceUrl, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies Dataverse sign-in activity from IPv4 addresses which are on a predefined block list. Blocked network ranges are maintained in the NetworkAddresses watchlist template.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 666fef96-1bb8-4abf-ad72-e5cb49561381
kind: Scheduled
name: Dataverse - Login from IP in the block list
description: Identifies Dataverse sign-in activity from IPv4 addresses which are on
  a predefined block list. Blocked network ranges are maintained in the NetworkAddresses
  watchlist template.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
  - T1078
query: |
  // Use static IP address or CIDR list specified in the
  // NetworkAddresses watchlist (from watchlist template)
  // with tag "BlockDataverse"
  let query_frequency = 1h;
  let blocked_networks = MSBizAppsNetworkAddresses()
      | where Tags has "BlockDataverse"
      | summarize by IPSubnet;
  let watchlist_entries_count = toscalar (blocked_networks
      | summarize count());
  DataverseActivity
  | where watchlist_entries_count > 0
  | where TimeGenerated >= ago(query_frequency)
  | where Message == "UserSignIn" and isnotempty(ClientIp)
  | summarize FirstEvent = arg_min(TimeGenerated, *) by UserId, ClientIp, InstanceUrl
  | evaluate ipv4_lookup(blocked_networks, ClientIp, IPSubnet)
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      FirstEvent,
      UserId,
      ClientIp,
      Message,
      InstanceUrl,
      CloudAppId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - Login from IP in the block list at {{InstanceUrl}} '
  alertDescriptionFormat: Sign-in activity by {{UserId}} in {{InstanceUrl}} was detected
    from an IP {{ClientIp}} on the block list.
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;

Let Binding: blocked_networks used in Stage 6

let blocked_networks = MSBizAppsNetworkAddresses()
    | where Tags has "BlockDataverse"
    | summarize by IPSubnet;

Let Binding: watchlist_entries_count used in Stage 2

let watchlist_entries_count = toscalar (blocked_networks
    | summarize count());

Stage 1: source

DataverseActivity

Stage 2: where

| where watchlist_entries_count > 0

Stage 3: where

| where TimeGenerated >= ago(query_frequency)

Stage 4: where

| where Message == "UserSignIn" and isnotempty(ClientIp)

Stage 5: summarize

| summarize FirstEvent = arg_min(TimeGenerated, *) by UserId, ClientIp, InstanceUrl

Stage 6: evaluate

| evaluate ipv4_lookup(blocked_networks, ClientIp, IPSubnet)

Stage 7: extend

| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])

Stage 8: project

| project
    FirstEvent,
    UserId,
    ClientIp,
    Message,
    InstanceUrl,
    CloudAppId,
    AccountName,
    UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ClientIpproject
CloudAppIdproject
FirstEventproject
InstanceUrlproject
Messageproject
UPNSuffixproject
UserIdproject

Detect Registry Run Key Creation/Modification

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This analytic rule detects any registry value or key creation in the registry run keys. This could be an indication of a persistence attempt by an adversary.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: dd041e4e-1ee2-41ec-ba4e-82a71d628260
name: Detect Registry Run Key Creation/Modification
description: |
  This analytic rule detects any registry value or key creation in the registry run keys. This could be an indication of a persistence attempt by an adversary.
severity: Medium
status: Available 
tags:
  - Schema: _ASim_RegistryEvent
    SchemaVersion: 0.1.2
requiredDataConnectors:
  - connectorId: CrowdStrikeFalconEndpointProtection
    dataTypes: 
      - CommonSecurityLog
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
  - connectorId: SentinelOne
    dataTypes:
      - SentinelOne_CL
  - connectorId: VMwareCarbonBlack
    dataTypes:
      - CarbonBlackEvents_CL
  - connectorId: CiscoSecureEndpoint
    dataTypes:
      - CiscoSecureEndpoint_CL
  - connectorId: TrendMicroApexOne
    dataTypes:
      - TMApexOneEvent
  - connectorId: TrendMicroApexOneAma
    dataTypes:
      - TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1547
  - T1112
query: |
  // List of startup registry keys to monitor
  let startupRegistryList = dynamic([
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce',
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServices',
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServices',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run',
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Userinit',
      'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Shell',
      'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows'
    ]);
  _ASim_RegistryEvent
  | where EventType in ('RegistryValueSet', 'RegistryKeyCreated') and RegistryKey has_any (startupRegistryList)
  | project
      TimeGenerated,
      DvcHostname,
      ActorUsername,
      ActorUsernameType,
      ActingProcessId,
      ActingProcessName,
      ActingProcessCommandLine,
      RegistryKey,
      RegistryValue,
      RegistryValueType,
      RegistryValueData
  | extend HostName = tostring(split(DvcHostname, '.')[0])
  | extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
  | extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
  | extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
  | extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
  | extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
  | extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\')) 
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Username
      - identifier: UPNSuffix
        columnName: UPNSuffix
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ActingProcessId
      - identifier: CommandLine
        columnName: ActingProcessCommandLine
  - entityType: RegistryKey
    fieldMappings:
      - identifier: Hive
        columnName: RegHive
      - identifier: Key
        columnName: RegKey
  - entityType: RegistryValue
    fieldMappings:
      - identifier: Name
        columnName: RegistryValue
      - identifier: Value
        columnName: RegistryValueData
      - identifier: ValueType
        columnName: RegistryValueType
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: "Registry Run Keys Modified on {{DvcHostname}} ({{DvcIpAddr}}) by ({{ActorUsername}})"
  alertDescriptionFormat: "Process {{ActingProcessName}} ProcessId: ({{ActingProcessId}}) modified registry run key {{RegistryKey}}."
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: startupRegistryList used in Stage 2

let startupRegistryList = dynamic([
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce',
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServices',
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServices',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run',
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Userinit',
    'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Shell',
    'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows'
  ]);

Stage 1: source

_ASim_RegistryEvent

Stage 2: where

| where EventType in ('RegistryValueSet', 'RegistryKeyCreated') and RegistryKey has_any (startupRegistryList)

Stage 3: project

| project
    TimeGenerated,
    DvcHostname,
    ActorUsername,
    ActorUsernameType,
    ActingProcessId,
    ActingProcessName,
    ActingProcessCommandLine,
    RegistryKey,
    RegistryValue,
    RegistryValueType,
    RegistryValueData

Stage 4: extend (7 consecutive steps)

| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypein
  • RegistryKeyCreated corpus 5 (kusto 5)
  • RegistryValueSet corpus 5 (kusto 5)
field:"EventType" kind:in
RegistryKeymatch
  • HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows transforms: term
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run transforms: term
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run transforms: term
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce transforms: term
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices transforms: term
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices transforms: term
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce transforms: term
field:"TargetObject" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessCommandLineproject
ActingProcessIdproject
ActingProcessNameproject
ActorUsernameproject
ActorUsernameTypeproject
DvcHostnameproject
RegistryKeyproject
RegistryValueproject
RegistryValueDataproject
RegistryValueTypeproject
TimeGeneratedproject
HostNameextend
DnsDomainextend
Usernameextend
NTDomainextend
UPNSuffixextend
RegHiveextend
RegKeyextend

Detecting UAC bypass - ChangePK and SLUI registry tampering

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This query identifies setting a registry key under HKCU, launching slui.exe and then ChangePK.exe.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 829a69ba-93e1-491f-8a1f-b19506e9d88a
name: Detecting UAC bypass - ChangePK and SLUI registry tampering
description: |
  This query identifies setting a registry key under HKCU, launching slui.exe and then ChangePK.exe.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1490
query: |
  DeviceProcessEvents
  | where InitiatingProcessParentFileName =~ "slui.exe"
  | where InitiatingProcessFileName =~ "changepk.exe"
  | where ProcessIntegrityLevel == "High"

entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: AccountSid
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountDomain
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where InitiatingProcessParentFileName =~ "slui.exe"

Stage 3: where

| where InitiatingProcessFileName =~ "changepk.exe"

Stage 4: where

| where ProcessIntegrityLevel == "High"

Indicators

These rows show field, operator, and value matches.

Europium - Hash and IP IOCs - September 2022

#
Severity
high
Time window
12h
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies a match across various data feeds for hashes and IP IOC related to Europium Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government

MITRE ATT&CK Coverage

TacticTechniques
Credential Access
Command & Control

Telemetry Coverage

Rule Body

id: 9d8b5a18-b7db-4c23-84a6-95febaf7e1e4
name: Europium - Hash and IP IOCs - September 2022
description: | 
  'Identifies a match across various data feeds for  hashes and IP IOC related to Europium
   Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government'
severity: High 
requiredDataConnectors: 
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: AzureMonitor(VMInsights)
    dataTypes:
      - VMConnection
  - connectorId: F5
    dataTypes:
      - CommonSecurityLog
  - connectorId: CiscoASA
    dataTypes: 
      - CommonSecurityLog
  - connectorId: PaloAltoNetworks
    dataTypes: 
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes: 
      - CommonSecurityLog
  - connectorId: CheckPoint
    dataTypes: 
      - CommonSecurityLog
  - connectorId: CEF
    dataTypes: 
      - CommonSecurityLog               
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
      - DeviceFileEvents
      - DeviceImageLoadEvents
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
  - connectorId: WindowsFirewall
    dataTypes:
      - WindowsFirewall      
queryFrequency: 12h 
queryPeriod: 12h 
triggerOperator: gt 
triggerThreshold: 0 
tactics: 
  - CommandAndControl
  - CredentialAccess
relevantTechniques:
  - T1071
  - T1003
tags:
  - Europium
  - Schema: ASIMFileEvent
    SchemaVersion: 0.1.0
query:  |  
  let iocs = externaldata(DateAdded:string,IoC:string,Type:string,TLP:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Europium_September2022.csv"] with (format="csv", ignoreFirstRecord=True);
  let sha256Hashes = (iocs | where Type =~ "sha256" | project IoC);
  let IPList = (iocs | where Type =~ "ip"| project IoC);
  let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
  (union isfuzzy=true
  (CommonSecurityLog
  | where SourceIP in (IPList) or DestinationIP in (IPList) or Message has_any (IPList)
  | parse Message with * '(' DNSName ')' * 
  | project TimeGenerated, SourceIP, DestinationIP, Message, SourceUserID, RequestURL, DNSName, Type
  | extend MessageIP = extract(IPRegex, 0, Message), RequestIP = extract(IPRegex, 0, RequestURL)
  | extend IPMatch = case(SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", MessageIP in (IPList), "Message",  "NoMatch")
  | extend timestamp = TimeGenerated, IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, IPMatch == "Message", MessageIP, "NoMatch")
  ),
  (DnsEvents
  | where IPAddresses in (IPList)  
  | project TimeGenerated, Computer, IPAddresses, Name, ClientIP, Type
  | extend DestinationIPAddress = IPAddresses, DNSName = Name, Computer 
  | extend timestamp = TimeGenerated, IPEntity = DestinationIPAddress, HostEntity = Computer
  ),
  (VMConnection
  | where SourceIp in (IPList) or DestinationIp in (IPList)
  | parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
  | project TimeGenerated, Computer, Direction, ProcessName, SourceIp, DestinationIp, DestinationPort, RemoteDnsQuestions, DNSName,BytesSent, BytesReceived, RemoteCountry, Type
  | extend IPMatch = case( SourceIp in (IPList), "SourceIP", DestinationIp in (IPList), "DestinationIP", "None") 
  | extend timestamp = TimeGenerated, IPEntity = case(IPMatch == "SourceIP", SourceIp, IPMatch == "DestinationIP", DestinationIp, "NoMatch"), File = ProcessName, HostEntity = Computer
  ),
  (Event
  | where Source == "Microsoft-Windows-Sysmon"
  | where EventID == 3
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend SourceIP = tostring(EventDetail.[9].["#text"]), DestinationIP = tostring(EventDetail.[14].["#text"]), Image = tostring(EventDetail.[4].["#text"])
  | where SourceIP in (IPList) or DestinationIP in (IPList)
  | project TimeGenerated, SourceIP, DestinationIP, Image, UserName, Computer, Type
  | extend IPMatch = case( SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", "None")
  | extend timestamp = TimeGenerated, File = tostring(split(Image, '\\', -1)[-1]), IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "None"), 
  HostEntity = Computer, AccountName = tostring(split(UserName, @'\')[1]), AccountDomain = tostring(split(UserName, @'\')[0])
  | extend InitiatingProcessAccount = UserName
  ), 
  (OfficeActivity
  | where ClientIP in (IPList) 
  | project TimeGenerated, UserAgent, Operation, RecordType, UserId, ClientIP, Type
  | extend timestamp = TimeGenerated, IPEntity = ClientIP, AccountName = tostring(split(UserId, "@")[0]), AccountDomain = tostring(split(UserId, "@")[1])
  | extend InitiatingProcessAccount = UserId
  ),
  (DeviceNetworkEvents
  | where RemoteIP in (IPList) or InitiatingProcessSHA256 in (sha256Hashes)
  | project TimeGenerated, ActionType, DeviceId, Computer = DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, 
  InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, RemotePort, LocalIP, Type
  | extend timestamp = TimeGenerated, IPEntity = RemoteIP, HostEntity = Computer, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain
  | extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
  ),
  (WindowsFirewall
  | where SourceIP in (IPList) or DestinationIP in (IPList) 
  | project TimeGenerated, Computer, CommunicationDirection, SourceIP, DestinationIP, SourcePort, DestinationPort, Type
  | extend IPMatch = case( SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", "None")
  | extend timestamp = TimeGenerated, HostEntity = Computer, IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "None")
  ), 
  (imFileEvent
  | where TargetFileSHA256 has_any (sha256Hashes)
  | extend Account = ActorUsername, Computer = DvcHostname, IPAddress = SrcIpAddr, CommandLine = ActingProcessCommandLine, FileHash = TargetFileSHA256
  | project Type, TimeGenerated, Computer, Account, IPAddress, CommandLine, FileHash
  | extend timestamp = TimeGenerated, IPEntity = IPAddress,  HostEntity = Computer, Algorithm = "SHA256", FileHash = tostring(FileHash)
  | extend AccountName = tostring(split(Account, @'\')[1]), AccountDomain = tostring(split(Account, @'\')[0])
  | extend InitiatingProcessAccount = Account
  ),
  (DeviceFileEvents
  | where SHA256 has_any (sha256Hashes)
  | project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, 
  InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, Type
  | extend timestamp = TimeGenerated, HostEntity = DeviceName, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain, 
  Algorithm = "SHA256", FileHash = tostring(InitiatingProcessSHA256), CommandLine = InitiatingProcessCommandLine,Image = InitiatingProcessFolderPath
  | extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
  ),
  (DeviceImageLoadEvents
  | where SHA256 has_any (sha256Hashes)
  | project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, 
  InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, Type
  | extend timestamp = TimeGenerated, HostEntity = DeviceName, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain, 
  Algorithm = "SHA256", FileHash = tostring(InitiatingProcessSHA256),  CommandLine = InitiatingProcessCommandLine,Image = InitiatingProcessFolderPath
  | extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
  ),
  (Event
  | where Source =~ "Microsoft-Windows-Sysmon"
  | where EventID == 1
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend Image = EventDetail.[4].["#text"],  CommandLine = EventDetail.[10].["#text"], Hashes = tostring(EventDetail.[17].["#text"])
  | extend Hashes = extract_all(@"(?P<key>\w+)=(?P<value>[a-zA-Z0-9]+)", dynamic(["key","value"]), Hashes)
  | extend Hashes = column_ifexists("Hashes", dynamic(["", ""])), CommandLine = column_ifexists("CommandLine", "")
  | mv-expand Hashes
  | where Hashes[0] =~ "SHA256" and Hashes[1] has_any (sha256Hashes)  
  | project TimeGenerated, EventDetail, UserName, Computer, Type, Source, Hashes, CommandLine, Image
  | extend Type = strcat(Type, ": ", Source)
  | extend timestamp = TimeGenerated, HostEntity = Computer, AccountName = tostring(split(UserName, @'\')[1]), AccountUPNSuffix = tostring(split(UserName, @'\')[0]), FileHash = tostring(Hashes[1])
  | extend InitiatingProcessAccount = UserName
  )
  )
  | extend HostName = tostring(split(HostEntity, ".")[0]), DomainIndex = toint(indexof(HostEntity, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(HostEntity, DomainIndex + 1), HostEntity)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingProcessAccount
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostEntity
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPEntity
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algorithm
      - identifier: Value
        columnName: FileHash
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Parameters

let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';

Let Binding: iocs

let iocs = externaldata(DateAdded:string,IoC:string,Type:string,TLP:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Europium_September2022.csv"] with (format="csv", ignoreFirstRecord=True);

Let Binding: sha256Hashes

let sha256Hashes = (iocs | where Type =~ "sha256" | project IoC);

Let Binding: IPList

let IPList = (iocs | where Type =~ "ip"| project IoC);

Union isfuzzy=true (11 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, DnsEvents, VMConnection, Event, OfficeActivity, DeviceNetworkEvents, WindowsFirewall, imFileEvent, DeviceFileEvents, DeviceImageLoadEvents, Event

Leg 1: CommonSecurityLog

CommonSecurityLog
| where SourceIP in (IPList) or DestinationIP in (IPList) or Message has_any (IPList)
| parse Message with * '(' DNSName ')' * 
| project TimeGenerated, SourceIP, DestinationIP, Message, SourceUserID, RequestURL, DNSName, Type
| extend MessageIP = extract(IPRegex, 0, Message), RequestIP = extract(IPRegex, 0, RequestURL)
| extend IPMatch = case(SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", MessageIP in (IPList), "Message",  "NoMatch")
| extend timestamp = TimeGenerated, IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, IPMatch == "Message", MessageIP, "NoMatch")

Leg 2: DnsEvents

DnsEvents
| where IPAddresses in (IPList)  
| project TimeGenerated, Computer, IPAddresses, Name, ClientIP, Type
| extend DestinationIPAddress = IPAddresses, DNSName = Name, Computer 
| extend timestamp = TimeGenerated, IPEntity = DestinationIPAddress, HostEntity = Computer

Leg 3: VMConnection

VMConnection
| where SourceIp in (IPList) or DestinationIp in (IPList)
| parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
| project TimeGenerated, Computer, Direction, ProcessName, SourceIp, DestinationIp, DestinationPort, RemoteDnsQuestions, DNSName,BytesSent, BytesReceived, RemoteCountry, Type
| extend IPMatch = case( SourceIp in (IPList), "SourceIP", DestinationIp in (IPList), "DestinationIP", "None") 
| extend timestamp = TimeGenerated, IPEntity = case(IPMatch == "SourceIP", SourceIp, IPMatch == "DestinationIP", DestinationIp, "NoMatch"), File = ProcessName, HostEntity = Computer

Leg 4: Event

Event
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 3
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend SourceIP = tostring(EventDetail.[9].["#text"]), DestinationIP = tostring(EventDetail.[14].["#text"]), Image = tostring(EventDetail.[4].["#text"])
| where SourceIP in (IPList) or DestinationIP in (IPList)
| project TimeGenerated, SourceIP, DestinationIP, Image, UserName, Computer, Type
| extend IPMatch = case( SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", "None")
| extend timestamp = TimeGenerated, File = tostring(split(Image, '\\', -1)[-1]), IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "None"), 
HostEntity = Computer, AccountName = tostring(split(UserName, @'\')[1]), AccountDomain = tostring(split(UserName, @'\')[0])
| extend InitiatingProcessAccount = UserName

Leg 5: OfficeActivity

OfficeActivity
| where ClientIP in (IPList) 
| project TimeGenerated, UserAgent, Operation, RecordType, UserId, ClientIP, Type
| extend timestamp = TimeGenerated, IPEntity = ClientIP, AccountName = tostring(split(UserId, "@")[0]), AccountDomain = tostring(split(UserId, "@")[1])
| extend InitiatingProcessAccount = UserId

Leg 6: DeviceNetworkEvents

DeviceNetworkEvents
| where RemoteIP in (IPList) or InitiatingProcessSHA256 in (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, Computer = DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, 
InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, RemotePort, LocalIP, Type
| extend timestamp = TimeGenerated, IPEntity = RemoteIP, HostEntity = Computer, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)

Leg 7: WindowsFirewall

WindowsFirewall
| where SourceIP in (IPList) or DestinationIP in (IPList) 
| project TimeGenerated, Computer, CommunicationDirection, SourceIP, DestinationIP, SourcePort, DestinationPort, Type
| extend IPMatch = case( SourceIP in (IPList), "SourceIP", DestinationIP in (IPList), "DestinationIP", "None")
| extend timestamp = TimeGenerated, HostEntity = Computer, IPEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "None")

Leg 8: imFileEvent

imFileEvent
| where TargetFileSHA256 has_any (sha256Hashes)
| extend Account = ActorUsername, Computer = DvcHostname, IPAddress = SrcIpAddr, CommandLine = ActingProcessCommandLine, FileHash = TargetFileSHA256
| project Type, TimeGenerated, Computer, Account, IPAddress, CommandLine, FileHash
| extend timestamp = TimeGenerated, IPEntity = IPAddress,  HostEntity = Computer, Algorithm = "SHA256", FileHash = tostring(FileHash)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountDomain = tostring(split(Account, @'\')[0])
| extend InitiatingProcessAccount = Account

Leg 9: DeviceFileEvents

DeviceFileEvents
| where SHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, 
InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain, 
Algorithm = "SHA256", FileHash = tostring(InitiatingProcessSHA256), CommandLine = InitiatingProcessCommandLine,Image = InitiatingProcessFolderPath
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)

Leg 10: DeviceImageLoadEvents

DeviceImageLoadEvents
| where SHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, 
InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain, 
Algorithm = "SHA256", FileHash = tostring(InitiatingProcessSHA256),  CommandLine = InitiatingProcessCommandLine,Image = InitiatingProcessFolderPath
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)

Leg 11: Event

Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend Image = EventDetail.[4].["#text"],  CommandLine = EventDetail.[10].["#text"], Hashes = tostring(EventDetail.[17].["#text"])
| extend Hashes = extract_all(@"(?P<key>\w+)=(?P<value>[a-zA-Z0-9]+)", dynamic(["key","value"]), Hashes)
| extend Hashes = column_ifexists("Hashes", dynamic(["", ""])), CommandLine = column_ifexists("CommandLine", "")
| mv-expand Hashes
| where Hashes[0] =~ "SHA256" and Hashes[1] has_any (sha256Hashes)  
| project TimeGenerated, EventDetail, UserName, Computer, Type, Source, Hashes, CommandLine, Image
| extend Type = strcat(Type, ": ", Source)
| extend timestamp = TimeGenerated, HostEntity = Computer, AccountName = tostring(split(UserName, @'\')[1]), AccountUPNSuffix = tostring(split(UserName, @'\')[0]), FileHash = tostring(Hashes[1])
| extend InitiatingProcessAccount = UserName

Applied to the Combined Result

| extend HostName = tostring(split(HostEntity, ".")[0]), DomainIndex = toint(indexof(HostEntity, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(HostEntity, DomainIndex + 1), HostEntity)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CommandLineproject
Computerproject
EventDetailproject
Hashesproject
Imageproject
Sourceproject
TimeGeneratedproject
Typeextend
UserNameproject
AccountNameextend
AccountUPNSuffixextend
FileHashextend
HostEntityextend
timestampextend
InitiatingProcessAccountextend
DomainIndexextend
HostNameextend
HostNameDomainextend

External user added and removed in short timeframe

#
Status
available
Severity
low
Time window
1h
Group by
$left.MemberAdded, $right.MemberRemoved
Source
github.com/Azure/Azure-Sentinel

This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: bff093b2-500e-4ae5-bb49-a5b1423cbd5b
name: External user added and removed in short timeframe
description: |
  'This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  let TeamsAddDel = (Op:string){
  OfficeActivity
  | where OfficeWorkload =~ "MicrosoftTeams"
  | where Operation == Op
  | where Members has ("#EXT#")
  | mv-expand Members
  | extend UPN = tostring(Members.UPN)
  | where UPN has ("#EXT#")
  | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP
  };
  let TeamsAdd = TeamsAddDel("MemberAdded")
  | project TimeAdded=TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;
  let TeamsDel = TeamsAddDel("MemberRemoved")
  | project TimeDeleted=TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;
  TeamsAdd
  | join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved
  | where TimeDeleted > TimeAdded
  | project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP
  | extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1])
  | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1])
  | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: MemberAdded_Removed
      - identifier: Name
        columnName: MemberAdded_RemovedAccountName
      - identifier: UPNSuffix
        columnName: MemberAdded_RemovedAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserWhoAdded
      - identifier: Name
        columnName: UserWhoAddedAccountName
      - identifier: UPNSuffix
        columnName: UserWhoAddedAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserWhoDeleted
      - identifier: Name
        columnName: UserWhoDeletedAccountName
      - identifier: UPNSuffix
        columnName: UserWhoDeletedAccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 2.1.3
kind: Scheduled

Stages and Predicates

let TeamsAddDel and let TeamsAdd are inlined into the numbered stages below.

Let Binding: TeamsDel used in Stage 10

let TeamsDel = TeamsAddDel("MemberRemoved")
| project TimeDeleted=TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;

Stages 1 to 9 define let TeamsAdd as the rule's main pipeline source. Stages 10 to 13 use it.

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload =~ "MicrosoftTeams"

Stage 3: where

| where Operation == Op

Stage 4: where

| where Members has ("#EXT#")

Stage 5: mv-expand

| mv-expand Members

Stage 6: extend

| extend UPN = tostring(Members.UPN)

Stage 7: where

| where UPN has ("#EXT#")

Stage 8: project

| project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP

Stage 9: project

| project TimeAdded=TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP

Stage 10: join

TeamsAdd
| join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved

Stage 11: where

where TimeDeleted > TimeAdded

Stage 12: project

project ClientIP, MemberAdded_Removed, TeamName, TimeAdded, TimeDeleted, UserWhoAdded, UserWhoDeleted

Stage 13: extend (3 consecutive steps)

extend MemberAdded_RemovedAccountName, MemberAdded_RemovedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Membersmatch
  • #EXT# transforms: term
field:"Members" kind:match value:"#EXT#"
OfficeWorkloadeq
  • MicrosoftTeams corpus 3 (kusto 3)
  • MicrosoftTeams
field:"m365::Workload" kind:eq
Operationeq
  • MemberAdded
  • MemberRemoved
field:"Operation" kind:eq
UPNmatch
  • #EXT# transforms: term
field:"UPN" kind:match value:"#EXT#"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPproject
MemberAdded_Removedproject
TeamNameproject
TimeAddedproject
TimeDeletedproject
UserWhoAddedproject
UserWhoDeletedproject
MemberAdded_RemovedAccountNameextend
MemberAdded_RemovedAccountUPNSuffixextend
UserWhoAddedAccountNameextend
UserWhoAddedAccountUPNSuffixextend
UserWhoDeletedAccountNameextend
UserWhoDeletedAccountUPNSuffixextend

GCP IAM - New Service Account

#
Status
available
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects new service account creation.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: a768aa52-453e-4e3e-80c2-62928d2e2f56
name: GCP IAM - New Service Account
description: |
  'Detects new service account creation.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 24h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  GCP_IAM
  | where PayloadMethodname =~ 'google.iam.admin.v1.CreateServiceAccount'
  | extend result = parse_json(todynamic(PayloadAuthorizationinfo))[0]['granted']
  | where result =~ 'true'
  | project-away result
  | extend AccountName = tostring(split(PayloadRequestAccountId, "@")[0]), AccountUPNSuffix = tostring(split(PayloadRequestAccountId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: PayloadRequestAccountId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GCP_IAM

Stage 2: where

| where PayloadMethodname =~ 'google.iam.admin.v1.CreateServiceAccount'

Stage 3: extend

| extend result = parse_json(todynamic(PayloadAuthorizationinfo))[0]['granted']

Stage 4: where

| where result =~ 'true'

Stage 5: project-away

| project-away result

Stage 6: extend

| extend AccountName = tostring(split(PayloadRequestAccountId, "@")[0]), AccountUPNSuffix = tostring(split(PayloadRequestAccountId, "@")[1])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AccountUPNSuffixextend

Hunt for public remotly exploitable devices (with high EPSS)

#
Group by
CveId, DeviceId, DeviceName
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This query searches for devices that comply with the following criteria: - Incomming connections from public IP addresses in last 7 days (internet exposed) - High or Critical severity CVE's - CVE's must have known exploits - CVE's are remotely exploitable over the network - No user interaction is required to exploit the CVE's - EPSS score of CVE must by above 10% (likelihood of exploitation) > If devices are placed behind a proxy, they will not be returned in this query by default

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

// Flag remotly exploitable, no user interaction, CVE's with a EPSS score above a certain threshold (likelyhood of exploitation)
// For devices with incomming public connections
// See efficiency research on https://www.first.org/epss/model
let epss_threshold = 0.1;
let exploit_statusses = dynamic(["ExploitIsPublic","ExploitIsInKit","ExploitIsVerified"]);
// Xspm base query we materialize since we need these results multiple times
let xspm_base = materialize (
    ExposureGraphNodes
    // Get device nodes with their inventory ID
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    // Get first important properties
    | extend DeviceId = tostring(parse_json(EntityIds)["id"]),
        ExposureScore = tostring(parse_json(NodeProperties)["rawData"]["exposureScore"]),
        HasHighOrCriticalCve = tostring(parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["hasHighOrCritical"])
    // Focus on devices with high exposure
    | where ExposureScore == "High"
    // Get vulnerability exploit information
    | extend RceExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToRemoteCodeExecution"]["explotabilityLevels"]
    | extend PrivEscExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToPrivilegeEscalation"]["explotabilityLevels"]
    // Focus on devices where cve has known epxloits
    | where RceExploitLevels has_any (exploit_statusses) or PrivEscExploitLevels has_any (exploit_statusses)
    // Focus on devices that are public exposed
    | join kind=inner (
        DeviceNetworkEvents
        | where TimeGenerated > ago(7d)
        | where ActionType contains "InboundConnection"
        | where RemoteIPType == "Public"
        // Exclude MacOS Rapportd and ControlCenter
        | where InitiatingProcessFileName != "rapportd" and InitiatingProcessFileName != "controlcenter"
        | distinct DeviceName, DeviceId, LocalPort, InitiatingProcessFolderPath, InitiatingProcessVersionInfoProductName, InitiatingProcessFileName
    ) on $left.DeviceId == $right.DeviceId
    // Save all the open ports and their process in a JSON
    | extend OpenPortJson = bag_pack_columns(LocalPort, InitiatingProcessFolderPath, InitiatingProcessFileName)
    // Save open ports by Device ID
    | summarize PublicOpenPortList = make_set(OpenPortJson) by DeviceId
);
// Save flagged device IDs in list to limit results of CVE's we need to search later
let flagged_devices = toscalar(
    xspm_base
    | summarize make_set(DeviceId)
);
// CVE base query we materialize since we need these results multiple times
let cve_base = materialize (
    DeviceTvmSoftwareVulnerabilities
    | where VulnerabilitySeverityLevel in ("High", "Critical")
    | where DeviceId in ( flagged_devices )
);
// Save flagged CVE IDs in list to limit results of CVE database we need to search later
let flagged_cves = toscalar(
    cve_base
    | summarize make_set(CveId)
);
// Query the CVE's of the flagged devices
cve_base
// Enrich the CVE data with their EPSS and CVSS Score
| join kind=inner (
    DeviceTvmSoftwareVulnerabilitiesKB
    // Focus on flagged CVE's
    | where CveId in ( flagged_cves )
    // Focus on CVE's tagged with Attack Vector being over the Network
    // 'Vulnerabilities with this rating are remotely exploitable, from one or more hops away, up to and including remote exploitation over the Internet.'
    // 'Does not require user interaction'
    | where CvssVector contains "/AV:N" and CvssVector contains "/UI:N"
    // Focus on CVE's where an exploit is available
    | where IsExploitAvailable != 0
    | distinct CveId, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList=tostring(AffectedSoftware)
) on CveId
// Continue with only relevant data
| project DeviceId, DeviceName, OSPlatform, OSVersion, OSArchitecture, SoftwareName, SoftwareVendor, SoftwareVersion, CveId, VulnerabilitySeverityLevel, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList
// Now flag CVE's with a EPSS score above a certain threshold
// See efficiency research on https://www.first.org/epss/model
| where EpssScore >= epss_threshold
// Save all the CVE data in a JSON column
| extend CveJson = bag_pack_columns(SoftwareName, SoftwareVendor, SoftwareVersion, CveId, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList)
// Group the CVE data for each device per device
| summarize CveList = make_list(CveJson) by DeviceId, DeviceName
// Add xspm data again
| join kind=inner xspm_base on DeviceId
| project-away DeviceId1
// Sort by CVE amount
| extend CveCount = array_length(CveList)
| sort by CveCount desc

Stages and Predicates

Parameters

let epss_threshold = 0.1;
let exploit_statusses = dynamic(["ExploitIsPublic","ExploitIsInKit","ExploitIsVerified"]);

let cve_base is inlined into the numbered stages below.

Let Binding: xspm_base used in Stages 1, 11

let xspm_base = materialize (
    ExposureGraphNodes
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    | extend DeviceId = tostring(parse_json(EntityIds)["id"]),
        ExposureScore = tostring(parse_json(NodeProperties)["rawData"]["exposureScore"]),
        HasHighOrCriticalCve = tostring(parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["hasHighOrCritical"])
    | where ExposureScore == "High"
    | extend RceExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToRemoteCodeExecution"]["explotabilityLevels"]
    | extend PrivEscExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToPrivilegeEscalation"]["explotabilityLevels"]
    | where RceExploitLevels has_any (exploit_statusses) or PrivEscExploitLevels has_any (exploit_statusses)
    | join kind=inner (
        DeviceNetworkEvents
        | where TimeGenerated > ago(7d)
        | where ActionType contains "InboundConnection"
        | where RemoteIPType == "Public"
        | where InitiatingProcessFileName != "rapportd" and InitiatingProcessFileName != "controlcenter"
        | distinct DeviceName, DeviceId, LocalPort, InitiatingProcessFolderPath, InitiatingProcessVersionInfoProductName, InitiatingProcessFileName
    ) on $left.DeviceId == $right.DeviceId
    | extend OpenPortJson = bag_pack_columns(LocalPort, InitiatingProcessFolderPath, InitiatingProcessFileName)
    | summarize PublicOpenPortList = make_set(OpenPortJson) by DeviceId
);

Let Binding: flagged_devices used in Stage 5

let flagged_devices = toscalar(
    xspm_base
    | summarize make_set(DeviceId)
);

Let Binding: flagged_cves

let flagged_cves = toscalar(
    cve_base
    | summarize make_set(CveId)
);

Stage 1: source

let xspm_base

Stage 2: source

let cve_base

Stage 3: source

DeviceTvmSoftwareVulnerabilities

Stage 4: where

where VulnerabilitySeverityLevel in ("Critical", "High")

Stage 5: where

where DeviceId == "flagged_devices"

Stage 6: join

join kind=inner (DeviceTvmSoftwareVulnerabilitiesKB) on CveId

Stage 7: project

project AffectedSoftwareList, CveId, CvssScore, CvssVector, DeviceId, DeviceName, EpssScore, IsExploitAvailable, OSArchitecture, OSPlatform, OSVersion, SoftwareName, SoftwareVendor, SoftwareVersion, VulnerabilitySeverityLevel

Stage 8: where

where EpssScore >= 0.1

Stage 9: extend

extend CveJson

Stage 10: summarize

summarize CveList by DeviceId, DeviceName

Stage 11: join

join kind=inner (xspm_base) on DeviceId

Stage 12: project-away

project-away DeviceId1

Stage 13: extend

extend CveCount

Stage 14: sort

sort by CveCount

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CveListsummarize
DeviceIdsummarize
DeviceNamesummarize
CveCountextend

Infoblox - TI - CommonSecurityLog Match Found - MalwareC2

#
Status
available
Severity
medium
Time window
14d
Group by
$left.DomainName, $right.DestinationDnsDomain, IndicatorId
Source
github.com/Azure/Azure-Sentinel

'CommonSecurityLog (CEF) MalwareC2/MalwareC2DGA match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired.'

MITRE ATT&CK Coverage

Rule Body

id: 5b0864a9-4577-4087-b9fa-de3e14a8a999
name: Infoblox - TI - CommonSecurityLog Match Found - MalwareC2
description: |
  'CommonSecurityLog (CEF) MalwareC2/MalwareC2DGA match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes: 
      - ThreatIntelligenceIndicator
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1498
  - T1565
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let TI = ThreatIntelligenceIndicator
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()  
  | where Description has_cs "Infoblox"
  | where Description has_cs "MalwareC2"
  | where isnotempty(DomainName)
  ;
  let Data = CommonSecurityLog
  | extend HitTime = TimeGenerated
  | where TimeGenerated >= ago(dt_lookBack)
  | where isnotempty(DestinationDnsDomain)
  //Remove trailing period at end of domain
  | extend DestinationDnsDomain = trim_end(@"\.$", DestinationDnsDomain)
  ;
  TI | join kind=innerunique Data on $left.DomainName == $right.DestinationDnsDomain
  | where HitTime >= TimeGenerated and HitTime < ExpirationDateTime
  | project LatestIndicatorTime, HitTime, DeviceEventClassID, DestinationDnsDomain, DeviceAction, SourceIP, DeviceName, SourceMACAddress, SourceUserName, AdditionalExtensions, 
  AdditionalInformation, Description, ThreatType, TrafficLightProtocolLevel, Type, ConfidenceScore, ExpirationDateTime, SourceSystem, Action, IndicatorId, ExternalIndicatorId, Tags
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceName
      - identifier: FullName
        columnName: SourceUserName
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DestinationDnsDomain
customDetails:
  SourceMACAddress: SourceMACAddress
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let TI is inlined into the numbered stages below.

Let Binding: Data used in Stage 8

let Data = CommonSecurityLog
| extend HitTime = TimeGenerated
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(DestinationDnsDomain)
| extend DestinationDnsDomain = trim_end(@"\.$", DestinationDnsDomain);

Stages 1 to 7 define let TI as the rule's main pipeline source. Stages 8 to 10 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: where

| where Description has_cs "Infoblox"

Stage 6: where

| where Description has_cs "MalwareC2"

Stage 7: where

| where isnotempty(DomainName)

Stage 8: join

TI
| join kind=innerunique Data on $left.DomainName == $right.DestinationDnsDomain

Stage 9: where

where HitTime >= TimeGenerated and HitTime < ExpirationDateTime

Stage 10: project

project Action, AdditionalExtensions, AdditionalInformation, ConfidenceScore, Description, DestinationDnsDomain, DeviceAction, DeviceEventClassID, DeviceName, ExpirationDateTime, ExternalIndicatorId, HitTime, IndicatorId, LatestIndicatorTime, SourceIP, SourceMACAddress, SourceSystem, SourceUserName, Tags, ThreatType, TrafficLightProtocolLevel, Type

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true
field:"Active" kind:eq value:"true"
Descriptionmatch
  • Infoblox transforms: term
  • MalwareC2 transforms: term
field:"Description" kind:match
DestinationDnsDomainis_not_null
  • (no value, null check)
field:"DestinationDnsDomain" kind:is_not_null
DomainNameis_not_null
  • (no value, null check)
field:"DomainName" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
AdditionalExtensionsproject
AdditionalInformationproject
ConfidenceScoreproject
Descriptionproject
DestinationDnsDomainproject
DeviceActionproject
DeviceEventClassIDproject
DeviceNameproject
ExpirationDateTimeproject
ExternalIndicatorIdproject
HitTimeproject
IndicatorIdproject
LatestIndicatorTimeproject
SourceIPproject
SourceMACAddressproject
SourceSystemproject
SourceUserNameproject
Tagsproject
ThreatTypeproject
TrafficLightProtocolLevelproject
Typeproject

Insider Risk_High User Security Alert Correlations

#
Severity
medium
Time window
7d
Group by
$left.AlertId, $right.SystemAlertId, IncidentNumber, SystemAlertId, UPN
Source
github.com/Azure/Azure-Sentinel

'This alert joins SecurityAlerts from Microsoft Products with SecurityIncidents from Microsoft Sentinel and Microsoft Defender XDR. This join allows for identifying patterns in user principal names associated with respective security alerts. A machine learning function (Basket) is leveraged with a .001 threshold. Baset finds all frequent patterns of discrete attributes (dimensions) in the data. It returns the frequent patterns passed the frequency threshold. This query evaluates UserPrincipalName for patterns in SecurityAlerts and Reporting Security Tools. This query can be further tuned/configured for higher confidence percentages, security products, or alert severities pending the needs of the organization. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information on the basket plugin, see basket plugin'

MITRE ATT&CK Coverage

TacticTechniques
Execution

Rule Body

id: a4fb4255-f55b-4c24-b396-976ee075d406
name: Insider Risk_High User Security Alert Correlations
description: |
  'This alert joins SecurityAlerts from Microsoft Products with SecurityIncidents from Microsoft Sentinel and Microsoft Defender XDR. This join allows for identifying patterns in user principal names associated with respective security alerts. A machine learning function (Basket) is leveraged with a .001 threshold. Baset finds all frequent patterns of discrete attributes (dimensions) in the data. It returns the frequent patterns passed the frequency threshold. This query evaluates UserPrincipalName for patterns in SecurityAlerts and Reporting Security Tools. This query can be further tuned/configured for higher confidence percentages, security products, or alert severities pending the needs of the organization. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information on the basket plugin, see [basket plugin](https://docs.microsoft.com/azure/data-explorer/kusto/query/basketplugin)'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftDefenderAdvancedThreatProtection
    dataTypes:
      - SecurityAlert (MDATP)
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert (IPC)
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert (ASC)
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
  - connectorId: MicrosoftCloudAppSecurity
    dataTypes:
      - SecurityAlert (ASC for IoT)
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (MCAS)
  - connectorId: OfficeATP
    dataTypes:
      - SecurityAlert (Office 365)
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
query: |
  let AlertLinks = SecurityAlert
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | summarize AlertLinks=make_set(AlertLink) by UPN;
  let LastTimeObserved = SecurityIncident
  | summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
  | mv-expand AlertIds
  | extend AlertId = tostring(AlertIds)
  | join kind= innerunique ( 
            SecurityAlert 
            )
            on $left.AlertId == $right.SystemAlertId
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | project UPN, AlertName, Severity, ProductName, TimeGenerated | summarize arg_max(AlertName, Severity, ProductName, TimeGenerated) by UPN;
  SecurityIncident
  | summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
  | mv-expand AlertIds
  | extend AlertId = tostring(AlertIds)
  | join kind= innerunique ( 
            SecurityAlert 
            )
            on $left.AlertId == $right.SystemAlertId
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | project UPN, AlertName, Severity, ProductName
  | evaluate basket(0.001)
  | where UPN <> ""
  // | lookup kind=inner _GetWatchlist('<Your Watchlist Name>') on $left.UPN == $right.SearchKey
  | project UPN, Percent, AlertName, Severity, ProductName
  | join (LastTimeObserved) on UPN
  | sort by Percent desc
  | extend LastObserved = TimeGenerated
  | join kind=inner (AlertLinks) on UPN
  | project UPN, AlertName, Severity, ProductName, Percent, LastObserved, AlertLinks
  | extend AccountName = tostring(split(UPN, "@")[0]), AccountUPNSuffix = tostring(split(UPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UPN
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.1.3
kind: Scheduled

Stages and Predicates

Let Binding: AlertLinks used in Stages 18, 19

let AlertLinks = SecurityAlert
| summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
| mv-expand todynamic(Entities)
| where Entities["Type"] =~ "account"
| extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
          Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
| extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
| extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
| where UPN <> ""
| summarize AlertLinks=make_set(AlertLink) by UPN;

Let Binding: LastTimeObserved used in Stage 15

let LastTimeObserved = SecurityIncident
| summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
| mv-expand AlertIds
| extend AlertId = tostring(AlertIds)
| join kind= innerunique ( 
          SecurityAlert 
          )
          on $left.AlertId == $right.SystemAlertId
| summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
| mv-expand todynamic(Entities)
| where Entities["Type"] =~ "account"
| extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
          Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
| extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
| extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
| where UPN <> ""
| project UPN, AlertName, Severity, ProductName, TimeGenerated | summarize arg_max(AlertName, Severity, ProductName, TimeGenerated) by UPN;

Stage 1: source

SecurityIncident

Stage 2: summarize

| summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber

Stage 3: mv-expand

| mv-expand AlertIds

Stage 4: extend

| extend AlertId = tostring(AlertIds)

Stage 5: join

| join kind= innerunique ( 
          SecurityAlert 
          )
          on $left.AlertId == $right.SystemAlertId

Stage 6: summarize

summarize NumberOfUpdates by SystemAlertId

Stage 7: mv-expand

mv-expand

Stage 8: where

where Entities["Type"] =~ "account"

Stage 9: extend (3 consecutive steps)

extend AadTenantId, AadUserId, Host, Href_, IsDomainJoined, NTDomain, Name, Sid, UPN, UPNSuffix

Stage 10: where

where UPN != ""

Stage 11: project

project AlertName, ProductName, Severity, UPN

Stage 12: evaluate

evaluate

Stage 13: where

where UPN != ""

Stage 14: project

project AlertName, Percent, ProductName, Severity, UPN

Stage 15: join

join (LastTimeObserved) on UPN

Stage 16: sort

sort by Percent

Stage 17: extend

extend LastObserved

Stage 18: join

| join kind=inner (AlertLinks) on UPN

Stage 19: project

project AlertLinks, AlertName, LastObserved, Percent, ProductName, Severity, UPN

Stage 20: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Entities["Type"]eq
  • account transforms: kusto_dynamic_path

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinksproject
AlertNameproject
LastObservedproject
Percentproject
ProductNameproject
Severityproject
UPNproject
AccountNameextend
AccountUPNSuffixextend

License Support Expired

#
Status
available
Severity
high
Time window
5m
Group by
$left.Edition, $left.Type, $right.Edition, $right.Type
Source
github.com/Azure/Azure-Sentinel

Detects when the Veeam support contract is expired. This might impact backup operations and data protection.

Rule Body

id: 7a6f7e95-b574-44ab-b215-f5c32a2378d1
name: License Support Expired
description: Detects when the Veeam support contract is expired. This might impact
  backup operations and data protection.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "let license_editions_lookup = union isfuzzy=true (datatable(Edition:string,\
  \ EditionDescription:string)[]), (_GetWatchlist(\"license_editions_lookup\")); \n\
  let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]),\
  \ (_GetWatchlist(\"license_types_lookup\")); \nVeeam_GetSecurityEvents\n| where\
  \ instanceId == 24050\n| extend Edition = extract(\"Edition=\\\"([^\\\"]*)\\\"\"\
  , 1, SyslogMessage)\n| lookup kind=leftouter (license_editions_lookup) \n    on\
  \ $left.Edition == $right.Edition\n| extend Type = extract(\"Type=\\\"([^\\\"]*)\\\
  \"\", 1, SyslogMessage)\n| lookup kind=leftouter (license_types_lookup) \n    on\
  \ $left.Type == $right.Type\n| extend TenantID = extract(\"TenantID=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend DaysLeft = extract(\"DaysLeft=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend SupportLeft = extract(\"SupportLeft=\\\"\
  ([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    [\"License Edition\"] = EditionDescription,\n    [\"License Type\"] = TypeDescription,\n\
  \    [\"Days Left\"] = DaysLeft,\n    [\"Days of Support Left\"] = SupportLeft,\n\
  \    MessageDetails = Description,\n    Severity = SeverityDescription\n"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Parameters

let license_editions_lookup = union isfuzzy=true (datatable(Edition:string, EditionDescription:string)[]), (_GetWatchlist("license_editions_lookup"));
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]), (_GetWatchlist("license_types_lookup"));

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 24050

Stage 3: extend

| extend Edition = extract("Edition=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: kusto:lookup

lookup kind=leftouter (license_editions_lookup) on $left.Edition, $right.Edition

Stage 5: extend

extend Type

Stage 6: kusto:lookup

lookup kind=leftouter (license_types_lookup) on $left.Type, $right.Type

Stage 7: extend (3 consecutive steps)

extend DaysLeft, SupportLeft, TenantID

Stage 8: project

project DataSource, Date, Days Left, Days of Support Left, EventId, License Edition, License Type, MessageDetails, Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
Days Leftproject
Days of Support Leftproject
EventIdproject
License Editionproject
License Typeproject
MessageDetailsproject
Severityproject

Mimecast Secure Email Gateway - Virus

#
Status
available
Severity
informational
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'Detect threat for virus from mail receipt virus event.'

MITRE ATT&CK Coverage

TacticTechniques
Execution

Rule Body

id: d78d7352-fa5a-47d4-b48f-cb2c3252c0eb
name: Mimecast Secure Email Gateway - Virus
description: |
  'Detect threat for virus from mail receipt virus event.'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: MimecastSEGAPI
    dataTypes:
      - MimecastCG
enabled: true
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1053
query: |
  MimecastCG
  | where Type == "email_receipt" and isnotempty(['Virus Found'])
  | extend  SenderEnvelope = ['Sender Envelope']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: SenderEnvelope
  - identifier: Recipient
    columnName: Recipients
  - identifier: Subject
    columnName: Subject
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastCG

Stage 2: where

| where Type == "email_receipt" and isnotempty(['Virus Found'])

Stage 3: extend

| extend  SenderEnvelope = ['Sender Envelope']

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Typeeq
  • email_receipt
field:"Type" kind:eq value:"email_receipt"
Virus Foundis_not_null
  • (no value, null check)
field:"Virus Found" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
SenderEnvelopeextend

Mimecast Secure Email Gateway - Virus

#
Severity
informational
Time window
15m
Source
github.com/Azure/Azure-Sentinel

Detect threat for virus from mail receipt virus event

MITRE ATT&CK Coverage

TacticTechniques
Execution

Rule Body

id: 30f73baa-602c-4373-8f02-04ff5e51fc7f
name: Mimecast Secure Email Gateway - Virus
description: Detect threat for virus from mail receipt virus event
severity: Informational
requiredDataConnectors:
  - connectorId: MimecastSIEMAPI
    dataTypes:
      - MimecastSIEM_CL
enabled: true
query: MimecastSIEM_CL| where mimecastEventId_s=="mail_receipt_virus"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Execution
relevantTechniques:
- T1053
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
customDetails:
  IP: IP_s
  MsgId_s: MsgId_s
  Virus: Virus_s
  RejType: RejType_s
  Error: Error_s
  RejCode: RejCode_s
  Dir: Dir_s
  headerFrom: headerFrom_s
  Act: Act_s
  RejInfo: RejInfo_s
  TlsVer: TlsVer_s
  Cphr: Cphr_s
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: Sender_s
  - identifier: Recipient
    columnName: Rcpt_s
  - identifier: Subject
    columnName: Subject_s
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastSIEM_CL

Stage 2: where

| where mimecastEventId_s=="mail_receipt_virus"

Indicators

These rows show field, operator, and value matches.

Pathlock TDnR - Bank Master Data Changes

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Detects changes to bank master data in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized modifications to bank data may indicate financial fraud, payment redirection, or business email compromise attacks targeting payment processes.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c05
name: Pathlock TDnR - Bank Master Data Changes
kind: Scheduled
description: >-
  Detects changes to bank master data in SAP, forwarded by Pathlock Threat Detection and Response.
  Unauthorized modifications to bank data may indicate financial fraud, payment redirection,
  or business email compromise attacks targeting payment processes.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1565
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_BANK"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "CHANGEDOC_BANK"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Possible Resource-Based Constrained Delegation Abuse

#
Severity
medium
Time window
1h
Group by
AttributeLDAPDisplayName, Computer, ObjectDN, SubjectAccount, SubjectDomainName, SubjectLogonId, SubjectUserName, SubjectUserSid
Author
Vasileios Paschalidis
Source
github.com/Azure/Azure-Sentinel

This query identifies Active Directory computer objects modifications that allow an adversary to abuse the Resource-based constrained delegation. This query checks for event id 5136 that the Object Class field is "computer" and the LDAP Display Name is "msDS-AllowedToActOnBehalfOfOtherIdentity" which is an indicator of Resource-based constrained delegation. Ref: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rule Body

id: 2937bc6b-7cda-4fba-b452-ea43ba8e835f
name:  Possible Resource-Based Constrained Delegation Abuse
description: |
  'This query identifies Active Directory computer objects modifications that allow an adversary to abuse the Resource-based constrained delegation. 
  This query checks for event id 5136 that the Object Class field is "computer" and the LDAP Display Name is "msDS-AllowedToActOnBehalfOfOtherIdentity" which is an indicator of Resource-based constrained delegation.
  Ref: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1134
query: |
  SecurityEvent
  | where EventID == 5136 
  | parse EventData with * 'ObjectClass">' ObjectClass "<" *
  | parse EventData with * 'AttributeLDAPDisplayName">' AttributeLDAPDisplayName "<" *
  | where ObjectClass == "computer" and AttributeLDAPDisplayName == "msDS-AllowedToActOnBehalfOfOtherIdentity"
  | parse EventData with * 'ObjectDN">' ObjectDN "<" *
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid, SubjectLogonId, ObjectDN, AttributeLDAPDisplayName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SubjectAccount
      - identifier: Name
        columnName: SubjectUserName
      - identifier: NTDomain
        columnName: SubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: SubjectUserSid
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Vasileios Paschalidis
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 5136

Stage 3: parse

| parse EventData with * 'ObjectClass">' ObjectClass "<" *

Stage 4: parse

| parse EventData with * 'AttributeLDAPDisplayName">' AttributeLDAPDisplayName "<" *

Stage 5: where

| where ObjectClass == "computer" and AttributeLDAPDisplayName == "msDS-AllowedToActOnBehalfOfOtherIdentity"

Stage 6: parse

| parse EventData with * 'ObjectDN">' ObjectDN "<" *

Stage 7: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid, SubjectLogonId, ObjectDN, AttributeLDAPDisplayName

Stage 8: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 9: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Stage 10: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AttributeLDAPDisplayNamesummarize
Computersummarize
EndTimesummarize
ObjectDNsummarize
StartTimesummarize
SubjectAccountsummarize
SubjectDomainNamesummarize
SubjectLogonIdsummarize
SubjectUserNamesummarize
SubjectUserSidsummarize
HostNameextend
HostNameDomainextend

Power Automate - Departing employee flow activity

#
Status
available
Severity
high
Time window
7d
Group by
$left.ActorName, $right.UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

Identifies instances where an employee who has been notified or is already terminated, on the TerminatedEmployees watchlist, creates or modifies a Power Automate flow.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: b1e11b8c-545a-4dea-a912-0008e160d183
kind: Scheduled
name: Power Automate - Departing employee flow activity
description: Identifies instances where an employee who has been notified or is already
  terminated, on the TerminatedEmployees watchlist, creates or modifies a Power Automate
  flow.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: PowerAutomate
    dataTypes:
      - PowerAutomateActivity
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - Impact
relevantTechniques:
  - T1567
  - T1485
  - T1491
  - T0813
  - T0879
  - T0826
query: |
  let query_frequency = 1h;
  PowerAutomateActivity
  | where TimeGenerated >= ago(query_frequency)
  | where EventOriginalType in ("CreateFlow", "EditFlow")
  | join kind=inner (MSBizAppsTerminatedEmployees()) on $left.ActorName == $right.UserPrincipalName
  | extend path = parse_url(FlowDetailsUrl).Path
  | extend EnvironmentId = tostring(split(path, "/")[2])
  | extend FlowId = tostring(split(path, "/")[-2])
  | extend
      AccountName = tostring(split(ActorName, "@")[0]),
      UPNSuffix = tostring(split(ActorName, "@")[1]),
      PowerAutomateAppId = 27592,
      CloudAppId = 32780
  | project
      TimeGenerated,
      EventOriginalType,
      ActorName,
      EnvironmentId,
      AccountName,
      UPNSuffix,
      PowerAutomateAppId,
      CloudAppId,
      FlowId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: PowerAutomateAppId
alertDetailsOverride:
  alertDisplayNameFormat: PowerAutomate - Terminated user {{EventOriginalType}} detected
  alertDescriptionFormat: '{{ActorName}} is on the terminated employees watchlist
    and carried out {{EventOriginalType}} in environment id {{EnvironmentId}}.'
customDetails:
  Environment: EnvironmentId
  FlowDetails: FlowId
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;

Stage 1: source

PowerAutomateActivity

Stage 2: where

| where TimeGenerated >= ago(query_frequency)

Stage 3: where

| where EventOriginalType in ("CreateFlow", "EditFlow")

Stage 4: join

| join kind=inner (MSBizAppsTerminatedEmployees()) on $left.ActorName == $right.UserPrincipalName

Stage 5: extend (4 consecutive steps)

extend AccountName, CloudAppId, EnvironmentId, FlowId, PowerAutomateAppId, UPNSuffix, path

Stage 6: project

project AccountName, ActorName, CloudAppId, EnvironmentId, EventOriginalType, FlowId, PowerAutomateAppId, TimeGenerated, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventOriginalTypein
  • CreateFlow
  • EditFlow
field:"EventOriginalType" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ActorNameproject
CloudAppIdproject
EnvironmentIdproject
EventOriginalTypeproject
FlowIdproject
PowerAutomateAppIdproject
TimeGeneratedproject
UPNSuffixproject

Preview - TI map Domain entity to Cloud App Events

#
Severity
medium
Time window
14d
Group by
$left.TI_DomainEntity, $right.Domain, IndicatorId
Source
github.com/Azure/Azure-Sentinel

Identifies compromises and attacks and detect malicious activities in one's domain entity from TI.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: b97e118c-b7fa-42a6-84de-2e13443fbb8f
name: Preview - TI map Domain entity to Cloud App Events
description: |
    'Identifies compromises and attacks and detect malicious activities in one's domain entity from TI.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - CloudAppEvents
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d; 
  let list_tlds =
    ThreatIntelligenceIndicator
      | where TimeGenerated >= ago(ioc_lookBack)
      | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
      | where Active == true and ExpirationDateTime > now()
      | where isnotempty(DomainName)
      | extend DomainName = tolower(DomainName)
      | extend parts = split(DomainName, '.')
      | extend tld = parts[(array_length(parts) - 1)]
      | summarize count() by tostring(tld)
      | summarize make_set(tld);
  let Domain_Indicators =
    ThreatIntelligenceIndicator
      | where TimeGenerated >= ago(ioc_lookBack)
      | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
      | where Active == true and ExpirationDateTime > now()
      | extend TI_DomainEntity = DomainName
      | where isnotempty(TI_DomainEntity);
    Domain_Indicators
      | join kind=innerunique (
    CloudAppEvents
      | extend IngestionTime = ingestion_time()
      | where IngestionTime > ago(dt_lookBack)
      | extend PAUrl = columnifexists("RequestURL", "None")
      | extend Domain = trim('"', tostring(parseurl(PAUrl).Host))
      | extend Domain = tolower(Domain)
      | extend parts = split(Domain, '.')
      | extend tld = parts[(array_length(parts) - 1)]
      | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_DomainEntity == $right.Domain
      | where CloudAppEvents_TimeGenerated < ExpirationDateTime
      | summarize CloudAppEvents_TimeGenerated = argmax(CloudAppEvents_TimeGenerated, *) by IndicatorId
      | extend Description = max_CloudAppEvents_TimeGenerated_Description, ActivityGroupNames = max_CloudAppEvents_TimeGenerated_ActivityGroupNames, ThreatType = max_CloudAppEvents_TimeGenerated_ThreatType, ExpirationDateTime = max_CloudAppEvents_TimeGenerated_ExpirationDateTime, ConfidenceScore = max_CloudAppEvents_TimeGenerated_ConfidenceScore, DomainName = max_CloudAppEvents_TimeGenerated_DomainName, ProviderName = max_CloudAppEvents_TimeGenerated_IndicatorProvider, AlertSeverity = max_CloudAppEvents_TimeGenerated_ThreatSeverity, IPAddress = max_CloudAppEvents_TimeGenerated_IPAddress
      | project CloudAppEvents_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, DomainName, ProviderName, AlertSeverity, IPAddress
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let Domain_Indicators is inlined into the numbered stages below.

Let Binding: list_tlds

let list_tlds = ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | where isnotempty(DomainName)
    | extend DomainName = tolower(DomainName)
    | extend parts = split(DomainName, '.')
    | extend tld = parts[(array_length(parts) - 1)]
    | summarize count() by tostring(tld)
    | summarize make_set(tld);

Stages 1 to 6 define let Domain_Indicators as the rule's main pipeline source. Stages 7 to 11 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: extend

| extend TI_DomainEntity = DomainName

Stage 6: where

| where isnotempty(TI_DomainEntity)

Stage 7: join

Domain_Indicators
| join kind=innerunique (
  CloudAppEvents
    | extend IngestionTime = ingestion_time()
    | where IngestionTime > ago(dt_lookBack)
    | extend PAUrl = columnifexists("RequestURL", "None")
    | extend Domain = trim('"', tostring(parseurl(PAUrl).Host))
    | extend Domain = tolower(Domain)
    | extend parts = split(Domain, '.')
    | extend tld = parts[(array_length(parts) - 1)]
    | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_DomainEntity == $right.Domain

Stage 8: where

where CloudAppEvents_TimeGenerated < ExpirationDateTime

Stage 9: summarize

summarize CloudAppEvents_TimeGenerated by IndicatorId

Stage 10: extend

extend ActivityGroupNames, AlertSeverity, ConfidenceScore, Description, DomainName, ExpirationDateTime, IPAddress, ProviderName, ThreatType

Stage 11: project

project ActivityGroupNames, AlertSeverity, CloudAppEvents_TimeGenerated, ConfidenceScore, Description, DomainName, ExpirationDateTime, IPAddress, IndicatorId, ProviderName, ThreatType

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true corpus 60 (kusto 60)
field:"Active" kind:eq value:"true"
TI_DomainEntityis_not_null
  • (no value, null check)
field:"TI_DomainEntity" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AlertSeverityproject
CloudAppEvents_TimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
DomainNameproject
ExpirationDateTimeproject
IPAddressproject
IndicatorIdproject
ProviderNameproject
ThreatTypeproject

Radiflow - Unauthorized Internet Access

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Generates an incident when an unauthorized link between the network and the Internet is detected by Radiflow's iSID.

MITRE ATT&CK Coverage

Rule Body

id: cc33e1a9-e167-460b-93e6-f14af652dbd3
name: Radiflow - Unauthorized Internet Access
description: Generates an incident when an unauthorized link between the network and the Internet is detected by Radiflow's iSID.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: RadiflowIsid
    dataTypes:
      - RadiflowEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Impact
relevantTechniques:
- T0822
- T0883
- T0882
query: |
  RadiflowEvent
  | where DeviceProduct =~ 'iSID'
  | where EventClassID in (55, 147, 193, 194)
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: Unauthorized Internet Access
  alertDescriptionFormat: "An external connection to the network has been detected. Please check whether this activity is legitimate and the external device is authorized to connect.\n\nMessage: {{EventMessage}}\nSource device: {{SourceIP}} \nDestination device (if any): {{DestinationIP}} "
  alertSeverityColumnName: EventSeverity
  alertDynamicProperties: []
suppressionEnabled: false
suppressionDuration: 5h
customDetails:
  SourceMAC: SourceMACAddress
  SourceType: SourceType
  SourceVendor: SourceVendor
  SourceVLAN: SourceVLAN
  DestinationMAC: DestinationMACAddress
  DestinationType: DestinationType
  DestinationVendor: DestinationVendor
  Port: Port
  Protocol: Protocol
  SourceHostName: SourceHostName
  SourceIP: SourceIP
  DestinationHostName: DestinationHostName
  DestinationIP: DestinationIP
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: SourceHostName
  - identifier: NetBiosName
    columnName: SourceHostName
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: DestinationHostName
  - identifier: NetBiosName
    columnName: DestinationHostName
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SourceIP
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: DestinationIP
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

RadiflowEvent

Stage 2: where

| where DeviceProduct =~ 'iSID'

Stage 3: where

| where EventClassID in (55, 147, 193, 194)

Indicators

These rows show field, operator, and value matches.

Sentinel One - Rule deleted

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects when a rule was deleted.'

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Rule Body

id: e171b587-22bd-46ec-b96c-7c99024847a7
name: Sentinel One - Rule deleted
description: |
  'Detects when a rule was deleted.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SentinelOne
    dataTypes:
      - SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: | 
  SentinelOne
  | where ActivityType == 3602
  | project EventCreationTime, DataRuleName, DataRuleQueryDetails, DataUserName
  | extend AccountCustomEntity = DataUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SentinelOne

Stage 2: where

| where ActivityType == 3602

Stage 3: project

| project EventCreationTime, DataRuleName, DataRuleQueryDetails, DataUserName

Stage 4: extend

| extend AccountCustomEntity = DataUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataRuleNameproject
DataRuleQueryDetailsproject
DataUserNameproject
EventCreationTimeproject
AccountCustomEntityextend

Suspicious modification of Global Administrator user properties

#
Severity
medium
Time window
14d
Group by
AccountObjectId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query will detect if user properties of Global Administrator are updated by an existing user. Usually only user administrator or other global administrator can update such properties. Investigate if such user change is an attempt to elevate an existing low privileged identity or rogue administrator activity

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 48602a24-67cf-4362-b258-3f4249e55def
name: Suspicious modification of Global Administrator user properties
description: |
  'This query will detect if user properties of Global Administrator are updated by an existing user. Usually only user administrator or other global administrator can update such properties.
  Investigate if such user change is an attempt to elevate an existing low privileged identity or rogue administrator activity'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
query: |
  let query_frequency = 1h;
  let query_period = 14d;
  IdentityInfo
  | where TimeGenerated > ago(query_period)
  | where set_has_element(AssignedRoles, "Global Administrator")
  | distinct AccountUPN, AccountObjectId
  | join kind=inner (
      AuditLogs
      | where TimeGenerated > ago(query_frequency)
      | where OperationName=~ "Update user" and Result =~ "success"
      // | where isnotempty(InitiatedBy["user"])
      | mv-expand TargetResource = TargetResources
      | where TargetResource["type"] == "User"
      | extend AccountObjectId = tostring(TargetResource["id"])
      | where tostring(TargetResource["modifiedProperties"]) != "[]"
      | mv-apply modifiedProperty = TargetResource["modifiedProperties"] on (
          summarize modifiedProperties = make_bag(
              bag_pack(tostring(modifiedProperty["displayName"]),
                  bag_pack("oldValue", trim(@'[\"\s]+', tostring(modifiedProperty["oldValue"])),
                      "newValue", trim(@'[\"\s]+', tostring(modifiedProperty["newValue"])))))
      )
      | where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) in ("LastDirSyncTime", ""))
      | where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) == "StrongAuthenticationPhoneAppDetail" and isnotempty(modifiedProperties["StrongAuthenticationPhoneAppDetail"]) and tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["newValue"])))) == tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["oldValue"])))))
      | extend
          Initiator = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["displayName"]), tostring(InitiatedBy["user"]["userPrincipalName"])),
          InitiatorId = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["servicePrincipalId"]), tostring(InitiatedBy["user"]["id"])),
          IPAddress = tostring(InitiatedBy[tostring(bag_keys(InitiatedBy)[0])]["ipAddress"])
  ) on AccountObjectId
  | project TimeGenerated, Category, Identity, Initiator, IPAddress, OperationName, Result, AccountUPN, InitiatedBy, AdditionalDetails, TargetResources, AccountObjectId, InitiatorId, CorrelationId
  | extend
      InitiatorName = tostring(split(Initiator, "@")[0]),
      InitiatorUPNSuffix = tostring(split(Initiator, "@")[1]),
      AccountName = tostring(split(AccountUPN, "@")[0]),
      AccountUPNSuffix = tostring(split(AccountUPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Initiator
      - identifier: Name
        columnName: InitiatorName
      - identifier: UPNSuffix
        columnName: InitiatorUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUPN
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let query_frequency = 1h;
let query_period = 14d;

Stage 1: source

IdentityInfo

Stage 2: where

| where TimeGenerated > ago(query_period)

Stage 3: where

| where set_has_element(AssignedRoles, "Global Administrator")

Stage 4: distinct

| distinct AccountUPN, AccountObjectId

Stage 5: join

| join kind=inner (
    AuditLogs
    | where TimeGenerated > ago(query_frequency)
    | where OperationName=~ "Update user" and Result =~ "success"
    | mv-expand TargetResource = TargetResources
    | where TargetResource["type"] == "User"
    | extend AccountObjectId = tostring(TargetResource["id"])
    | where tostring(TargetResource["modifiedProperties"]) != "[]"
    | mv-apply modifiedProperty = TargetResource["modifiedProperties"] on (
        summarize modifiedProperties = make_bag(
            bag_pack(tostring(modifiedProperty["displayName"]),
                bag_pack("oldValue", trim(@'[\"\s]+', tostring(modifiedProperty["oldValue"])),
                    "newValue", trim(@'[\"\s]+', tostring(modifiedProperty["newValue"])))))
    )
    | where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) in ("LastDirSyncTime", ""))
    | where not(tostring(modifiedProperties["Included Updated Properties"]["newValue"]) == "StrongAuthenticationPhoneAppDetail" and isnotempty(modifiedProperties["StrongAuthenticationPhoneAppDetail"]) and tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["newValue"])))) == tostring(array_sort_asc(extract_all(@'\"Id\"\:\"([^\"]+)\"', tostring(modifiedProperties["StrongAuthenticationPhoneAppDetail"]["oldValue"])))))
    | extend
        Initiator = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["displayName"]), tostring(InitiatedBy["user"]["userPrincipalName"])),
        InitiatorId = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["servicePrincipalId"]), tostring(InitiatedBy["user"]["id"])),
        IPAddress = tostring(InitiatedBy[tostring(bag_keys(InitiatedBy)[0])]["ipAddress"])
) on AccountObjectId

Stage 6: project

project AccountObjectId, AccountUPN, AdditionalDetails, Category, CorrelationId, IPAddress, Identity, InitiatedBy, Initiator, InitiatorId, OperationName, Result, TargetResources, TimeGenerated

Stage 7: extend

extend AccountName, AccountUPNSuffix, InitiatorName, InitiatorUPNSuffix

Stage 8: summarize aggregation inside the mv-apply branch

summarize

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AssignedRolesin
  • Global Administrator transforms: set_has_element
field:"AssignedRoles" kind:in value:"Global Administrator"
OperationNameeq
  • Update user
field:"OperationName" kind:eq value:"Update user"
Resulteq
  • success
field:"Result" kind:eq value:"success"
TargetResource["modifiedProperties"]ne
  • [] transforms: tostring, kusto_dynamic_path
TargetResource["type"]eq
  • User transforms: kusto_dynamic_path

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
modifiedProperties["Included Updated Properties"]["newValue"]eqStrongAuthenticationPhoneAppDetail
modifiedProperties["StrongAuthenticationPhoneAppDetail"]is_not_null(no value, null check)
modifiedProperties["Included Updated Properties"]["newValue"]inLastDirSyncTime

VMware SD-WAN - Orchestrator Audit Event

#
Severity
informational
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This rule is searching for configuration changes. Configuration changes can override security measures and the overarching security design. Therefore audit events must be accurately tracked.

Rule Body

id: 50c86f92-86b0-4ae3-bb94-698da076ca9e
name: VMware SD-WAN - Orchestrator Audit Event
version: 1.0.0
kind: Scheduled
description: This rule is searching for configuration changes. Configuration changes can override security measures and the overarching security design. Therefore audit events must be accurately tracked.
severity: Informational
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
  - connectorId: VMwareSDWAN
    dataTypes:
      - SDWAN
query: |
  VMware_VECO_EventLogs_CL
  | where event == "EDIT_PROFILE"
  | extend edgeProfile = extract("^profile \\[(.+)\\] [a-z]+ module", 1, message)
  | extend configAction = extract("^profile \\[.+\\] (.+) module", 1, message)
  | extend edgeModule = extract("^profile \\[.+\\] [a-z]+ module \\[(.+)\\]$", 1, message)
  | extend configChange = todynamic(detail).diff
  | project TimeGenerated, severity, edgeProfile, configAction, edgeModule, configChange
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDescriptionFormat: "There was a configuration change event on the VMware Edge Cloud Orchestrator.\nThe configuration changes are the following:\n{{{configChange}} "
  alertDynamicProperties:
  - alertProperty: ProductComponentName
    value: edgeProfile
customDetails:
  edgeProfile: edgeProfile
  auditAction: configAction
  edgeModule: edgeModule
suppressionDuration: 5h

Stages and Predicates

Stage 1: source

VMware_VECO_EventLogs_CL

Stage 2: where

| where event == "EDIT_PROFILE"

Stage 3: extend (4 consecutive steps)

| extend edgeProfile = extract("^profile \\[(.+)\\] [a-z]+ module", 1, message)
| extend configAction = extract("^profile \\[.+\\] (.+) module", 1, message)
| extend edgeModule = extract("^profile \\[.+\\] [a-z]+ module \\[(.+)\\]$", 1, message)
| extend configChange = todynamic(detail).diff

Stage 4: project

| project TimeGenerated, severity, edgeProfile, configAction, edgeModule, configChange

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
eventeq
  • EDIT_PROFILE
field:"event" kind:eq value:"EDIT_PROFILE"

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
configActionproject
configChangeproject
edgeModuleproject
edgeProfileproject
severityproject

Back to all Kusto rules