Detection rules › Kusto
Kusto rules: 1j
AdminSDHolder Modifications
#This query detects modification in the AdminSDHolder in the Active Directory which could indicate an attempt for persistence. AdminSDHolder Modification is a persistence technique in which an attacker abuses the SDProp process in Active Directory to establish a persistent backdoor to Active Directory. This query searches for the event id 5136 where the Object DN is AdminSDHolder. Ref: https://netwrix.com/en/cybersecurity-glossary/cyber-security-attacks/adminsdholder-attack/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: 52aec824-96c1-4a03-8e44-bb70532e6cea
name: AdminSDHolder Modifications
description: |
'This query detects modification in the AdminSDHolder in the Active Directory which could indicate an attempt for persistence.
AdminSDHolder Modification is a persistence technique in which an attacker abuses the SDProp process in Active Directory to establish a persistent backdoor to Active Directory.
This query searches for the event id 5136 where the Object DN is AdminSDHolder.
Ref: https://netwrix.com/en/cybersecurity-glossary/cyber-security-attacks/adminsdholder-attack/'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078
query: |
SecurityEvent
| where EventID == 5136 and EventData contains "<Data Name=\"ObjectDN\">CN=AdminSDHolder,CN=System"
| parse EventData with * 'ObjectDN">' ObjectDN "<" *
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, SubjectUserSid, SubjectLogonId, ObjectDN
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend Name = tostring(split(SubjectAccount, "\\")[1]), NTDomain = tostring(split(SubjectAccount, "\\")[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SubjectAccount
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: NTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Vasileios Paschalidis
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136 and EventData contains "<Data Name=\"ObjectDN\">CN=AdminSDHolder,CN=System"
Stage 3: parse
| parse EventData with * 'ObjectDN">' ObjectDN "<" *
Stage 4: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, SubjectUserSid, SubjectLogonId, ObjectDN
Stage 5: 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 Name = tostring(split(SubjectAccount, "\\")[1]), NTDomain = tostring(split(SubjectAccount, "\\")[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | contains |
| field:"EventData" kind:contains |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
EndTime | summarize |
ObjectDN | summarize |
StartTime | summarize |
SubjectAccount | summarize |
SubjectLogonId | summarize |
SubjectUserSid | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
NTDomain | extend |
Name | extend |
Audit policy manipulation using auditpol utility
#This detects attempts to manipulate audit policies using auditpol command. This technique was seen in relation to Solorigate attack but the results can indicate potential malicious activity used in different attacks. The process name in each data source is commented out as an adversary could rename it. It is advisable to keep process name commented but if the results show unrelated false positives, users may want to uncomment it. Refer to auditpol syntax: https://docs.microsoft.com/windows-server/administration/windows-commands/auditpol
Refer to our M365 blog for details on use during the Solorigate attack: https://www.microsoft.com/security/blog/2021/01/20/deep-dive-into-the-solorigate-second-stage-activation-from-sunburst-to-teardrop-and-raindrop/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
Rule Body
id: 66276b14-32c5-4226-88e3-080dacc31ce1
name: Audit policy manipulation using auditpol utility
description: |
This detects attempts to manipulate audit policies using auditpol command.
This technique was seen in relation to Solorigate attack but the results can indicate potential malicious activity used in different attacks.
The process name in each data source is commented out as an adversary could rename it. It is advisable to keep process name commented but if the results show unrelated false positives, users may want to uncomment it.
Refer to auditpol syntax: https://docs.microsoft.com/windows-server/administration/windows-commands/auditpol
Refer to our M365 blog for details on use during the Solorigate attack:
https://www.microsoft.com/security/blog/2021/01/20/deep-dive-into-the-solorigate-second-stage-activation-from-sunburst-to-teardrop-and-raindrop/
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
tags:
- Solorigate
- NOBELIUM
query: |
let timeframe = 1d;
let AccountAllowList = dynamic(['SYSTEM']);
let SubCategoryList = dynamic(["Logoff", "Account Lockout", "User Account Management", "Authorization Policy Change"]); // Add any Category in the list to be allowed or disallowed
let tokens = dynamic(["clear", "remove", "success:disable","failure:disable"]);
(union isfuzzy=true
(
SecurityEvent
| where TimeGenerated >= ago(timeframe)
//| where Process =~ "auditpol.exe"
| where CommandLine has_any (tokens)
| where AccountType !~ "Machine" and Account !in~ (AccountAllowList)
| parse CommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList) //use in~ for inclusion or !in~ for exclusion
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable") // use this filter if required to exclude certain toggles
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, DeviceName = Computer
),
(
DeviceProcessEvents
| where TimeGenerated >= ago(timeframe)
// | where InitiatingProcessFileName =~ "auditpol.exe"
| where InitiatingProcessCommandLine has_any (tokens)
| where AccountName !in~ (AccountAllowList)
| parse InitiatingProcessCommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList) //use in~ for inclusion or !in~ for exclusion
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable") // use this filter if required to exclude certain toggles
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessCommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain
),
(
Event
| where TimeGenerated > ago(timeframe)
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=['#text']
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
// | where OriginalFileName =~ "auditpol.exe"
| where CommandLine has_any (tokens)
| where User !in~ (AccountAllowList)
| parse CommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList) //use in~ for inclusion or !in~ for exclusion
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable") // use this filter if required to exclude certain toggles
| project TimeGenerated, Computer, User, Process, ParentImage, CommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = tostring(split(User, @'\')[1]), AccountUPNSuffix = tostring(split(User, @'\')[0]), DeviceName = Computer
)
)
| extend Account = strcat(AccountDomain, "\\", AccountName)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceName
version: 1.2.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let timeframe = 1d;
let AccountAllowList = dynamic(['SYSTEM']);
let SubCategoryList = dynamic(["Logoff", "Account Lockout", "User Account Management", "Authorization Policy Change"]);
let tokens = dynamic(["clear", "remove", "success:disable","failure:disable"]);
Union isfuzzy=true (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, DeviceProcessEvents, Event
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where CommandLine has_any (tokens)
| where AccountType !~ "Machine" and Account !in~ (AccountAllowList)
| parse CommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList)
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable")
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, DeviceName = Computer
Leg 2: DeviceProcessEvents
DeviceProcessEvents
| where TimeGenerated >= ago(timeframe)
| where InitiatingProcessCommandLine has_any (tokens)
| where AccountName !in~ (AccountAllowList)
| parse InitiatingProcessCommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList)
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable")
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessCommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain
Leg 3: Event
Event
| where TimeGenerated > ago(timeframe)
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=['#text']
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| where CommandLine has_any (tokens)
| where User !in~ (AccountAllowList)
| parse CommandLine with * "/subcategory:" subcategorytoken
| extend SubCategory = tostring(split(subcategorytoken, "\"")[1]) , Toggle = tostring(split(subcategorytoken, "\"")[2])
| where SubCategory in~ (SubCategoryList)
| where Toggle !in~ ("/failure:disable", " /success:enable /failure:disable")
| project TimeGenerated, Computer, User, Process, ParentImage, CommandLine, SubCategory, Toggle
| extend timestamp = TimeGenerated, AccountName = tostring(split(User, @'\')[1]), AccountUPNSuffix = tostring(split(User, @'\')[0]), DeviceName = Computer
Applied to the Combined Result
| extend Account = strcat(AccountDomain, "\\", AccountName)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountType | ne |
| field:"AccountType" kind:ne value:"Machine" |
CommandLine | match |
| field:"CommandLine" kind:match |
EventID | eq |
| field:"EventID" kind:eq value:"1" |
InitiatingProcessCommandLine | match |
| field:"ParentCommandLine" kind:match |
SubCategory | in |
| field:"SubCategory" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Account | eq | AccountAllowList | excludes:Account field:"Account" value:"AccountAllowList" |
Toggle | in | /success:enable /failure:disable, /failure:disable | excludes:Toggle field:"Toggle" value:" /success:enable /failure:disable" field:"Toggle" value:"/failure:disable" |
AccountName | eq | AccountAllowList | excludes:AccountName field:"AccountName" value:"AccountAllowList" |
User | eq | AccountAllowList | excludes:User field:"User" value:"AccountAllowList" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | project |
Computer | project |
ParentImage | project |
Process | project |
SubCategory | project |
TimeGenerated | project |
Toggle | project |
User | project |
AccountName | extend |
AccountUPNSuffix | extend |
DeviceName | extend |
timestamp | extend |
Account | extend |
Cisco SEG - Multiple suspiciuos attachments received
#'Detects possibly phishing emails.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: dfdb9a73-4335-4bb4-b29b-eb713bce61a6
name: Cisco SEG - Multiple suspiciuos attachments received
description: |
'Detects possibly phishing emails.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let r_threshold = 5;
CiscoSEGEvent
| where NetworkDirection =~ 'Incoming'
| where isnotempty(EventMessage)
| where strlen(EventMessage) < 20
| summarize rec=makeset(DstUserName) by EventMessage, bin(TimeGenerated, 10m)
| where array_length(rec) > r_threshold
| extend AccountCustomEntity = rec
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let r_threshold = 5;
Stage 1: source
CiscoSEGEvent
Stage 2: where
| where NetworkDirection =~ 'Incoming'
Stage 3: where
| where isnotempty(EventMessage)
Stage 4: where
| where strlen(EventMessage) < 20
Stage 5: summarize
| summarize rec=makeset(DstUserName) by EventMessage, bin(TimeGenerated, 10m)
Stage 6: where
| where array_length(rec) > r_threshold
Stage 7: extend
| extend AccountCustomEntity = rec
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | is_not_null | field:"EventMessage" kind:is_not_null | |
EventMessage | lt |
| field:"EventMessage" kind:lt value:"20" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"Incoming" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventMessage | summarize |
rec | summarize |
AccountCustomEntity | extend |
CYFIRMA - High severity File Hash Indicators with Block Action Rule
#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.
"This query retrieves file hash indicators marked for blocking, with no assigned role, from the CyfirmaIndicators_CL table. It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms, or automated blocklists."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Impact |
Rule Body
id: 7cb829b2-915a-42c2-adb9-725e9ce9bf43
name: CYFIRMA - High severity File Hash Indicators with Block Action Rule
description: |
"This query retrieves file hash indicators marked for blocking, with no assigned role, from the CyfirmaIndicators_CL table.
It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms, or automated blocklists."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
// File Hash Indicators with Block Action
let timeFrame = 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='MD5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
MD5,
SHA1,
SHA256,
Algo_MD5,
Algo_SHA1,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- Execution
- InitialAccess
- DefenseEvasion
- Impact
relevantTechniques:
- T1204
- T1566
- T1027
- T1486
- T1566.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence File Hash Indicators with Block Action - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_MD5
- identifier: Value
columnName: MD5
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA1
- identifier: Value
columnName: SHA1
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA256
- identifier: Value
columnName: SHA256
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))
Stage 3: extend (4 consecutive steps)
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='MD5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: project
| project
MD5,
SHA1,
SHA256,
Algo_MD5,
Algo_SHA1,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Block" |
Roles | is_null | field:"Roles" kind:is_null | |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - High severity Malicious Network Indicators Associated with Malware - Monitor Recommended Rule
#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.
"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
Rule Body
id: c7b0b6da-e9dc-405d-ad71-37661f56d40a
name: CYFIRMA - High severity Malicious Network Indicators Associated with Malware - Monitor Recommended Rule
description: |
"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Network Indicators Associated with Malware - Monitor Recommended Rule
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Malware'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- CommandAndControl
relevantTechniques:
- T1566
- T1189
- T1204
- T1071
- T1090
- T1059
- T1568
- T1566.002
- T1204.001
- T1071.001
- T1568.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Malicious Network Indicators Associated with Malware - Monitor Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Malware'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | match |
| field:"Roles" kind:match value:"Malware" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - Social and Public Exposure - Confidential Files Information Exposure Rule
#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.
"This rule detects Medium-severity alerts from CYFIRMA regarding exposure of confidential files or forms linked to internal or client-related information, publicly accessible on platforms. These exposures could lead to data leakage, compliance violations, or targeted attacks."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Collection | |
| Exfiltration |
Rule Body
id: a2984be5-8d69-4139-b98f-e89c9c421c27
name: CYFIRMA - Social and Public Exposure - Confidential Files Information Exposure Rule
description: |
"This rule detects Medium-severity alerts from CYFIRMA regarding exposure of confidential files or forms linked to internal or client-related information, publicly accessible on platforms.
These exposures could lead to data leakage, compliance violations, or targeted attacks."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaSPEConfidentialFilesAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Exfiltration
- Collection
- Reconnaissance
relevantTechniques:
- T1189
- T1213
- T1593
- T1567.002
query: |
// Medium severity - Social and Public Exposure - Confidential Files Information Exposure
let timeFrame = 5m;
CyfirmaSPEConfidentialFilesAlerts_CL
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Impact=impact,
Recommendation=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
customDetails:
AssetType: AssetType
AssetValue: AssetValue
RiskScore: RiskScore
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "Cyfirma - Medium Severity Alert: Confidential Files Information Exposure on Public Platforms - {{AlertTitle}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaSPEConfidentialFilesAlerts_CL
Stage 2: where
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Impact=impact,
Recommendation=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
TimeGenerated | project |
UID | project |
Dataverse - Anomalous application user activity
#Identifies anomalies in activity patterns of Dataverse application (non-interactive) users, based on activity falling outside the normal pattern of use.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Credential Access | |
| Execution | |
| Persistence | |
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: 0820da12-e895-417f-9175-7c256fcfb33e
kind: Scheduled
name: Dataverse - Anomalous application user activity
description: Identifies anomalies in activity patterns of Dataverse application (non-interactive)
users, based on activity falling outside the normal pattern of use.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 5h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Execution
- Persistence
relevantTechniques:
- T1528
- T1569
- T0871
- T0834
- T0859
query: |
let query_lookback = 14d;
let query_frequency = 5h;
let anomaly_threshold = 2.5;
let seasonality = -1;
let trend = 'linefit';
let step_duration = 5h;
let app_user_regex = "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\.com$";
let guid_regex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})";
let application_users = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where UserId !endswith "@onmicrosoft.com" and UserId != "Unknown"
| summarize by UserId
| where split(UserId, "@")[1] matches regex app_user_regex;
DataverseActivity
| where TimeGenerated >= startofday(ago(query_lookback))
| where UserId in (application_users)
| where isnotempty(OriginalObjectId)
| make-series TotalEvents = count() default=0 on TimeGenerated from startofday(ago(query_lookback)) to now() step step_duration by UserId, InstanceUrl, OriginalObjectId
| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(TotalEvents, anomaly_threshold, seasonality, trend)
| mv-expand
TotalEvents to typeof(double),
AnomalyTimeGenerated = TimeGenerated to typeof(datetime),
Anomalies to typeof(double),
Score to typeof(double),
Baseline to typeof(long)
| where Anomalies > 0
| extend Details = bag_pack(
"TotalEvents",
TotalEvents,
"Anomalies",
Anomalies,
"Baseline",
Baseline,
"Score",
Score,
"OriginalObjectId",
OriginalObjectId
)
| summarize Details = make_set(Details, 100) by UserId, InstanceUrl, AnomalyTimeGenerated
| extend
CloudAppId = int(32780),
AadUserId = extract(guid_regex, 1, tostring(split(UserId, "@")[0]))
| project
AnomalyTimeGenerated,
UserId,
AadUserId,
InstanceUrl,
Details,
CloudAppId
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: AadUserId
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: 'Dataverse - Non-interactive account anomaly detected in
{{InstanceUrl}} '
alertDescriptionFormat: 'Anomaly detected on {{UserId}} in {{InstanceUrl}}. Details:
{{Details}}'
customDetails:
InstranceUrl: InstanceUrl
version: 3.2.0
Stages and Predicates
Parameters
let query_lookback = 14d;
let query_frequency = 5h;
let anomaly_threshold = 2.5;
let seasonality = -1;
let trend = 'linefit';
let step_duration = 5h;
let app_user_regex = "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\.com$";
let guid_regex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})";
Let Binding: application_users
let application_users = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where UserId !endswith "@onmicrosoft.com" and UserId != "Unknown"
| summarize by UserId
| where split(UserId, "@")[1] matches regex app_user_regex;
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated >= startofday(ago(query_lookback))
Stage 3: where
| where UserId in (application_users)
Stage 4: where
| where isnotempty(OriginalObjectId)
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 5: make-series
| make-series TotalEvents = count() default=0 on TimeGenerated from startofday(ago(query_lookback)) to now() step step_duration by UserId, InstanceUrl, OriginalObjectId
Stage 6: extend
| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(TotalEvents, anomaly_threshold, seasonality, trend)
Stage 7: mv-expand
| mv-expand
TotalEvents to typeof(double),
AnomalyTimeGenerated = TimeGenerated to typeof(datetime),
Anomalies to typeof(double),
Score to typeof(double),
Baseline to typeof(long)
Stage 8: where
| where Anomalies > 0
Stage 9: extend
| extend Details = bag_pack(
"TotalEvents",
TotalEvents,
"Anomalies",
Anomalies,
"Baseline",
Baseline,
"Score",
Score,
"OriginalObjectId",
OriginalObjectId
)
Stage 10: summarize
| summarize Details = make_set(Details, 100) by UserId, InstanceUrl, AnomalyTimeGenerated
Stage 11: extend
| extend
CloudAppId = int(32780),
AadUserId = extract(guid_regex, 1, tostring(split(UserId, "@")[0]))
Stage 12: project
| project
AnomalyTimeGenerated,
UserId,
AadUserId,
InstanceUrl,
Details,
CloudAppId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Anomalies | gt |
| field:"Anomalies" kind:gt value:"0" |
OriginalObjectId | is_not_null | field:"OriginalObjectId" kind:is_not_null | |
UserId | in |
| field:"user" kind:in value:"application_users" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AadUserId | project |
AnomalyTimeGenerated | project |
CloudAppId | project |
Details | project |
InstanceUrl | project |
UserId | project |
Detect known risky user agents (ASIM Web Session)
#'This rule is designed to flag web requests that contain a user agent header that is recognized as malicious. It relies on a predefined list of known user agents, which is referenced from a specific CSV file'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Rule Body
id: 6a4dbcf8-f5e2-4b33-b34f-2db6487613f0
name: Detect known risky user agents (ASIM Web Session)
description: |
'This rule is designed to flag web requests that contain a user agent header that is recognized as malicious. It relies on a predefined list of known user agents, which is referenced from a specific CSV file'
severity: Medium
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1190
- T1133
- T1071
query: |
let lookback = 5m;
let knownUserAgentsIndicators = materialize(externaldata(UserAgent: string, UserAgentCategory: string)
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"]
with(format="csv", ignoreFirstRecord=True)
| extend joiner = 1);
let CustomUserAgentIndicators = (_ASIM_GetWatchlistRaw("UnusualUserAgents") // Create new Watchlist and add your custom indicators(Optional)
| extend
UserAgent = tostring(WatchlistItem["UserAgent"]),
UserAgentCategory = tostring(WatchlistItem["UserAgentCategory"])
| project UserAgent, UserAgentCategory, joiner = 1
| where isnotempty(UserAgent) and isnotempty(UserAgentCategory));
let CombinedUAList = union knownUserAgentsIndicators, CustomUserAgentIndicators;
let knownUserAgents=toscalar(CombinedUAList
| where isnotempty(UserAgent)
| summarize make_list(UserAgent, 1000));
_Im_WebSession (starttime=ago(lookback), httpuseragent_has_any=knownUserAgents)
| project SrcIpAddr, SrcUsername, SrcHostname, HttpUserAgent, Url, DstIpAddr, DstPortNumber, TimeGenerated
| extend joiner = 1
| join kind=inner CombinedUAList on joiner
| where HttpUserAgent has UserAgent
| summarize
EventCount=count(),
EventStartTime = min(TimeGenerated),
EventEndTime=max(TimeGenerated)
by SrcIpAddr, SrcUsername, SrcHostname, Url, HttpUserAgent, DstIpAddr, DstPortNumber, UserAgentCategory
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DstIpAddr
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcHostname
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
EventCount: EventCount
EventStartTime: EventStartTime
EventEndTime: EventEndTime
HttpUserAgent: HttpUserAgent
UserAgentCategory: UserAgentCategory
alertDetailsOverride:
alertDisplayNameFormat: "User '{{SrcUsername}}' with IP '{{SrcIpAddr}}' has been observed using User Agent categorized as '{{UserAgentCategory}}'"
alertDescriptionFormat: "User accessed this URL '{{Url}}' using User Agent '{{HttpUserAgent}}'. Perform a thorough analysis of the requests associated with this user agent header"
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 5m;
let CombinedUAList = union knownUserAgentsIndicators, CustomUserAgentIndicators;
Let Binding: knownUserAgentsIndicators
let knownUserAgentsIndicators = materialize(externaldata(UserAgent: string, UserAgentCategory: string)
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"]
with(format="csv", ignoreFirstRecord=True)
| extend joiner = 1);
Let Binding: CustomUserAgentIndicators
let CustomUserAgentIndicators = (_ASIM_GetWatchlistRaw("UnusualUserAgents")
| extend
UserAgent = tostring(WatchlistItem["UserAgent"]),
UserAgentCategory = tostring(WatchlistItem["UserAgentCategory"])
| project UserAgent, UserAgentCategory, joiner = 1
| where isnotempty(UserAgent) and isnotempty(UserAgentCategory));
Let Binding: knownUserAgents
let knownUserAgents = toscalar(CombinedUAList
| where isnotempty(UserAgent)
| summarize make_list(UserAgent, 1000));
Stage 1: source
_Im_WebSession (starttime=ago(lookback), httpuseragent_has_any=knownUserAgents)
Stage 2: project
| project SrcIpAddr, SrcUsername, SrcHostname, HttpUserAgent, Url, DstIpAddr, DstPortNumber, TimeGenerated
Stage 3: extend
| extend joiner = 1
Stage 4: join
join kind=inner (CombinedUAList) on joiner
Stage 5: where
where HttpUserAgent =~ UserAgent
Stage 6: summarize
summarize EventCount, EventEndTime, EventStartTime by SrcIpAddr, SrcUsername, SrcHostname, Url, HttpUserAgent, DstIpAddr, DstPortNumber, UserAgentCategory
Stage 7: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UserAgent | is_not_null | field:"UserAgent" kind:is_not_null | |
UserAgentCategory | is_not_null | field:"UserAgentCategory" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | summarize |
DstPortNumber | summarize |
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
HttpUserAgent | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
UserAgentCategory | summarize |
Name | extend |
UPNSuffix | extend |
Device Alert Surge
#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.
'Triggers an incident when a device generates 5 or more Medium or High severity alerts, indicating potential compromise.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 5c8e1f2e-9d6b-4f4a-8f3e-123456789abc
name: Device Alert Surge
description: |
'Triggers an incident when a device generates 5 or more Medium or High severity alerts, indicating potential compromise.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MorphisecCCF
dataTypes:
- Morphisec
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1059
- T1204
query: |
MorphisecAlerts_CL
| where threatMessageArrivalTime >= ago(1h)
| where attackSeverity in ("HIGH", "MEDIUM")
| summarize arg_max(threatMessageArrivalTime, *) by id
| summarize AlertCount = dcount(id) by hostname
| where AlertCount >= 5
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: hostname
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
groupByEntities:
- Host
groupByAlertDetails:
- DisplayName
alertDetailsOverride:
alertDisplayNameFormat: "Device alert surge detected: {{hostname}}"
alertDescriptionFormat: "Device {{hostname}} triggered {{AlertCount}} distinct High/Medium alerts within the last hour."
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MorphisecAlerts_CL
Stage 2: where
| where threatMessageArrivalTime >= ago(1h)
Stage 3: where
| where attackSeverity in ("HIGH", "MEDIUM")
Stage 4: summarize
| summarize arg_max(threatMessageArrivalTime, *) by id
Stage 5: summarize
| summarize AlertCount = dcount(id) by hostname
Stage 6: where
| where AlertCount >= 5
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertCount | ge |
| field:"AlertCount" kind:ge value:"5" |
attackSeverity | in |
| field:"attackSeverity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertCount | summarize |
hostname | summarize |
GitLab - Repository visibility to Public
#'This query leverages GitLab Audit Logs. A repository in GitLab changed visibility from Private or Internal to Public which could indicate compromise, error or misconfiguration leading to exposing the repository to the public.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Rule Body
id: 8b291c3d-90ba-4ebf-af2c-0283192d430e
name: GitLab - Repository visibility to Public
description: |
'This query leverages GitLab Audit Logs. A repository in GitLab changed visibility from Private or Internal to Public which could indicate compromise, error or misconfiguration leading to exposing the repository to the public.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- DefenseEvasion
- CredentialAccess
relevantTechniques:
- T1556
query: |
GitLabAudit
| where SourceVisibility != "Public" and ChangeType == "visibility" and TargetVisibility == "Public"
| project TimeGenerated, EventTime, IPAddress, AuthorName, ChangeType, TargetType, SourceVisibility, TargetVisibility, EntityName
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AuthorName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: EntityName
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
GitLabAudit
Stage 2: where
| where SourceVisibility != "Public" and ChangeType == "visibility" and TargetVisibility == "Public"
Stage 3: project
| project TimeGenerated, EventTime, IPAddress, AuthorName, ChangeType, TargetType, SourceVisibility, TargetVisibility, EntityName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ChangeType | eq |
| field:"ChangeType" kind:eq value:"visibility" |
SourceVisibility | ne |
| field:"SourceVisibility" kind:ne value:"Public" |
TargetVisibility | eq |
| field:"TargetVisibility" kind:eq value:"Public" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AuthorName | project |
ChangeType | project |
EntityName | project |
EventTime | project |
IPAddress | project |
SourceVisibility | project |
TargetType | project |
TargetVisibility | project |
TimeGenerated | project |
IaaS shadow admin 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.
The policy detects shadow admin users in AWS or Azure.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 31f43e9d-1839-4baf-a668-54c28b98af3e
name: IaaS shadow admin detected
kind: Scheduled
description: The policy detects shadow admin users in AWS or Azure.
severity: High
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |-
Authomize_v2_CL
| where ingestion_time() >= ago(30m)
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
| where Policy has "IaaS shadow admin detected"
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
requiredDataConnectors:
- connectorId: Authomize
dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: Alert from Authomize - IaaS shadow admin detected
alertDescriptionFormat: |
IaaS shadow admin detected. The policy detects shadow admin users in AWS or Azure.
alertSeverity: Severity
alertTactics: Tactics
alertDynamicProperties:
- alertProperty: AlertLink
value: URL
customDetails:
AuthomizeEventID: EventID
EventName: Policy
EventDescription: Description
EventRecommendation: Recommendation
ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.2
Stages and Predicates
Stage 1: source
Authomize_v2_CL
Stage 2: where
| where ingestion_time() >= ago(30m)
Stage 3: extend
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
Stage 4: where
| where Policy has "IaaS shadow admin detected"
Stage 5: project
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Policy | match |
| field:"policy_name_s" kind:match value:"IaaS shadow admin detected" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | project |
Description | project |
EventID | project |
Policy | project |
Recommendation | project |
Severity | project |
Tactics | project |
URL | project |
Malware Activity Detected
#Detects when restore points marked as suspicious. This might indicate potential compromise of backup data.
Rule Body
id: 9a7c80ef-8dc2-4b07-834d-b9ca18d603f7
name: Malware Activity Detected
description: Detects when restore points marked as suspicious. This might indicate
potential compromise of backup data.
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: |
Veeam_GetSecurityEvents
| where instanceId == 41600
| extend ActivityType = extract("ActivityType=\"([^\"]*)\"", 1, SyslogMessage)
| extend MachineDisplayName = extract("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\s*\\(([^)]+)\\)", 1, Description)
| extend MachineUuid = extract("([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", 1, Description)
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Malware Detection Method"] = ActivityType,
MessageDetails = Description,
Severity = SeverityDescription,
MachineDisplayName,
MachineUuid
version: 1.0.2
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
MachineDisplayName: MachineDisplayName
MachineUuid: MachineUuid
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 41600
Stage 3: extend (3 consecutive steps)
| extend ActivityType = extract("ActivityType=\"([^\"]*)\"", 1, SyslogMessage)
| extend MachineDisplayName = extract("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\s*\\(([^)]+)\\)", 1, Description)
| extend MachineUuid = extract("([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", 1, Description)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Malware Detection Method"] = ActivityType,
MessageDetails = Description,
Severity = SeverityDescription,
MachineDisplayName,
MachineUuid
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"41600" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MachineDisplayName | project |
MachineUuid | project |
Malware Detection Method | project |
MessageDetails | project |
Severity | project |
UserName | project |
Pathlock TDnR - SAP Public Cloud Security Audit Events
#Detects security audit log events from SAP Public Cloud systems, forwarded by Pathlock Threat Detection and Response. Public cloud security audit events capture authentication, authorization, and configuration activity in SAP S/4HANA Cloud and other SAP Public Cloud environments.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c57
name: Pathlock TDnR - SAP Public Cloud Security Audit Events
kind: Scheduled
description: >-
Detects security audit log events from SAP Public Cloud systems, forwarded by Pathlock Threat
Detection and Response. Public cloud security audit events capture authentication, authorization,
and configuration activity in SAP S/4HANA Cloud and other SAP Public Cloud environments.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
Pathlock_TDnR_CL
| where DataSource == "SEC_AUDIT_LOG_CLOUD"
| 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 == "SEC_AUDIT_LOG_CLOUD"
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:"SEC_AUDIT_LOG_CLOUD" |
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 |
PCI App with Open Findings Before Audit Window
#Detects PCI-scoped applications with open Critical/High findings when an upcoming PCI-DSS audit is within 30 days. Correlates PCIApplications and AuditCalendar watchlists to surface compliance risk before audit deadlines.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 0457b577-3864-42c9-9779-1c9441d86429
name: PCI App with Open Findings Before Audit Window
description: |
Detects PCI-scoped applications with open Critical/High findings when an
upcoming PCI-DSS audit is within 30 days. Correlates PCIApplications and
AuditCalendar watchlists to surface compliance risk before audit deadlines.
severity: High
status: Available
requiredDataConnectors:
- connectorId: StratoSecurePush
dataTypes:
- StratoSecure_Findings_CL
queryFrequency: 1d
queryPeriod: 1455m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
query: |
let auditWindowDays = 30;
let PCIApps = _GetWatchlist("PCIApplications")
| project WL_ApplicationId = column_ifexists("ApplicationId", ""),
WL_AppName = column_ifexists("AppName", ""),
WL_AuditDate = column_ifexists("AuditDate", ""),
WL_Owner = column_ifexists("Owner", "");
let AuditCalendar = _GetWatchlist("AuditCalendar")
| where column_ifexists("Framework", "") =~ "PCI-DSS"
| extend UpcomingAudit = todatetime(column_ifexists("AuditDate", ""))
| where UpcomingAudit between (now() .. now() + auditWindowDays * 1d)
| project WL_Cal_ApplicationId = column_ifexists("ApplicationId", ""),
Framework = column_ifexists("Framework", ""), UpcomingAudit;
StratoSecure_Findings_CL
| where FixStatus =~ "open"
| where Severity in ("Critical", "High")
| join kind=inner PCIApps on $left.ApplicationId == $right.WL_ApplicationId
| join kind=inner AuditCalendar on $left.ApplicationId == $right.WL_Cal_ApplicationId
| summarize TotalOpenFindings = count(), MaxSeverityInt = max(SeverityInt),
RuleIds = make_set(RuleId, 20), EndTime = max(TimeGenerated)
by ApplicationId, WL_AppName, WL_Owner, Framework, UpcomingAudit, StraTenantId
| extend MaxSeverity = case(
MaxSeverityInt >= 9, "Critical",
MaxSeverityInt >= 7, "High",
MaxSeverityInt >= 4, "Medium",
"Low")
| project TimeGenerated = EndTime, ApplicationId, AppName = WL_AppName, Owner = WL_Owner,
StraTenantId, Framework, UpcomingAudit, TotalOpenFindings, MaxSeverity, RuleIds
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let auditWindowDays = 30;
Let Binding: PCIApps
let PCIApps = _GetWatchlist("PCIApplications")
| project WL_ApplicationId = column_ifexists("ApplicationId", ""),
WL_AppName = column_ifexists("AppName", ""),
WL_AuditDate = column_ifexists("AuditDate", ""),
WL_Owner = column_ifexists("Owner", "");
Let Binding: AuditCalendar
let AuditCalendar = _GetWatchlist("AuditCalendar")
| where column_ifexists("Framework", "") =~ "PCI-DSS"
| extend UpcomingAudit = todatetime(column_ifexists("AuditDate", ""))
| where UpcomingAudit between (now() .. now() + auditWindowDays * 1d)
| project WL_Cal_ApplicationId = column_ifexists("ApplicationId", ""),
Framework = column_ifexists("Framework", ""), UpcomingAudit;
Stage 1: source
StratoSecure_Findings_CL
Stage 2: where
| where FixStatus =~ "open"
Stage 3: where
| where Severity in ("Critical", "High")
Stage 4: join
| join kind=inner PCIApps on $left.ApplicationId == $right.WL_ApplicationId
Stage 5: join
| join kind=inner AuditCalendar on $left.ApplicationId == $right.WL_Cal_ApplicationId
Stage 6: summarize
summarize EndTime, MaxSeverityInt, RuleIds, TotalOpenFindings by ApplicationId, WL_AppName, WL_Owner, Framework, UpcomingAudit, StraTenantId
Stage 7: extend
extend MaxSeverity
MaxSeverity =MaxSeverityInt >= 9"Critical"MaxSeverityInt >= 7"High"MaxSeverityInt >= 4"Medium""Low"Stage 8: project
project AppName, ApplicationId, Framework, MaxSeverity, Owner, RuleIds, StraTenantId, TimeGenerated, TotalOpenFindings, UpcomingAudit
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FixStatus | eq |
| field:"FixStatus" kind:eq value:"open" |
Framework | eq |
| field:"Framework" kind:eq value:"PCI-DSS" |
Severity | in |
| field:"Severity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppName | project |
ApplicationId | project |
Framework | project |
MaxSeverity | project |
Owner | project |
RuleIds | project |
StraTenantId | project |
TimeGenerated | project |
TotalOpenFindings | project |
UpcomingAudit | project |
Restore Point Marked as Infected
#Detects when a restore point is marked as infected.
Rule Body
id: bad9d4b8-41cb-41ba-82c8-e20e771b3440
name: Restore Point Marked as Infected
description: Detects when a restore point is marked as infected.
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: "Veeam_GetSecurityEvents\n| where instanceId == 42220\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 42220
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"42220" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Server Oriented Cmdlet And User Oriented Cmdlet used
#'Detect if a server oriented cmdlet and a user oriented cmdlet that are monitored are launched by the same user in the same server within a 10 minutes timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Collection | |
| Exfiltration |
Rule Body
id: 7bce901b-9bc8-4948-8dfc-8f68878092d5
name: Server Oriented Cmdlet And User Oriented Cmdlet used
description: |
'Detect if a server oriented cmdlet and a user oriented cmdlet that are monitored are launched by the same user in the same server within a 10 minutes timeframe'
requiredDataConnectors:
- connectorId: ESI-ExchangeAdminAuditLogEvents
dataTypes:
- Event
severity: High
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Exfiltration
- Persistence
- Collection
relevantTechniques:
- T1020
- T1098
- T1114
query: |
let timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;
ExchangeAdminAuditLogs
| where TimeGenerated > ago(2 * timeframe)
| where isempty(UserOriented)
| project serverExecutedTime = TimeGenerated,
ServerCmdlet = CmdletName,
ServerCmdletParams = CmdletParameters,
Computer,
Caller,
ServerCmdletTargetObject = TargetObject
| join kind= inner (
ExchangeAdminAuditLogs
| where TimeGenerated > ago(timeframe)
| where UserOriented =~ 'Yes'
| project userExecutedTime = TimeGenerated,
UserCmdlet = CmdletName,
UserCmdletParams = CmdletParameters,
Computer,
Caller,
UserCmdletTargetObject = TargetObject,
userPrincipalName,
objectGUID,
sAMAccountName,
IsVIP)
on Computer, Caller
| where userExecutedTime - serverExecutedTime < spanoftime
| extend TimeDelta = userExecutedTime - serverExecutedTime
| extend TimeDeltaInverse = serverExecutedTime - userExecutedTime
| where tolong(TimeDelta) >= threshold or tolong(TimeDeltaInverse) >= threshold
entityMappings:
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: userPrincipalName
- identifier: Upn
columnName: userPrincipalName
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: ServerCmdletTargetObject
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Caller
- identifier: ObjectGuid
columnName: objectGUID
version: 1.2.0
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;
Stage 1: source
ExchangeAdminAuditLogs
Stage 2: where
| where TimeGenerated > ago(2 * timeframe)
Stage 3: where
| where isempty(UserOriented)
Stage 4: project
| project serverExecutedTime = TimeGenerated,
ServerCmdlet = CmdletName,
ServerCmdletParams = CmdletParameters,
Computer,
Caller,
ServerCmdletTargetObject = TargetObject
Stage 5: join
| join kind= inner (
ExchangeAdminAuditLogs
| where TimeGenerated > ago(timeframe)
| where UserOriented =~ 'Yes'
| project userExecutedTime = TimeGenerated,
UserCmdlet = CmdletName,
UserCmdletParams = CmdletParameters,
Computer,
Caller,
UserCmdletTargetObject = TargetObject,
userPrincipalName,
objectGUID,
sAMAccountName,
IsVIP)
on Computer, Caller
Stage 6: where userExecutedTime - serverExecutedTime < 10m
where userExecutedTime - serverExecutedTime < 10m
Stage 7: extend
extend TimeDelta
Stage 8: extend
extend TimeDeltaInverse
Stage 9: where
where (tolong(TimeDelta) >= 0 or tolong(TimeDeltaInverse) >= 0)
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | project |
Computer | project |
ServerCmdlet | project |
ServerCmdletParams | project |
ServerCmdletTargetObject | project |
serverExecutedTime | project |
TimeDelta | extend |
TimeDeltaInverse | extend |
SlackAudit - Multiple failed logins for user
#'Identifies multiple failed Slack logins for a user account within a short time window, which may indicate password guessing or brute-force activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 93a91c37-032c-4380-847c-957c001957ad
name: SlackAudit - Multiple failed logins for user
description: |
'Identifies multiple failed Slack logins for a user account within a short time window, which may indicate password
guessing or brute-force activity.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SlackAuditAPI
dataTypes:
- SlackAudit_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 10;
SlackAudit
| where DvcAction in~ ('user_login_failed')
| summarize FailedLogins = count() by SrcUserName, bucket = bin(TimeGenerated, 5m)
| where FailedLogins > threshold
| extend AccountCustomEntity = SrcUserName
| project SrcUserName, bucket, FailedLogins, AccountCustomEntity
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
customDetails:
FailedLogins: FailedLogins
UserAccount: SrcUserName
TimeBucket: bucket
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
SlackAudit
Stage 2: where
| where DvcAction in~ ('user_login_failed')
Stage 3: summarize
| summarize FailedLogins = count() by SrcUserName, bucket = bin(TimeGenerated, 5m)
Stage 4: where
| where FailedLogins > threshold
Stage 5: extend
| extend AccountCustomEntity = SrcUserName
Stage 6: project
| project SrcUserName, bucket, FailedLogins, AccountCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | in |
| field:"DvcAction" kind:in value:"user_login_failed" |
FailedLogins | gt |
| field:"FailedLogins" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | project |
FailedLogins | project |
SrcUserName | project |
bucket | project |
SlackAudit - User role changed to admin or owner
#'This query detects Slack audit events where a user role is changed to admin or owner, indicating potential privilege escalation or persistence activity. It monitors role change actions in Slack audit logs and maps the affected user as the primary account entity for investigation.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: be6c5fc9-2ac3-43e6-8fb0-cb139e04e43e
name: SlackAudit - User role changed to admin or owner
description: |
'This query detects Slack audit events where a user role is changed to admin or owner, indicating potential privilege
escalation or persistence activity. It monitors role change actions in Slack audit logs and maps the affected user as the
primary account entity for investigation.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: SlackAuditAPI
dataTypes:
- SlackAudit_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
query: |
SlackAudit
| where DvcAction in~ ('role_change_to_admin', 'role_change_to_owner')
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: Slack user role changed to {{AccountCustomEntity}}
alertDescriptionFormat: Slack role change action {{DvcAction}} affected user {{AccountCustomEntity}}
customDetails:
AffectedUser: AccountCustomEntity
Action: DvcAction
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SlackAudit
Stage 2: where
| where DvcAction in~ ('role_change_to_admin', 'role_change_to_owner')
Stage 3: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | in |
| field:"DvcAction" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Snowflake - Multiple login failures from single IP
#'Detects Mmltiple login failures from single IP.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: b7d22407-1391-4256-b09a-414a9719443c
name: Snowflake - Multiple login failures from single IP
description: |
'Detects Mmltiple login failures from single IP.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let threshold = 10;
Snowflake
| where EventType =~ 'LOGIN'
| where LoginIsSuccess =~ 'No'
| summarize count() by SrcDvcIpAddr, bin(TimeGenerated, 5m)
| where count_ > threshold
| extend IPCustomEntity = SrcDvcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
Snowflake
Stage 2: where
| where EventType =~ 'LOGIN'
Stage 3: where
| where LoginIsSuccess =~ 'No'
Stage 4: summarize
| summarize count() by SrcDvcIpAddr, bin(TimeGenerated, 5m)
Stage 5: where
| where count_ > threshold
Stage 6: extend
| extend IPCustomEntity = SrcDvcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"LOGIN" |
LoginIsSuccess | eq |
| field:"LoginIsSuccess" kind:eq value:"No" |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcDvcIpAddr | summarize |
IPCustomEntity | extend |
TI Map URL Entity to Syslog Data
#'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in Syslog data.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: b31037ea-6f68-4fbd-bab2-d0d0f44c2fcf
name: TI Map URL Entity to Syslog Data
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in Syslog data.'
severity: Medium
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- 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;
ThreatIntelligenceIndicator
// Picking up only IOC's that contain the entities we want
| where isnotempty(Url)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (
Syslog
| where TimeGenerated >= ago(dt_lookBack)
// Extract URL from the Syslog message but only take messages that include URLs
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,SyslogMessage)
| where isnotempty(Url)
| extend Syslog_TimeGenerated = TimeGenerated
) on Url
| where Syslog_TimeGenerated < ExpirationDateTime
| summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated , *) by IndicatorId, Url
| project timestamp = Syslog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SyslogMessage, Computer, ProcessName, Url, HostIP
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.6
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(Url)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 5: where
| where Active == true and ExpirationDateTime > now()
Stage 6: join
| join kind=innerunique (
Syslog
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,SyslogMessage)
| where isnotempty(Url)
| extend Syslog_TimeGenerated = TimeGenerated
) on Url
Stage 7: where
where Syslog_TimeGenerated < ExpirationDateTime
Stage 8: summarize
summarize Syslog_TimeGenerated by IndicatorId, Url
Stage 9: project
project ActivityGroupNames, Computer, ConfidenceScore, Description, ExpirationDateTime, HostIP, IndicatorId, ProcessName, SyslogMessage, ThreatType, Url, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Url | is_not_null | field:"Url" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Computer | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
HostIP | project |
IndicatorId | project |
ProcessName | project |
SyslogMessage | project |
ThreatType | project |
Url | project |
timestamp | project |