Detection rules › Kusto
Kusto rules: 1a
ADFS Database Named Pipe Connection
#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
| Tactic | Techniques |
|---|---|
| Collection |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 18: PipeEvent (Pipe Connected) |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Computer | in |
| field:"Computer" kind:in value:"ADFS_Servers" |
EventID | eq |
| field:"EventID" kind:eq value:"18" |
PipeName | eq |
| field:"PipeName" kind:eq value:"\MICROSOFT##WID\tsql\query" |
Source | eq |
| field:"Source" kind:eq value:"Microsoft-Windows-Sysmon" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
process | in | AzureADConnect.exe, Microsoft.Identity.Health.Adfs.PshSurrogate.exe, Microsoft.IdentityServer.ServiceHost.exe, Microsoft.Tri.Sensor.exe, mmc.exe, sqlservr.exe, wsmprovhost.exe | excludes:process |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RenderedDescription | extend |
EventData | extend |
Key | extend |
Value | extend |
EventType | extend |
Image | extend |
PipeName | extend |
RuleName | extend |
TechniqueId | extend |
TechniqueName | extend |
process | extend |
Operation | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
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.
'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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Lateral Movement | |
| Command & Control |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
environment | eq |
| field:"environment" kind:eq value:"production" |
result | eq |
| field:"result" kind:eq value:"exploited" |
CTE - Compromised Device Suspected (High-Volume IOC Blocks)
#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
| Tactic | Techniques |
|---|---|
| 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 =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"block.url" |
IOC_BlockCount | ge |
| field:"IOC_BlockCount" kind:ge value:"5" |
IsIoc | eq |
| field:"IsIoc" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
DeviceOs | summarize |
DeviceUser | summarize |
DeviceVersion | summarize |
FirstSeen | summarize |
HostName | summarize |
IOC_BlockCount | summarize |
LastSeen | summarize |
SampleIps | summarize |
SampleUrls | summarize |
TenantId | summarize |
ClientIp | extend |
Url | extend |
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.
'Generates an incident when Cyble Intelligence detects a malicious advertisement, malvertising activity, or redirect attempting to impersonate a legitimate brand.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"malicious_ads" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Dataverse - Login from IP in the block list
#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
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
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
let blocked_networks = MSBizAppsNetworkAddresses()
| where Tags has "BlockDataverse"
| summarize by IPSubnet;
Let Binding: watchlist_entries_count
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientIp | is_not_null | field:"ClientIp" kind:is_not_null | |
Message | eq |
| field:"Message" kind:eq value:"UserSignIn" |
watchlist_entries_count | gt |
| field:"watchlist_entries_count" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ClientIp | project |
CloudAppId | project |
FirstEvent | project |
InstanceUrl | project |
Message | project |
UPNSuffix | project |
UserId | project |
Detect Registry Run Key Creation/Modification
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
RegistryKey | match |
| field:"TargetObject" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessCommandLine | project |
ActingProcessId | project |
ActingProcessName | project |
ActorUsername | project |
ActorUsernameType | project |
DvcHostname | project |
RegistryKey | project |
RegistryValue | project |
RegistryValueData | project |
RegistryValueType | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Username | extend |
NTDomain | extend |
UPNSuffix | extend |
RegHive | extend |
RegKey | extend |
Detecting UAC bypass - ChangePK and SLUI registry tampering
#This query identifies setting a registry key under HKCU, launching slui.exe and then ChangePK.exe.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
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
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Source |
|---|---|
CommandLine | project |
Computer | project |
EventDetail | project |
Hashes | project |
Image | project |
Source | project |
TimeGenerated | project |
Type | extend |
UserName | project |
AccountName | extend |
AccountUPNSuffix | extend |
FileHash | extend |
HostEntity | extend |
timestamp | extend |
InitiatingProcessAccount | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
External user added and removed in short timeframe
#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
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | MemberAdded |
| Microsoft 365 | MemberRemoved |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Members | match |
| field:"Members" kind:match value:"#EXT#" |
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq |
Operation | eq |
| field:"Operation" kind:eq |
UPN | match |
| field:"UPN" kind:match value:"#EXT#" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | project |
MemberAdded_Removed | project |
TeamName | project |
TimeAdded | project |
TimeDeleted | project |
UserWhoAdded | project |
UserWhoDeleted | project |
MemberAdded_RemovedAccountName | extend |
MemberAdded_RemovedAccountUPNSuffix | extend |
UserWhoAddedAccountName | extend |
UserWhoAddedAccountUPNSuffix | extend |
UserWhoDeletedAccountName | extend |
UserWhoDeletedAccountUPNSuffix | extend |
GCP IAM - New Service Account
#Detects new service account creation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | google.iam.admin.CreateServiceAccount: Create service account |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"google.iam.admin.v1.CreateServiceAccount" |
result | eq |
| field:"result" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | extend |
AccountUPNSuffix | extend |
Hunt for public remotly exploitable devices (with high EPSS)
#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
| Tactic | Techniques |
|---|---|
| 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
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
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.
| Field | Source |
|---|---|
CveList | summarize |
DeviceId | summarize |
DeviceName | summarize |
CveCount | extend |
Infoblox - TI - CommonSecurityLog Match Found - MalwareC2
#'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
| Tactic | Techniques |
|---|---|
| Impact |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Description | match |
| field:"Description" kind:match |
DestinationDnsDomain | is_not_null | field:"DestinationDnsDomain" kind:is_not_null | |
DomainName | is_not_null | field:"DomainName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
AdditionalExtensions | project |
AdditionalInformation | project |
ConfidenceScore | project |
Description | project |
DestinationDnsDomain | project |
DeviceAction | project |
DeviceEventClassID | project |
DeviceName | project |
ExpirationDateTime | project |
ExternalIndicatorId | project |
HitTime | project |
IndicatorId | project |
LatestIndicatorTime | project |
SourceIP | project |
SourceMACAddress | project |
SourceSystem | project |
SourceUserName | project |
Tags | project |
ThreatType | project |
TrafficLightProtocolLevel | project |
Type | project |
Insider Risk_High User Security Alert Correlations
#'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
| Tactic | Techniques |
|---|---|
| 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
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Entities["Type"] | eq |
|
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertLinks | project |
AlertName | project |
LastObserved | project |
Percent | project |
ProductName | project |
Severity | project |
UPN | project |
AccountName | extend |
AccountUPNSuffix | extend |
License Support Expired
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"24050" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
Days Left | project |
Days of Support Left | project |
EventId | project |
License Edition | project |
License Type | project |
MessageDetails | project |
Severity | project |
Mimecast Secure Email Gateway - Virus
#'Detect threat for virus from mail receipt virus event.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Type | eq |
| field:"Type" kind:eq value:"email_receipt" |
Virus Found | is_not_null | field:"Virus Found" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SenderEnvelope | extend |
Mimecast Secure Email Gateway - Virus
#Detect threat for virus from mail receipt virus event
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
mimecastEventId_s | eq |
| field:"mimecastEventId_s" kind:eq value:"mail_receipt_virus" |
Pathlock TDnR - Bank Master Data Changes
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"CHANGEDOC_BANK" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Possible Resource-Based Constrained Delegation Abuse
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
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 =DomainIndex != -1substring(Computer, (DomainIndex + 1))ComputerStage 10: project-away
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AttributeLDAPDisplayName | eq |
| field:"AttributeLDAPDisplayName" kind:eq value:"msDS-AllowedToActOnBehalfOfOtherIdentity" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
ObjectClass | eq |
| field:"ObjectClass" kind:eq value:"computer" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AttributeLDAPDisplayName | summarize |
Computer | summarize |
EndTime | summarize |
ObjectDN | summarize |
StartTime | summarize |
SubjectAccount | summarize |
SubjectDomainName | summarize |
SubjectLogonId | summarize |
SubjectUserName | summarize |
SubjectUserSid | summarize |
HostName | extend |
HostNameDomain | extend |
Power Automate - Departing employee flow activity
#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
| Tactic | Techniques |
|---|---|
| Exfiltration | |
| Impact | |
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Power Automate activity (catch-all) |
| Microsoft 365 | CreateFlow |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalType | in |
| field:"EventOriginalType" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ActorName | project |
CloudAppId | project |
EnvironmentId | project |
EventOriginalType | project |
FlowId | project |
PowerAutomateAppId | project |
TimeGenerated | project |
UPNSuffix | project |
Preview - TI map Domain entity to Cloud App Events
#Identifies compromises and attacks and detect malicious activities in one's domain entity from TI.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | CloudAppEvents action any: Cloud app activity |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
TI_DomainEntity | is_not_null | field:"TI_DomainEntity" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AlertSeverity | project |
CloudAppEvents_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
DomainName | project |
ExpirationDateTime | project |
IPAddress | project |
IndicatorId | project |
ProviderName | project |
ThreatType | project |
Sentinel One - Rule deleted
#'Detects when a rule was deleted.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | eq |
| field:"ActivityType" kind:eq value:"3602" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataRuleName | project |
DataRuleQueryDetails | project |
DataUserName | project |
EventCreationTime | project |
AccountCustomEntity | extend |
Suspicious modification of Global Administrator user properties
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | IdentityInfo action any: Identity information |
| Entra-AuditLogs | Update user |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AssignedRoles | in |
| field:"AssignedRoles" kind:in value:"Global Administrator" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Update user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetResource["modifiedProperties"] | ne |
| |
TargetResource["type"] | eq |
|
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
modifiedProperties["Included Updated Properties"]["newValue"] | eq | StrongAuthenticationPhoneAppDetail | |
modifiedProperties["StrongAuthenticationPhoneAppDetail"] | is_not_null | ||
modifiedProperties["Included Updated Properties"]["newValue"] | in | LastDirSyncTime |
VMware SD-WAN - Orchestrator Audit Event
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"EDIT_PROFILE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TimeGenerated | project |
configAction | project |
configChange | project |
edgeModule | project |
edgeProfile | project |
severity | project |