Detection rules › Kusto
Kusto rules: 3j
Alsid privileged accounts issues
#'Searches for triggered Indicators of Exposures related to privileged accounts issues'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: a5fe9489-cf8b-47ae-a87e-8f3a13e4203e
name: Alsid privileged accounts issues
description: |
'Searches for triggered Indicators of Exposures related to privileged accounts issues'
severity: Low
requiredDataConnectors:
- connectorId: AlsidForAD
dataTypes:
- AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
AppServices AV Scan with Infected Files
#'Identifies if an AV scan finds infected files in Azure App Services.'
Rule Body
id: 9d0295ee-cb75-4f2c-9952-e5acfbb67036
name: AppServices AV Scan with Infected Files
description: |
'Identifies if an AV scan finds infected files in Azure App Services.'
severity: Informational
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 1
query: |
let timeframe = ago(1d);
AppServiceAntivirusScanAuditLogs
| where NumberOfInfectedFiles > 0
| extend timestamp = TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: AzureID
columnName: _ResourceId
version: 1.0.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: SecurityJedi
support:
tier: Community
categories:
domains: [ "Security - Others", "Platform" ]
Stages and Predicates
Parameters
let timeframe = ago(1d);
Stage 1: source
AppServiceAntivirusScanAuditLogs
Stage 2: where
| where NumberOfInfectedFiles > 0
Stage 3: extend
| extend timestamp = TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
NumberOfInfectedFiles | gt |
| field:"NumberOfInfectedFiles" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
timestamp | extend |
AV detections related to Zinc actors
#This query looks for Microsoft Defender AV detections related to Zinc threat actor. In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available. Reference: https://www.microsoft.com/security/blog/2022/09/29/zinc-weaponizing-open-source-software/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
Rule Body
id: 3705158d-e008-49c9-92dd-e538e1549090
name: AV detections related to Zinc actors
description: |
'This query looks for Microsoft Defender AV detections related to Zinc threat actor. In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, etc.
This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.
Reference: https://www.microsoft.com/security/blog/2022/09/29/zinc-weaponizing-open-source-software/'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- DeviceInfo
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1486
tags:
- Zinc
query: |
let Zinc_threats = dynamic(["Trojan:Win32/ZetaNile.A", "Trojan:Win32/EventHorizon.A", "Trojan:Win32/FoggyBrass.A", "Trojan:Win32/FoggyBrass.B", "Trojan:Win32/PhantomStar.A","Trojan:Win32/PhantomStar.C","TrojanDropper:Win32/PhantomStar.A"]);
DeviceInfo
| extend DeviceName = tolower(DeviceName)
| join kind=inner ( SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Zinc_threats) or ThreatFamilyName in~ (Zinc_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
) on $left.DeviceName == $right.CompromisedEntity
| summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId , bin(TimeGenerated, 1d), CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
| extend HostName = tostring(split(CompromisedEntity, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(CompromisedEntity, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: PublicIP
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: Zinc_threats
let Zinc_threats = dynamic(["Trojan:Win32/ZetaNile.A", "Trojan:Win32/EventHorizon.A", "Trojan:Win32/FoggyBrass.A", "Trojan:Win32/FoggyBrass.B", "Trojan:Win32/PhantomStar.A","Trojan:Win32/PhantomStar.C","TrojanDropper:Win32/PhantomStar.A"]);
Stage 1: source
DeviceInfo
Stage 2: extend
| extend DeviceName = tolower(DeviceName)
Stage 3: join
| join kind=inner ( SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Zinc_threats) or ThreatFamilyName in~ (Zinc_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
) on $left.DeviceName == $right.CompromisedEntity
Stage 4: summarize
summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
ThreatFamilyName | in |
| field:"ThreatFamilyName" kind:in |
ThreatName | in |
| field:"ThreatName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertSeverity | summarize |
CompromisedEntity | summarize |
Description | summarize |
DeviceId | summarize |
DisplayName | summarize |
Entities | summarize |
ProductName | summarize |
PublicIP | summarize |
TenantId | summarize |
ThreatFamilyName | summarize |
ThreatName | summarize |
DnsDomain | extend |
HostName | extend |
AWSCloudTrail - Privilege escalation with FullAccess managed policy
#Detects successful attachment of AWS managed policies containing FullAccess permissions to users, roles, or groups, excluding admin-named policies. This action can rapidly expand privilege scope and should be reviewed as potential cloud privilege escalation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS IAM CompromisedKeyQuarantine Policy Attached to User (Elastic)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - CloudFormation policy created then used for privilege escalation (Kusto)
- AWSCloudTrail - Created CRUD S3 policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD DynamoDB policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD Lambda policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of DataPipeline policy and then privilege escalation (Kusto)
Rule Body
id: afb4191b-a142-4065-a0da-f721ee3d006c
name: AWSCloudTrail - Privilege escalation with FullAccess managed policy
description: |
Detects successful attachment of AWS managed policies containing FullAccess permissions to users, roles, or
groups, excluding admin-named policies. This action can rapidly expand privilege scope and should be reviewed
as potential cloud privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1098.003
query: |
AWSCloudTrail
| where EventName in ("AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
| where tostring(parse_json(RequestParameters).policyArn) has "FullAccess" and tostring(parse_json(RequestParameters).policyArn) !has "Admin"
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, UserIdentityArn, ResponseElements
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
EventName: EventName
EventSource: EventSource
AWSRegion: AWSRegion
UserIdentityArn: UserIdentityArn
alertDetailsOverride:
alertDisplayNameFormat: 'AWS FullAccess managed policy attachment by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} from {{SourceIpAddress}} attaching FullAccess managed policy in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in ("AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: where
| where tostring(parse_json(RequestParameters).policyArn) has "FullAccess" and tostring(parse_json(RequestParameters).policyArn) !has "Admin"
Stage 4: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 5: project
| project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, UserIdentityArn, ResponseElements
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
RequestParameters.policyArn | match |
| field:"RequestParameters.policyArn" kind:match value:"FullAccess" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RequestParameters.policyArn | match | Admin | excludes:RequestParameters.policyArn field:"RequestParameters.policyArn" value:"Admin" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | project |
AccountName | project |
AccountUPNSuffix | project |
AdditionalEventData | project |
EventName | project |
EventSource | project |
EventTypeName | project |
RecipientAccountId | project |
RequestParameters | project |
ResponseElements | project |
SessionMfaAuthenticated | project |
SourceIpAddress | project |
TimeGenerated | project |
UserAgent | project |
UserIdentityAccountId | project |
UserIdentityArn | project |
UserIdentityPrincipalid | project |
UserIdentityUserName | project |
Cisco SE - Policy update failure
#'Detects policy updates failures.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 64fece0a-44db-4bab-844d-fd503dc0aaba
name: Cisco SE - Policy update failure
description: |
'Detects policy updates failures.'
severity: Medium
requiredDataConnectors:
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
CiscoSecureEndpoint
| where EventMessage =~ 'Policy Update Failure'
| extend HostCustomEntity = DstHostname
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSecureEndpoint
Stage 2: where
| where EventMessage =~ 'Policy Update Failure'
Stage 3: extend
| extend HostCustomEntity = DstHostname
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"Policy Update Failure" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostCustomEntity | extend |
CMMC 2.0 Level 2 (Advanced) Readiness Posture
#'CMMC 2.0 Level 2 (Advanced) assessments have deviated from configured threshold baselines. This alert is triggered when CMMC2.0 policy compliance is assessed below 70% compliance in 7 days.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 7bfe573b-3069-4e81-98fe-9a4cffbcbc24
name: CMMC 2.0 Level 2 (Advanced) Readiness Posture
description: |
'CMMC 2.0 Level 2 (Advanced) assessments have deviated from configured threshold baselines. This alert is triggered when CMMC2.0 policy compliance is assessed below 70% compliance in 7 days.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
SecurityRegulatoryCompliance
| where ComplianceStandard == "NIST-SP-800-171-R2"
| extend Level=iff(ComplianceControl in ("3.1.1","3.1.2","3.1.20","3.1.22","3.4.1","3.5.2","3.5.2","3.8.3","3.13.1","3.13.5","3.14.1","3.14.2","3.14.4","3.14.5"), "Level 1: Foundational","Level 2: Advanced")
| where Level == "Level 2: Advanced"
| summarize arg_max(TimeGenerated, *) by RecommendationName, AssessedResourceId, Level
|summarize Failed=countif(State=="Failed"),Passed=countif(State=="Passed"),Total=countif(State=="Passed" or State == "Failed") by Level
|extend PassedControlsPercentage = (Passed/todouble(Total))*100
| where PassedControlsPercentage < 70
//Adjust Either Passed Thresholds within Organizational Needs
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
| project Level, Total, PassedControlsPercentage, Passed, Failed, RemediationLink, LastObserved=now()
| extend URLCustomEntity = RemediationLink
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityRegulatoryCompliance
Stage 2: where
| where ComplianceStandard == "NIST-SP-800-171-R2"
Stage 3: extend
| extend Level=iff(ComplianceControl in ("3.1.1","3.1.2","3.1.20","3.1.22","3.4.1","3.5.2","3.5.2","3.8.3","3.13.1","3.13.5","3.14.1","3.14.2","3.14.4","3.14.5"), "Level 1: Foundational","Level 2: Advanced")
Level =ComplianceControl in ("3.1.1", "3.1.2", "3.1.20", "3.1.22", "3.4.1", "3.5.2", "3.5.2", "3.8.3", "3.13.1", "3.13.5", "3.14.1", "3.14.2", "3.14.4", "3.14.5")"Level 1: Foundational""Level 2: Advanced"Stage 4: where
| where Level == "Level 2: Advanced"
Stage 5: summarize
| summarize arg_max(TimeGenerated, *) by RecommendationName, AssessedResourceId, Level
Stage 6: summarize
| summarize Failed=countif(State=="Failed"),Passed=countif(State=="Passed"),Total=countif(State=="Passed" or State == "Failed") by Level
Stage 7: extend
| extend PassedControlsPercentage = (Passed/todouble(Total))*100
Stage 8: where
| where PassedControlsPercentage < 70
Stage 9: extend
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
Stage 10: project
| project Level, Total, PassedControlsPercentage, Passed, Failed, RemediationLink, LastObserved=now()
Stage 11: extend
| extend URLCustomEntity = RemediationLink
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ComplianceStandard | eq |
| field:"ComplianceStandard" kind:eq value:"NIST-SP-800-171-R2" |
Level | eq |
| field:"Level" kind:eq value:"Level 2: Advanced" |
PassedControlsPercentage | lt |
| field:"PassedControlsPercentage" kind:lt value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Failed | project |
LastObserved | project |
Level | project |
Passed | project |
PassedControlsPercentage | project |
RemediationLink | project |
Total | project |
URLCustomEntity | extend |
Cross-tenant Access Settings Organization Deleted
#Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when an Organization is deleted from the Microsoft Entra ID Cross-tenant Access Settings.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- Cross-tenant Access Settings Organization Added (Kusto)
- Cross-tenant Access Settings Organization Inbound Collaboration Settings Changed (Kusto)
- Cross-tenant Access Settings Organization Inbound Direct Settings Changed (Kusto)
- Cross-tenant Access Settings Organization Outbound Collaboration Settings Changed (Kusto)
- Cross-tenant Access Settings Organization Outbound Direct Settings Changed (Kusto)
Rule Body
id: eb8a9c1c-f532-4630-817c-1ecd8a60ed80
name: Cross-tenant Access Settings Organization Deleted
description: |
'Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when an Organization is deleted from the Microsoft Entra ID Cross-tenant Access Settings.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Persistence
- Discovery
relevantTechniques:
- T1078.004
- T1136.003
- T1087.004
query: |
AuditLogs
| where OperationName has "Delete partner specific cross-tenant access setting"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Policy"
| extend Properties = TargetResource.modifiedProperties
)
| mv-apply Property = Properties on
(
where Property.displayName =~ "tenantId"
| extend ExtTenantDeleted = trim('"',tostring(Property.oldValue))
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName has "Delete partner specific cross-tenant access setting"
Stage 3: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Policy"
| extend Properties = TargetResource.modifiedProperties
)
Stage 4: kusto:mv-apply
| mv-apply Property = Properties on
(
where Property.displayName =~ "tenantId"
| extend ExtTenantDeleted = trim('"',tostring(Property.oldValue))
)
Stage 5: extend (6 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | match |
| field:"OperationName" kind:match value:"Delete partner specific cross-tenant access setting" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"tenantId" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"Policy" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
CYFIRMA - High severity Trojan 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 KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Stealth | |
| Credential Access | |
| Command & Control |
Rule Body
id: 649f525a-1f92-412d-bfc2-ce642e7a7f1f
name: CYFIRMA - High severity Trojan File Hash Indicators with Block Action Rule
description: |
"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table.
It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values.
The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Trojan 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 (Roles has '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,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
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:
- InitialAccess
- Execution
- Persistence
- DefenseEvasion
- CommandAndControl
- CredentialAccess
relevantTechniques:
- T1566
- T1204
- T1547
- T1027
- T1071
- T1003
- T1566.001
- T1547.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Trojan File Hash Indicators with Block Action Rule - {{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: Value
columnName: MD5
- identifier: Algorithm
columnName: Algo_MD5
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: SHA1
- identifier: Algorithm
columnName: Algo_SHA1
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: SHA256
- identifier: Algorithm
columnName: Algo_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 (Roles has '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,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
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 | match |
| field:"Roles" kind:match value:"Trojan" |
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 |
Darktrace Model Alert
#This query searches for Darktrace model alerts and creates a Microsoft Sentinel alert from each matching event. Edit this analytic rule if you would like it to create Microsoft Sentinel incidents.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Lateral Movement | |
| Command & Control |
Rule Body
id: 9392a06f-63a4-4a5d-8ca3-647064b13c28
name: Darktrace Model Alert
kind: NRT
description: |
This query searches for Darktrace model alerts and creates a Microsoft Sentinel alert
from each matching event. Edit this analytic rule if you would like it to create
Microsoft Sentinel incidents.
severity: High
requiredDataConnectors:
- connectorId: DarktraceActiveAISecurityPlatform
dataTypes:
- DarktraceModelAlerts_CL
tactics:
- InitialAccess
- Execution
- LateralMovement
- CommandAndControl
relevantTechniques:
- T1190
- T1059
- T1021
- T1071
query: |
DarktraceModelAlerts_CL
| where TimeGenerated >= ago(5m)
| extend SentinelSeverity = case(
compliance == true, "Informational",
category == "Informational", "Low",
category == "Suspicious", "Medium",
category == "Critical", "High",
"Informational")
| extend ProviderName = "Darktrace"
| mv-apply item = mitreTechniques on (
extend techniqueId = tostring(item.techniqueId)
| summarize techniqueIdArray = make_list(techniqueId, 5)
)
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: accountName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: sourceIp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: destIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: destHost
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: sourceHost
customDetails:
DeviceHostname: deviceHostname
DeviceCredentials: deviceCredentials
Compliance: compliance
Score: score
CustomLabel: customLabel
Category: category
alertDetailsOverride:
alertDisplayNameFormat: 'Darktrace Model Alert: {{modelName}} '
alertDescriptionFormat: '{{message}}'
alertSeverityColumnName: SentinelSeverity
alertDynamicProperties:
- alertProperty: AlertLink
value: alertUrl
- alertProperty: ProductName
value: darktraceProduct
- alertProperty: ProviderName
value: ProviderName
- alertProperty: Techniques
value: techniqueIdArray
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.1.0
Stages and Predicates
Stage 1: source
DarktraceModelAlerts_CL
Stage 2: where
| where TimeGenerated >= ago(5m)
Stage 3: extend
| extend SentinelSeverity = case(
compliance == true, "Informational",
category == "Informational", "Low",
category == "Suspicious", "Medium",
category == "Critical", "High",
"Informational")
SentinelSeverity =compliance == true"Informational"category == "Informational""Low"category == "Suspicious""Medium"category == "Critical""High""Informational"Stage 4: extend
| extend ProviderName = "Darktrace"
Stage 5: kusto:mv-apply
| mv-apply item = mitreTechniques on (
extend techniqueId = tostring(item.techniqueId)
| summarize techniqueIdArray = make_list(techniqueId, 5)
)
Stage 6: summarize aggregation inside the mv-apply branch
summarize
Detect excessive NXDOMAIN DNS queries - Anomaly based (ASIM DNS Solution)
#This rule makes use of the series decompose anomaly method to generate an alert when client requests excessive amount of DNS queries to non-existent domains. This helps in identifying possible C2 communications. It utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 22: DNSEvent (DNS query) |
Rule Body
id: 02f23312-1a33-4390-8b80-f7cd4df4dea0
name: Detect excessive NXDOMAIN DNS queries - Anomaly based (ASIM DNS Solution)
description: |
'This rule makes use of the series decompose anomaly method to generate an alert when client requests excessive amount of DNS queries to non-existent domains. This helps in identifying possible C2 communications. It utilizes [ASIM](https://aka.ms/AboutASIM) normalization and is applied to any source that supports the ASIM DNS schema.'
severity: Medium
status: Available
tags:
- Schema: ASimDns
SchemaVersion: 0.1.6
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1568
- T1008
query: |
let threshold = 2.5;
let min_t = ago(14d);
let max_t = now();
let dt = 1d;
let summarizationexist = (
union isfuzzy=true
(
DNS_Summarized_Logs_ip_CL
| where EventTime_t > ago(1d)
| project v = int(2)
),
(
print int(1)
| project v = print_0
)
| summarize maxv = max(v)
| extend sumexist = (maxv > 1)
);
let allData = union isfuzzy=true
(
(datatable(exists: int, sumexist: bool)[1, false]
| join (summarizationexist) on sumexist)
| join (
_Im_Dns(responsecodename='NXDOMAIN', starttime=todatetime(min_t), endtime=max_t)
| summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1)
)
on exists
| project-away exists, maxv, sum*
),
(
DNS_Summarized_Logs_ip_CL
| where EventTime_t > min_t and EventResultDetails_s == 'NXDOMAIN'
| summarize Count=toint(sum(count__d)) by SrcIpAddr=SrcIpAddr_s, bin(EventTime=EventTime_t, 1h)
);
allData
| make-series EventCount=sum(Count) on EventTime from min_t to max_t step dt by SrcIpAddr
| extend (anomalies, score, baseline) = series_decompose_anomalies(EventCount, threshold, -1, 'linefit')
| mv-expand anomalies, score, baseline, EventTime, EventCount
| extend
anomalies = toint(anomalies),
score = toint(score),
baseline = toint(baseline),
EventTime = todatetime(EventTime),
Total = tolong(EventCount)
| where EventTime >= ago(dt)
| where score >= threshold * 2
| join kind=inner(_Im_Dns(responsecodename='NXDOMAIN', starttime=ago(dt), endtime=max_t)
| summarize DNSQueries = make_set(DnsQuery) by SrcIpAddr)
on SrcIpAddr
| project-away SrcIpAddr1
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
DNSQueries: DNSQueries
AnomalyScore: score
baseline: baseline
Total: Total
alertDetailsOverride:
alertDisplayNameFormat: "[Anomaly] Excessive NXDOMAIN DNS Queries has been detected from client IP: '{{SrcIpAddr}}'"
alertDescriptionFormat: "This client is generating excessive amount of DNS queries for non-existent domains. This can be an indication of possible C2 communications.\n\nBaseline for 'NXDOMAIN' error count for this client: '{{baseline}}'\n\nCurrent 'NXDOMAIN' error count for this client: '{{Total}}'\n\nDNS queries requested by the client include:\n\n'{{DNSQueries}}'"
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 2.5;
let min_t = ago(14d);
let max_t = now();
let dt = 1d;
Let Binding: summarizationexist
let summarizationexist = (
union isfuzzy=true
(
DNS_Summarized_Logs_ip_CL
| where EventTime_t > ago(1d)
| project v = int(2)
),
(
print int(1)
| project v = print_0
)
| summarize maxv = max(v)
| extend sumexist = (maxv > 1)
);
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: datatable(exists:, DNS_Summarized_Logs_ip_CL
Leg 1: datatable(exists:
(datatable(exists: int, sumexist: bool)[1, false]
| join (summarizationexist) on sumexist)
| join (
_Im_Dns(responsecodename='NXDOMAIN', starttime=todatetime(min_t), endtime=max_t)
| summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1)
)
on exists
| project-away exists, maxv, sum*
Leg 2: DNS_Summarized_Logs_ip_CL
DNS_Summarized_Logs_ip_CL
| where EventTime_t > min_t and EventResultDetails_s == 'NXDOMAIN'
| summarize Count=toint(sum(count__d)) by SrcIpAddr=SrcIpAddr_s, bin(EventTime=EventTime_t, 1h)
Applied to the Combined Result
| make-series EventCount=sum(Count) on EventTime from min_t to max_t step dt by SrcIpAddr | extend (anomalies, score, baseline) = series_decompose_anomalies(EventCount, threshold, -1, 'linefit') | mv-expand anomalies, score, baseline, EventTime, EventCount | extend
anomalies = toint(anomalies),
score = toint(score),
baseline = toint(baseline),
EventTime = todatetime(EventTime),
Total = tolong(EventCount) | where EventTime >= ago(dt) | where score >= threshold * 2 | join kind=inner(_Im_Dns(responsecodename='NXDOMAIN', starttime=ago(dt), endtime=max_t)
| summarize DNSQueries = make_set(DnsQuery) by SrcIpAddr)
on SrcIpAddr | project-away SrcIpAddr1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventResultDetails_s | eq |
| field:"EventResultDetails_s" kind:eq value:"NXDOMAIN" |
score | ge |
| field:"score" kind:ge value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCount | summarize |
SrcIpAddr | summarize |
anomalies | extend |
baseline | extend |
score | extend |
EventTime | extend |
Total | extend |
Google SecOps - GCTI Threat Intelligence Finding
#Creates incidents in Microsoft Sentinel when Google Security Operations raises an active threat intelligence alert (GCTI_FINDING). These alerts are generated by Google's global threat intel corpus and represent high-confidence threats, distinct from customer-authored rule detections.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control | |
| Exfiltration |
Rule Body
id: b3e7f921-5c4a-4d8e-a2f9-7b1d3e6c9a5f
name: Google SecOps - GCTI Threat Intelligence Finding
description: |
Creates incidents in Microsoft Sentinel when Google Security Operations raises an active threat intelligence alert (GCTI_FINDING). These alerts are generated by Google's global threat intel corpus and represent high-confidence threats, distinct from customer-authored rule detections.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GSDetectionAlerts
dataTypes:
- DetectionAlerts_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1078
- T1566
- T1071
- T1048
query: |
GoogleSecOpsDetectionAlerts
| where detectionType == "GCTI_FINDING"
| where alertState == "ALERTING"
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: varPrincipalIp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: varTargetIp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: varSourceIp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: varCorrelationIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: urlBackToProduct
customDetails:
RuleName: ruleName
RuleId: ruleId
Severity: severity
RiskScore: riskScore
DetectionType: detectionType
DetectionTime: detectionTime
Description: description
PrincipalIP: varPrincipalIp
TargetIP: varTargetIp
SourceIP: varSourceIp
CorrelationIP: varCorrelationIp
PrincipalHostname: varPrincipalHostname
TargetHostname: varTargetHostname
SourceHostname: varSourceHostname
PrincipalUser: varPrincipalUserUserid
SourceUser: varSourceUserUserid
TargetUser: varTargetUserUserid
alert_identifier: id
alertDetailsOverride:
alertDisplayNameFormat: "GCTI Finding: {{ruleName}} : {{id}}"
alertDescriptionFormat: "Google Threat Intelligence confirmed finding. Rule: {{ruleName}}. {{description}}"
eventGroupingSettings:
aggregationKind: AlertPerResult
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: Selected
groupByCustomDetails:
- alert_identifier
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GoogleSecOpsDetectionAlerts
Stage 2: where
| where detectionType == "GCTI_FINDING"
Stage 3: where
| where alertState == "ALERTING"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
alertState | eq |
| field:"alertState" kind:eq value:"ALERTING" |
detectionType | eq |
| field:"detectionType" kind:eq value:"GCTI_FINDING" |
Guardian- Token Limit Policy Violation Detection
#'This alert creates an incident when Token Limit Policy Violation detected from the Guardian.'
Rule Body
id: b7798389-6823-4af0-94e6-6135c4f8264e
name: Guardian- Token Limit Policy Violation Detection
description: |
'This alert creates an incident when Token Limit Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Token Limit'
| where Severity =~ 'Informational'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Token Limit Policy Violation detection
alertDescriptionFormat: |
This query detects Token Limit Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Token Limit'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Token Limit" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
Guest accounts added in Entra ID Groups other than the ones specified
#Guest Accounts are added in the Organization Tenants to perform various tasks i.e projects execution, support etc.. This detection notifies when guest users are added to Microsoft Entra ID Groups other than the ones specified and poses a risk to gain access to sensitive apps or data.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add member to group |
| Azure | Add owner to group |
Rules Detecting the Same Action
These rules filter on the same operation.
- Conditional Access - Dynamic Group Exclusion Changes (Kusto)
- Detect suspicious conditional access policy modifications (Kusto)
- Detecting Impossible travel with mailbox permission tampering & Privilege Escalation attempt (Kusto)
- Entra ID Successful Group Deletion (YARA-L)
- User Removed From Group With CA Policy Modification Access (Sigma)
Rule Body
id: 6ab1f7b2-61b8-442f-bc81-96afe7ad8c53
name: Guest accounts added in Entra ID Groups other than the ones specified
description: |
'Guest Accounts are added in the Organization Tenants to perform various tasks i.e projects execution, support etc.. This detection notifies when guest users are added to Microsoft Entra ID Groups other than the ones specified and poses a risk to gain access to sensitive apps or data.'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Persistence
- Discovery
relevantTechniques:
- T1078.004
- T1136.003
- T1087.004
query: |
// OBJECT ID of AAD Groups can be found by navigating to Azure Active Directory then from menu on the left, select Groups and from the list shown of AAD Groups, the Second Column shows the ObjectID of each
let GroupIDs = dynamic(["List with Custom AAD GROUP OBJECT ID 1","Custom AAD GROUP OBJECT ID 2"]);
AuditLogs
| where OperationName in ('Add member to group', 'Add owner to group')
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
// Uncomment the following line to filter events where the inviting user was a guest user
//| where InitiatedBy has_any ("CUSTOM DOMAIN NAME#", "#EXT#")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend InvitedUserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName)),
Properties = TargetResource.modifiedProperties
)
| mv-apply Property = Properties on
(
where Property.displayName =~ "Group.DisplayName"
| extend AADGroup = trim('"',tostring(Property.newValue))
)
| where InvitedUserPrincipalName has_any ("CUSTOM DOMAIN NAME#", "#EXT#")
| mv-apply Property = Properties on
(
where Property.displayName =~ "Group.ObjectID"
| extend AADGroupId = trim('"',tostring(Property.newValue))
)
| project-away TargetResource, Property
| where AADGroupId !in (GroupIDs)
| extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
| extend InvitedUserName = tostring(split(InvitedUserPrincipalName,'@',0)[0]), InvitedUPNSuffix = tostring(split(InvitedUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InvitedUserPrincipalName
- identifier: Name
columnName: InvitedUserName
- identifier: UPNSuffix
columnName: InvitedUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: AADGroup
- identifier: ObjectGuid
columnName: AADGroupId
version: 1.0.6
kind: Scheduled
Stages and Predicates
Parameters
let GroupIDs = dynamic(["List with Custom AAD GROUP OBJECT ID 1","Custom AAD GROUP OBJECT ID 2"]);
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName in ('Add member to group', 'Add owner to group')
Stage 3: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 4: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend InvitedUserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName)),
Properties = TargetResource.modifiedProperties
)
Stage 5: kusto:mv-apply
| mv-apply Property = Properties on
(
where Property.displayName =~ "Group.DisplayName"
| extend AADGroup = trim('"',tostring(Property.newValue))
)
Stage 6: where
| where InvitedUserPrincipalName has_any ("CUSTOM DOMAIN NAME#", "#EXT#")
Stage 7: kusto:mv-apply
| mv-apply Property = Properties on
(
where Property.displayName =~ "Group.ObjectID"
| extend AADGroupId = trim('"',tostring(Property.newValue))
)
Stage 8: project-away
| project-away TargetResource, Property
Stage 9: where
| where AADGroupId !in (GroupIDs)
Stage 10: extend
| extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Stage 11: extend
| extend InvitedUserName = tostring(split(InvitedUserPrincipalName,'@',0)[0]), InvitedUPNSuffix = tostring(split(InvitedUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InvitedUserPrincipalName | match |
| field:"InvitedUserPrincipalName" kind:match |
OperationName | in |
| field:"OperationName" kind:in |
Property.displayName | eq |
| field:"Property.displayName" kind:eq |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AADGroupId | in | Custom AAD GROUP OBJECT ID 2, List with Custom AAD GROUP OBJECT ID 1 | excludes:AADGroupId field:"AADGroupId" value:"Custom AAD GROUP OBJECT ID 2" field:"AADGroupId" value:"List with Custom AAD GROUP OBJECT ID 1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
Name | extend |
UPNSuffix | extend |
InvitedUPNSuffix | extend |
InvitedUserName | extend |
GWorkspace - Two-step authentification disabled for a user
#Triggers on two-step authentification disabled for a user.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Google Workspace | TURN_OFF_2_STEP_VERIFICATION: Turn off 2-step verification |
Rule Body
id: c8cc02d0-5da6-11ec-bf63-0242ac130002
name: GWorkspace - Two-step authentification disabled for a user
description: |
'Triggers on two-step authentification disabled for a user.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: GoogleWorkspaceReportsAPI
dataTypes:
- GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1111
query: |
GWorkspaceActivityReports
| where EventMessage =~ 'TURN_OFF_2_STEP_VERIFICATION'
| extend AccountCustomEntity = ActorEmail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GWorkspaceActivityReports
Stage 2: where
| where EventMessage =~ 'TURN_OFF_2_STEP_VERIFICATION'
Stage 3: extend
| extend AccountCustomEntity = ActorEmail
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"TURN_OFF_2_STEP_VERIFICATION" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Multi-Factor Authentication Disabled for a User
#Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Telemetry Coverage
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)
- ASL AWS Multi-Factor Authentication Disabled (Splunk)
- Authentication Method Changed for Privileged Account (Kusto)
Rule Body
id: 65c78944-930b-4cae-bd79-c3664ae30ba7
name: Multi-Factor Authentication Disabled for a User
description: |
'Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Persistence
relevantTechniques:
- T1098
- T1556
query: |
(union isfuzzy=true
(AuditLogs
| where OperationName =~ "Disable Strong Authentication"
| extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
| extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
| extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
| extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
| extend Targetprop = todynamic(TargetResources)
| extend TargetUser = tostring(Targetprop[0].userPrincipalName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type
),
(AWSCloudTrail
| where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
| extend _parsedRequestParameters = parse_json(RequestParameters)
| extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
| extend TargetUser = tostring(_parsedRequestParameters.userName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress
)
)
| extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: AuditLogs, AWSCloudTrail
Leg 1: AuditLogs
AuditLogs
| where OperationName =~ "Disable Strong Authentication"
| extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
| extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
| extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
| extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
| extend Targetprop = todynamic(TargetResources)
| extend TargetUser = tostring(Targetprop[0].userPrincipalName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type
Leg 2: AWSCloudTrail
AWSCloudTrail
| where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
| extend _parsedRequestParameters = parse_json(RequestParameters)
| extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
| extend TargetUser = tostring(_parsedRequestParameters.userName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress
Applied to the Combined Result
| extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | in |
| field:"aws::eventName" kind:in |
OperationName | eq |
| field:"OperationName" kind:eq value:"Disable Strong Authentication" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTimeUtc | summarize |
IPAddress | summarize |
Operation | summarize |
Source | summarize |
StartTimeUtc | summarize |
TenantorInstance_Detail | summarize |
User | summarize |
UPNSuffix | extend |
UserName | extend |
timestamp | extend |
Netskope - DLP Incident Spike
#Detects a spike in Netskope DLP incidents within a short window. A sudden increase in DLP violations for a single user or DLP profile can indicate active data exfiltration, a misconfigured policy, or bulk handling of sensitive data. Triggers when a user generates more DLP incidents in the last hour than a configurable threshold.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rule Body
id: c3f8e4d6-0d57-4a3b-9e2c-4f6a8b0d3e52
name: Netskope - DLP Incident Spike
description: |
Detects a spike in Netskope DLP incidents within a short window. A sudden increase in
DLP violations for a single user or DLP profile can indicate active data exfiltration,
a misconfigured policy, or bulk handling of sensitive data. Triggers when a user
generates more DLP incidents in the last hour than a configurable threshold.
severity: High
status: Available
requiredDataConnectors:
- connectorId: NetskopeAlertEventsConnector
dataTypes:
- NetskopeAlertEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- Collection
relevantTechniques:
- T1567
- T1530
query: |
let dlpIncidentThreshold = 10;
NetskopeAlertEvents_CL
| where TimeGenerated > ago(1h)
| where AlertType =~ "DLP" or isnotempty(DlpProfile) or isnotempty(DlpIncidentId)
| summarize
IncidentCount = count(),
DistinctIncidents = dcount(DlpIncidentId),
Profiles = make_set(DlpProfile, 20),
Rules = make_set(DlpRule, 20),
Applications = make_set(App, 20),
Files = make_set(DlpFile, 20),
Activities = make_set(Activity, 10),
Actions = make_set(Action, 10),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by User, Userip, Hostname
| where IncidentCount > dlpIncidentThreshold
| order by IncidentCount desc
| project
LastSeen,
User,
Userip,
Hostname,
IncidentCount,
DistinctIncidents,
Profiles,
Rules,
Applications,
Files,
Activities,
Actions,
FirstSeen
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Userip
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let dlpIncidentThreshold = 10;
Stage 1: source
NetskopeAlertEvents_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where AlertType =~ "DLP" or isnotempty(DlpProfile) or isnotempty(DlpIncidentId)
Stage 4: summarize
| summarize
IncidentCount = count(),
DistinctIncidents = dcount(DlpIncidentId),
Profiles = make_set(DlpProfile, 20),
Rules = make_set(DlpRule, 20),
Applications = make_set(App, 20),
Files = make_set(DlpFile, 20),
Activities = make_set(Activity, 10),
Actions = make_set(Action, 10),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by User, Userip, Hostname
Stage 5: where
| where IncidentCount > dlpIncidentThreshold
Stage 6: sort
| order by IncidentCount desc
Stage 7: project
| project
LastSeen,
User,
Userip,
Hostname,
IncidentCount,
DistinctIncidents,
Profiles,
Rules,
Applications,
Files,
Activities,
Actions,
FirstSeen
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertType | eq |
| field:"AlertType" kind:eq value:"DLP" |
DlpIncidentId | is_not_null | field:"DlpIncidentId" kind:is_not_null | |
DlpProfile | is_not_null | field:"DlpProfile" kind:is_not_null | |
IncidentCount | gt |
| field:"IncidentCount" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actions | project |
Activities | project |
Applications | project |
DistinctIncidents | project |
Files | project |
FirstSeen | project |
Hostname | project |
IncidentCount | project |
LastSeen | project |
Profiles | project |
Rules | project |
User | project |
Userip | project |
Netskope - Repeated or Critical Policy Violations
#Detects users with repeated policy violations or critical policy blocks. Monitors policy enforcement effectiveness and compliance.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Exfiltration |
Rule Body
id: dacab67e-fcf3-41c6-a191-579c7be1814d
name: Netskope - Repeated or Critical Policy Violations
description: |
Detects users with repeated policy violations or critical policy blocks. Monitors policy enforcement effectiveness and compliance.
severity: High
status: Available
requiredDataConnectors:
- connectorId: NetskopeWebTxConnector
dataTypes:
- NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Exfiltration
relevantTechniques:
- T1562
- T1048
query: |
let violationThreshold = 5;
NetskopeWebTransactions_CL
| where TimeGenerated > ago(1h)
| where isnotempty(CsUsername)
| where isnotempty(XPolicyAction) and isnotempty(XPolicyName)
| where XPolicyAction =~ 'block' or XPolicyAction =~ 'alert'
| summarize
ViolationCount = count(),
Policies = make_set(XPolicyName),
PolicyActions = make_set(XPolicyAction),
Apps = make_set(XCsApp),
Hosts = make_set(CsHost),
Activities = make_set(XCsAppActivity),
BlockCount = countif(XPolicyAction =~ 'block'),
AlertCount = countif(XPolicyAction =~ 'alert'),
FirstViolation = min(TimeGenerated),
LastViolation = max(TimeGenerated)
by CsUsername, XCCountry, XCDevice
| where ViolationCount >= violationThreshold or BlockCount > 0
| extend ViolationSeverity = case(
BlockCount > 10, 'Critical',
BlockCount > 5, 'High',
ViolationCount > 20, 'High',
ViolationCount > 10, 'Medium',
'Low')
| project
TimeGenerated = LastViolation,
User = CsUsername,
TotalViolations = ViolationCount,
BlockedAttempts = BlockCount,
AlertedActions = AlertCount,
ViolatedPolicies = Policies,
PolicyActions,
ApplicationsInvolved = Apps,
TargetHosts = Hosts,
Activities,
Country = XCCountry,
Device = XCDevice,
ViolationSeverity,
FirstOccurrence = FirstViolation,
LastOccurrence = LastViolation
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let violationThreshold = 5;
Stage 1: source
NetskopeWebTransactions_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where isnotempty(CsUsername)
Stage 4: where
| where isnotempty(XPolicyAction) and isnotempty(XPolicyName)
Stage 5: where
| where XPolicyAction =~ 'block' or XPolicyAction =~ 'alert'
Stage 6: summarize
| summarize
ViolationCount = count(),
Policies = make_set(XPolicyName),
PolicyActions = make_set(XPolicyAction),
Apps = make_set(XCsApp),
Hosts = make_set(CsHost),
Activities = make_set(XCsAppActivity),
BlockCount = countif(XPolicyAction =~ 'block'),
AlertCount = countif(XPolicyAction =~ 'alert'),
FirstViolation = min(TimeGenerated),
LastViolation = max(TimeGenerated)
by CsUsername, XCCountry, XCDevice
Stage 7: where
| where ViolationCount >= violationThreshold or BlockCount > 0
Stage 8: extend
| extend ViolationSeverity = case(
BlockCount > 10, 'Critical',
BlockCount > 5, 'High',
ViolationCount > 20, 'High',
ViolationCount > 10, 'Medium',
'Low')
ViolationSeverity =BlockCount > 10'Critical'BlockCount > 5'High'ViolationCount > 20'High'ViolationCount > 10'Medium''Low'Stage 9: project
| project
TimeGenerated = LastViolation,
User = CsUsername,
TotalViolations = ViolationCount,
BlockedAttempts = BlockCount,
AlertedActions = AlertCount,
ViolatedPolicies = Policies,
PolicyActions,
ApplicationsInvolved = Apps,
TargetHosts = Hosts,
Activities,
Country = XCCountry,
Device = XCDevice,
ViolationSeverity,
FirstOccurrence = FirstViolation,
LastOccurrence = LastViolation
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
BlockCount | gt |
| field:"BlockCount" kind:gt value:"0" |
CsUsername | is_not_null | field:"CsUsername" kind:is_not_null | |
ViolationCount | ge |
| field:"ViolationCount" kind:ge value:"5" |
XPolicyAction | eq |
| field:"XPolicyAction" kind:eq |
XPolicyAction | is_not_null | field:"XPolicyAction" kind:is_not_null | |
XPolicyName | is_not_null | field:"XPolicyName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activities | project |
AlertedActions | project |
ApplicationsInvolved | project |
BlockedAttempts | project |
Country | project |
Device | project |
FirstOccurrence | project |
LastOccurrence | project |
PolicyActions | project |
TargetHosts | project |
TimeGenerated | project |
TotalViolations | project |
User | project |
ViolatedPolicies | project |
ViolationSeverity | project |
New CloudShell User
#Identifies when a user creates an Azure CloudShell for the first time. Monitor this activity to ensure only the expected users are using CloudShell.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
Rule Body
id: 6d7214d9-4a28-44df-aafb-0910b9e6ae3e
name: New CloudShell User
description: |
'Identifies when a user creates an Azure CloudShell for the first time.
Monitor this activity to ensure only the expected users are using CloudShell.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
query: |
let match_window = 3m;
AzureActivity
| where ResourceGroup has "cloud-shell"
| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/listKeys/action")
| where ActivityStatusValue =~ "Success"
| extend TimeKey = bin(TimeGenerated, match_window), AzureIP = CallerIpAddress
| join kind = inner
(AzureActivity
| where ResourceGroup has "cloud-shell"
| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/write")
| extend TimeKey = bin(TimeGenerated, match_window), UserIP = CallerIpAddress
) on Caller, TimeKey
| summarize count() by TimeKey, Caller, ResourceGroup, SubscriptionId, TenantId, AzureIP, UserIP, HTTPRequest, Type, Properties, CategoryValue, OperationList = strcat(OperationNameValue, ' , ', OperationNameValue1)
| extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: UserIP
version: 2.0.3
kind: Scheduled
Stages and Predicates
Parameters
let match_window = 3m;
Stage 1: source
AzureActivity
Stage 2: where
| where ResourceGroup has "cloud-shell"
Stage 3: where
| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/listKeys/action")
Stage 4: where
| where ActivityStatusValue =~ "Success"
Stage 5: extend
| extend TimeKey = bin(TimeGenerated, match_window), AzureIP = CallerIpAddress
Stage 6: join
| join kind = inner
(AzureActivity
| where ResourceGroup has "cloud-shell"
| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/write")
| extend TimeKey = bin(TimeGenerated, match_window), UserIP = CallerIpAddress
) on Caller, TimeKey
Stage 7: summarize
summarize OperationList by TimeKey, Caller, ResourceGroup, SubscriptionId, TenantId, AzureIP, UserIP, HTTPRequest, Type, Properties, CategoryValue, OperationList
Stage 8: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityStatusValue | eq |
| field:"ActivityStatusValue" kind:eq value:"Success" |
OperationNameValue | eq |
| field:"azure_ad::operation_name_value" kind:eq |
ResourceGroup | match |
| field:"ResourceGroup" kind:match value:"cloud-shell" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AzureIP | summarize |
Caller | summarize |
CategoryValue | summarize |
HTTPRequest | summarize |
OperationList | summarize |
Properties | summarize |
ResourceGroup | summarize |
SubscriptionId | summarize |
TenantId | summarize |
TimeKey | summarize |
Type | summarize |
UserIP | summarize |
Name | extend |
UPNSuffix | extend |
Oracle - Command in URI
#'Detects command in URI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 6ae36a5e-573f-11ec-bf63-0242ac130002
name: Oracle - Command in URI
description: |
'Detects command in URI'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- OracleWebLogicServer_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
OracleWebLogicServerEvent
| where UrlOriginal has_any ('cat%20/etc/passwd', '/etc/passwd', 'ping -i', '/usr/bin/id(', '%2f%75%73%72%2f%62%69%6e%2f%69%64', 'phpinfo()' , '%70%68%70%69%6e%66%6f%28%29', ';id', '%3b%69%64', '/bin/bash -c', '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27', '/bin/bash', '%2f%62%69%6e%2f%62%61%73%68', 'sleep(', '%73%6c%65%65%70%28', 'curl', '%63%75%72%6c', '&dir', '%26%64%69%72', '& dir', '%26%20%64%69%72', '<script>', '%3c%73%63%72%69%70%74%3e', 'eval(', '%65%76%61%6c%28', 'exec(', '%65%78%65%63%28' , 'whoami', '%77%68%6f%61%6d%69', 'wget', 'python', 'gcc' , 'uname', 'systeminfo', '%77%67%65%74', '%70%79%74%68%6f%6e', '%75%6e%61%6d%65', '%73%79%73%74%65%6d%69%6e%66%6f')
| extend UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
OracleWebLogicServerEvent
Stage 2: where
| where UrlOriginal has_any ('cat%20/etc/passwd', '/etc/passwd', 'ping -i', '/usr/bin/id(', '%2f%75%73%72%2f%62%69%6e%2f%69%64', 'phpinfo()' , '%70%68%70%69%6e%66%6f%28%29', ';id', '%3b%69%64', '/bin/bash -c', '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27', '/bin/bash', '%2f%62%69%6e%2f%62%61%73%68', 'sleep(', '%73%6c%65%65%70%28', 'curl', '%63%75%72%6c', '&dir', '%26%64%69%72', '& dir', '%26%20%64%69%72', '<script>', '%3c%73%63%72%69%70%74%3e', 'eval(', '%65%76%61%6c%28', 'exec(', '%65%78%65%63%28' , 'whoami', '%77%68%6f%61%6d%69', 'wget', 'python', 'gcc' , 'uname', 'systeminfo', '%77%67%65%74', '%70%79%74%68%6f%6e', '%75%6e%61%6d%65', '%73%79%73%74%65%6d%69%6e%66%6f')
Stage 3: extend
| extend UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UrlOriginal | match |
| field:"UrlOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UrlCustomEntity | extend |
Pathlock TDnR - SAP Instance Profile Changes
#Detects changes to SAP instance profile parameters (RZ10), forwarded by Pathlock Threat Detection and Response. Instance profile modifications can alter security-critical parameters such as login/password_max_new_valid, auth/rfc_authority_check, or rec/client, potentially weakening system security posture.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c64
name: Pathlock TDnR - SAP Instance Profile Changes
kind: Scheduled
description: >-
Detects changes to SAP instance profile parameters (RZ10), forwarded by Pathlock Threat Detection
and Response. Instance profile modifications can alter security-critical parameters such as
login/password_max_new_valid, auth/rfc_authority_check, or rec/client, potentially weakening
system security posture.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1562
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "SYSPROFILE_CHANGES"
| 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 == "SYSPROFILE_CHANGES"
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:"SYSPROFILE_CHANGES" |
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 |
Red Canary Threat Detection
#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 Incidents using detection data assembled by Red Canary.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: 6d263abb-6445-45cc-93e9-c593d3d77b89
kind: Scheduled
name: Red Canary Threat Detection
description: Triggers Incidents using detection data assembled by Red Canary.
severity: High
requiredDataConnectors:
- connectorId: RedCanary_ConnectorDefinition
dataTypes:
- RedCanaryDetections_CL
queryPeriod: 1d
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- CommandAndControl
- CredentialAccess
- DefenseEvasion
- Discovery
- Execution
- Exfiltration
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1566
- T1059
- T1547
- T1548
- T1562
- T1003
- T1087
- T1021
- T1119
- T1071
- T1041
- T1499
query: |
let detectionLookback = 1d;
let newDetectionWindow = 5m;
RedCanaryDetections_CL
| where TimeGenerated >= ago(detectionLookback)
| where isnotempty(detection_id_s)
| summarize FirstSeen = min(TimeGenerated), arg_max(TimeGenerated, *) by detection_id_s
| where FirstSeen >= ago(newDetectionWindow)
| extend process_iocs_raw = column_ifexists('process_iocs_s', '[]'),
child_process_iocs_raw = column_ifexists('child_process_iocs_s', '[]'),
cross_process_iocs_raw = column_ifexists('cross_process_iocs_s', '[]'),
file_modification_iocs_raw = column_ifexists('file_modification_iocs_s', '[]'),
identities_raw = column_ifexists('identities_s', '[]')
| extend process_ioc_array = todynamic(iff(isempty(process_iocs_raw), '[]', process_iocs_raw)),
child_process_ioc_array = todynamic(iff(isempty(child_process_iocs_raw), '[]', child_process_iocs_raw)),
cross_process_ioc_array = todynamic(iff(isempty(cross_process_iocs_raw), '[]', cross_process_iocs_raw)),
file_mod_ioc_array = todynamic(iff(isempty(file_modification_iocs_raw), '[]', file_modification_iocs_raw)),
identities_array = todynamic(iff(isempty(identities_raw), '[]', identities_raw))
| extend evidence_array = array_concat(process_ioc_array, child_process_ioc_array, cross_process_ioc_array, file_mod_ioc_array)
| extend evidence = todynamic(evidence_array[0]),
identity = todynamic(identities_array[0])
| extend file_hash_array = todynamic(evidence.file_hashes)
| extend file_hash = todynamic(file_hash_array[0])
| project detection_id_s,
detection_url_s = column_ifexists('detection_url_s', ''),
detection_headline_s = column_ifexists('detection_headline_s', ''),
detection_details_s = column_ifexists('detection_details_s', ''),
detection_severity_s = column_ifexists('detection_severity_s', ''),
host_name_s = column_ifexists('host_name_s', ''),
host_full_name_s = column_ifexists('host_full_name_s', ''),
host_os_family_s = column_ifexists('host_os_family_s', ''),
host_os_version_s = column_ifexists('host_os_version_s', ''),
tactics_s = column_ifexists('tactics_s', ''),
process_id = tostring(evidence.process_id),
process_command_line = tostring(evidence.process_command_line),
process_creation_time_utc = tostring(evidence.process_creation_time_utc),
file_hash_algorithm = tostring(file_hash.algorithm),
file_hash_value = tostring(file_hash.value),
file_directory = tostring(evidence.file_directory),
file_name = tostring(evidence.file_name),
user_name = tostring(identity.user_name),
user_uid = tostring(identity.user_uid)
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: process_id
- identifier: CommandLine
columnName: process_command_line
- identifier: CreationTimeUtc
columnName: process_creation_time_utc
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: user_name
- identifier: Sid
columnName: user_uid
- identifier: Name
columnName: user_name
- entityType: File
fieldMappings:
- identifier: Directory
columnName: file_directory
- identifier: Name
columnName: file_name
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: host_name_s
- identifier: FullName
columnName: host_full_name_s
- identifier: OSFamily
columnName: host_os_family_s
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: file_hash_algorithm
- identifier: Value
columnName: file_hash_value
customDetails:
detection_id: detection_id_s
alertDetailsOverride:
alertDisplayNameFormat: Red Canary has published Detection-{{detection_id_s}}
alertDescriptionFormat: |
Red Canary has published a {{detection_severity_s}} severity detection with details:
{{detection_details_s}}
View the Detection at: {{detection_url_s}}
alertTacticsColumnName: tactics_s
alertSeverityColumnName: detection_severity_s
version: 1.0.2
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 7d
matchingMethod: Selected
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails:
- detection_id
Stages and Predicates
Parameters
let detectionLookback = 1d;
let newDetectionWindow = 5m;
Stage 1: source
RedCanaryDetections_CL
Stage 2: where
| where TimeGenerated >= ago(detectionLookback)
Stage 3: where
| where isnotempty(detection_id_s)
Stage 4: summarize
| summarize FirstSeen = min(TimeGenerated), arg_max(TimeGenerated, *) by detection_id_s
Stage 5: where
| where FirstSeen >= ago(newDetectionWindow)
Stage 6: extend (6 consecutive steps)
| extend process_iocs_raw = column_ifexists('process_iocs_s', '[]'),
child_process_iocs_raw = column_ifexists('child_process_iocs_s', '[]'),
cross_process_iocs_raw = column_ifexists('cross_process_iocs_s', '[]'),
file_modification_iocs_raw = column_ifexists('file_modification_iocs_s', '[]'),
identities_raw = column_ifexists('identities_s', '[]')
| extend process_ioc_array = todynamic(iff(isempty(process_iocs_raw), '[]', process_iocs_raw)),
child_process_ioc_array = todynamic(iff(isempty(child_process_iocs_raw), '[]', child_process_iocs_raw)),
cross_process_ioc_array = todynamic(iff(isempty(cross_process_iocs_raw), '[]', cross_process_iocs_raw)),
file_mod_ioc_array = todynamic(iff(isempty(file_modification_iocs_raw), '[]', file_modification_iocs_raw)),
identities_array = todynamic(iff(isempty(identities_raw), '[]', identities_raw))
| extend evidence_array = array_concat(process_ioc_array, child_process_ioc_array, cross_process_ioc_array, file_mod_ioc_array)
| extend evidence = todynamic(evidence_array[0]),
identity = todynamic(identities_array[0])
| extend file_hash_array = todynamic(evidence.file_hashes)
| extend file_hash = todynamic(file_hash_array[0])
Stage 7: project
| project detection_id_s,
detection_url_s = column_ifexists('detection_url_s', ''),
detection_headline_s = column_ifexists('detection_headline_s', ''),
detection_details_s = column_ifexists('detection_details_s', ''),
detection_severity_s = column_ifexists('detection_severity_s', ''),
host_name_s = column_ifexists('host_name_s', ''),
host_full_name_s = column_ifexists('host_full_name_s', ''),
host_os_family_s = column_ifexists('host_os_family_s', ''),
host_os_version_s = column_ifexists('host_os_version_s', ''),
tactics_s = column_ifexists('tactics_s', ''),
process_id = tostring(evidence.process_id),
process_command_line = tostring(evidence.process_command_line),
process_creation_time_utc = tostring(evidence.process_creation_time_utc),
file_hash_algorithm = tostring(file_hash.algorithm),
file_hash_value = tostring(file_hash.value),
file_directory = tostring(evidence.file_directory),
file_name = tostring(evidence.file_name),
user_name = tostring(identity.user_name),
user_uid = tostring(identity.user_uid)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
detection_id_s | is_not_null | field:"detection_id_s" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
detection_details_s | project |
detection_headline_s | project |
detection_id_s | project |
detection_severity_s | project |
detection_url_s | project |
file_directory | project |
file_hash_algorithm | project |
file_hash_value | project |
file_name | project |
host_full_name_s | project |
host_name_s | project |
host_os_family_s | project |
host_os_version_s | project |
process_command_line | project |
process_creation_time_utc | project |
process_id | project |
tactics_s | project |
user_name | project |
user_uid | project |
Theom - Shadow DB large datastore value
#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.
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0035 (Theom has observed shadow (or clone) databases/tables with large financial value. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 7cf83fce-276a-4b12-a876-7b1bc0683cd6
name: Theom - Shadow DB large datastore value
description: |
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0035 (Theom has observed shadow (or clone) databases/tables with large financial value. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1560
- T1530
query: |
TheomAlerts_CL
| where customProps_RuleId_s == "TRIS0035" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
alertDescriptionFormat: "\nSummary: {{summary_s}} \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: customProps_AssetName_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: deepLink_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
TheomAlerts_CL
Stage 2: where
| where customProps_RuleId_s == "TRIS0035" and (priority_s == "P1" or priority_s == "P2")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
customProps_RuleId_s | eq |
| field:"customProps_RuleId_s" kind:eq value:"TRIS0035" |
priority_s | eq |
| field:"priority_s" kind:eq |
Threat Essentials - Mail redirect via ExO transport rule
#Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: d7c575b2-84f5-48cb-92c5-70d7e8246284
name: Threat Essentials - Mail redirect via ExO transport rule
description: |
'Identifies when Exchange Online transport rule configured to forward emails.
This could be an adversary mailbox configured to collect mail from multiple user accounts.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1114
- T1020
query: |
OfficeActivity
| where OfficeWorkload == "Exchange"
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
| extend p = parse_json(Parameters)
| extend RuleName = case(
Operation =~ "Set-TransportRule", tostring(OfficeObjectId),
Operation =~ "New-TransportRule", tostring(p[1].Value),
"Unknown"
)
| mvexpand p
| where (p.Name =~ "BlindCopyTo" or p.Name =~ "RedirectMessageTo") and isnotempty(p.Value)
| extend RedirectTo = p.Value
| extend ClientIPOnly = case(
ClientIP has "." and ClientIP has ":", tostring(split(ClientIP,":")[0]),
ClientIP has "." and ClientIP has "-", tostring(split(ClientIP,"-")[0]),
ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))),
ClientIP
)
| extend Port = case(
ClientIP has "." and ClientIP has ":", (split(ClientIP,":")[1]),
ClientIP has "." and ClientIP has "-", (split(ClientIP,"-")[1]),
ClientIP has "[" and ClientIP has ":", tostring(split(ClientIP,"]:")[1]),
ClientIP has "[" and ClientIP has "-", tostring(split(ClientIP,"]-")[1]),
ClientIP
)
| extend ClientIP = ClientIPOnly
| project TimeGenerated, RedirectTo, ClientIP, Port, UserId, Operation, RuleName
| extend Name=split(UserId, "@")[0], UPNSuffix=split(UserId, "@")[1]
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
OfficeActivity
Stage 2: where
| where OfficeWorkload == "Exchange"
Stage 3: where
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
Stage 4: extend
| extend p = parse_json(Parameters)
Stage 5: extend
| extend RuleName = case(
Operation =~ "Set-TransportRule", tostring(OfficeObjectId),
Operation =~ "New-TransportRule", tostring(p[1].Value),
"Unknown"
)
RuleName =Operation =~ "Set-TransportRule"tostring(OfficeObjectId)Operation =~ "New-TransportRule"tostring(p[1].Value)"Unknown"Stage 6: mv-expand
| mvexpand p
Stage 7: where
| where (p.Name =~ "BlindCopyTo" or p.Name =~ "RedirectMessageTo") and isnotempty(p.Value)
Stage 8: extend (4 consecutive steps)
| extend RedirectTo = p.Value
| extend ClientIPOnly = case(
ClientIP has "." and ClientIP has ":", tostring(split(ClientIP,":")[0]),
ClientIP has "." and ClientIP has "-", tostring(split(ClientIP,"-")[0]),
ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))),
ClientIP
)
| extend Port = case(
ClientIP has "." and ClientIP has ":", (split(ClientIP,":")[1]),
ClientIP has "." and ClientIP has "-", (split(ClientIP,"-")[1]),
ClientIP has "[" and ClientIP has ":", tostring(split(ClientIP,"]:")[1]),
ClientIP has "[" and ClientIP has "-", tostring(split(ClientIP,"]-")[1]),
ClientIP
)
| extend ClientIP = ClientIPOnly
Stage 9: project
| project TimeGenerated, RedirectTo, ClientIP, Port, UserId, Operation, RuleName
Stage 10: extend
| extend Name=split(UserId, "@")[0], UPNSuffix=split(UserId, "@")[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq value:"Exchange" |
Operation | in |
| field:"Operation" kind:in |
p.Name | eq |
| field:"p.Name" kind:eq |
p.Value | is_not_null | field:"p.Value" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | project |
Operation | project |
Port | project |
RedirectTo | project |
RuleName | project |
TimeGenerated | project |
UserId | project |
Name | extend |
UPNSuffix | extend |
Ubiquiti - Large ICMP to external server
#'Detects large ICMP packets to external host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: 6df85d74-e32f-4b71-80e5-bfe2af00be1c
name: Ubiquiti - Large ICMP to external server
description: |
'Detects large ICMP packets to external host.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1041
- T1572
query: |
UbiquitiAuditEvent
| where EventCategory =~ 'firewall'
| where ipv4_is_private(SrcIpAddr)
| where ipv4_is_private(DstIpAddr) == 'False'
| where NetworkProtocol =~ 'ICMP'
| summarize avg_packet_length = avg(toint(NetworkBytes))
| extend a = 1
| join (UbiquitiAuditEvent
| where EventCategory =~ 'firewall'
| where ipv4_is_private(SrcIpAddr)
| where ipv4_is_private(DstIpAddr) == 'False'
| where NetworkProtocol =~ 'ICMP'
| extend a = 1) on a
| where toint(NetworkBytes) > 2*avg_packet_length
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
UbiquitiAuditEvent
Stage 2: where
| where EventCategory =~ 'firewall'
Stage 3: where
| where ipv4_is_private(SrcIpAddr)
Stage 4: where
| where ipv4_is_private(DstIpAddr) == 'False'
Stage 5: where
| where NetworkProtocol =~ 'ICMP'
Stage 6: summarize
| summarize avg_packet_length = avg(toint(NetworkBytes))
Stage 7: extend
| extend a = 1
Stage 8: join
| join (UbiquitiAuditEvent
| where EventCategory =~ 'firewall'
| where ipv4_is_private(SrcIpAddr)
| where ipv4_is_private(DstIpAddr) == 'False'
| where NetworkProtocol =~ 'ICMP'
| extend a = 1) on a
Stage 9: where
where <unknown:toint(, NetworkBytes)> > (avg_packet_length * 2)
Stage 10: extend
extend IPCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventCategory | eq |
| field:"EventCategory" kind:eq value:"firewall" |
NetworkProtocol | eq |
| field:"NetworkProtocol" kind:eq value:"ICMP" |
SrcIpAddr | cidr_match |
| field:"SrcIpAddr" kind:cidr_match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstIpAddr | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:DstIpAddr |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
avg_packet_length | summarize |
a | extend |
IPCustomEntity | extend |
VMware ESXi - Root login
#'Detects when root user login from uncommon IP address.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Privilege Escalation |
Rule Body
id: deb448a8-6a9d-4f8c-8a95-679a0a2cd62c
name: VMware ESXi - Root login
description: |
'Detects when root user login from uncommon IP address.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
let p_lookback = 14d;
let t_lookback = 1h;
let root_ips = VMwareESXi
| where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
| summarize makeset(SrcIpAddr);
VMwareESXi
| where TimeGenerated > ago(t_lookback)
| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
| where SrcIpAddr !in (root_ips)
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let p_lookback = 14d;
let t_lookback = 1h;
Let Binding: root_ips
let root_ips = VMwareESXi
| where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
| summarize makeset(SrcIpAddr);
Stage 1: source
VMwareESXi
Stage 2: where
| where TimeGenerated > ago(t_lookback)
Stage 3: where
| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
Stage 4: extend
| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
Stage 5: where
| where SrcIpAddr !in (root_ips)
Stage 6: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SrcIpAddr | eq | root_ips | excludes:SrcIpAddr field:"SrcIpAddr" value:"root_ips" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | extend |
IPCustomEntity | extend |