Detection rules › Kusto
Kusto rules: 1t
AWSCloudTrail - Privilege escalation via CRUD S3 policy
#Detects inline IAM policy updates that grant broad Amazon S3 create, read, update, and delete permissions. This type of policy expansion can be used to gain higher-impact access to cloud data and facilitate privilege escalation objectives.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS IAM Access Key Compromise Detection (Panther)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - Privilege escalation via CloudFormation policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD DynamoDB policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD IAM policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD KMS policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD Lambda policy (Kusto)
- AWSCloudTrail - Privilege escalation via DataPipeline policy (Kusto)
Rule Body
id: fc3061bb-319c-4fe9-abe2-f59899a6d907
name: AWSCloudTrail - Privilege escalation via CRUD S3 policy
description: |
Detects inline IAM policy updates that grant broad Amazon S3 create, read, update, and delete permissions.
This type of policy expansion can be used to gain higher-impact access to cloud data and facilitate privilege
escalation objectives.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1098.003
query: |
AWSCloudTrail
| where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
| mvexpand Statement
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
| extend Action = tostring(Action)
| where Effect =~ "Allow" and (Action contains "s3:Create" and Action contains "s3:Get" and Action contains "s3:Put" and Action contains "s3:Delete") and Resource == "*" and Condition == ""
| 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]), "")
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
PolicyName: PolicyName
EventName: EventName
UserIdentityArn: UserIdentityArn
RecipientAccountId: RecipientAccountId
alertDetailsOverride:
alertDisplayNameFormat: 'AWS S3 privilege escalation policy update by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} Event, updating inline S3 escalation policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
Stage 4: extend
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
Stage 5: mv-expand
| mvexpand Statement
Stage 6: extend
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
Stage 7: extend
| extend Action = tostring(Action)
Stage 8: where
| where Effect =~ "Allow" and (Action contains "s3:Create" and Action contains "s3:Get" and Action contains "s3:Put" and Action contains "s3:Delete") and Resource == "*" and Condition == ""
Stage 9: 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 10: distinct
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
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 |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
PolicyName | extend |
Statement | extend |
Action | extend |
Condition | extend |
Effect | extend |
Resource | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Claroty - Multiple failed logins by user
#Detects multiple failed logins by the same user in Claroty SRA event logs. The rule looks for failed Login to SRA events, extracts the source username from the event message, and alerts when a user exceeds 5 failed logins within 5 minutes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Rule Body
id: 4b5bb3fc-c690-4f54-9a74-016213d699b4
name: Claroty - Multiple failed logins by user
description: 'Detects multiple failed logins by the same user in Claroty SRA event logs. The rule looks for failed Login to
SRA events, extracts the source username from the event message, and alerts when a user exceeds 5 failed logins within 5
minutes.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1110
- T1190
- T1133
query: |
let threshold = 5;
ClarotyEvent
| where EventType has 'Login to SRA'
| where EventType !has 'succeeded'
| extend SrcUsername = trim(' ', tostring(extract(@'User\s(.*?)\sfailed', 1, EventMessage)))
| where isnotempty(SrcUsername)
| summarize FailedLogins = count() by SrcUsername, bin(TimeGenerated, 5m)
| where FailedLogins > threshold
| extend AccountCustomEntity = SrcUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: Claroty multiple failed logins for {{SrcUsername}}
alertDescriptionFormat: '{{FailedLogins}} failed logins for user {{SrcUsername}} exceeded the threshold in 5 minutes.'
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
ClarotyEvent
Stage 2: where
| where EventType has 'Login to SRA'
Stage 3: where
| where EventType !has 'succeeded'
Stage 4: extend
| extend SrcUsername = trim(' ', tostring(extract(@'User\s(.*?)\sfailed', 1, EventMessage)))
Stage 5: where
| where isnotempty(SrcUsername)
Stage 6: summarize
| summarize FailedLogins = count() by SrcUsername, bin(TimeGenerated, 5m)
Stage 7: where
| where FailedLogins > threshold
Stage 8: extend
| extend AccountCustomEntity = SrcUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | match |
| field:"EventType" kind:match value:"Login to SRA" |
FailedLogins | gt |
| field:"FailedLogins" kind:gt value:"5" |
SrcUsername | is_not_null | field:"SrcUsername" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
EventType | match | succeeded | excludes:EventType field:"EventType" value:"succeeded" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FailedLogins | summarize |
SrcUsername | summarize |
AccountCustomEntity | extend |
Cloudflare - XSS probing pattern in request
#'Detects XSS probing patterns.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 4d9d00b9-31a6-49e4-88c1-9e68277053ac
name: Cloudflare - XSS probing pattern in request
description: |
'Detects XSS probing patterns.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let s_threshold = 3;
Cloudflare
| where HttpRequestMethod in~ ('POST', 'PUT')
| extend susp_ch = countof(ClientRequestURI, '%00')
| where ClientRequestURI matches regex @'(alert\()|(alert\%28)|(String\.fromCharCode\()|(expression\(alert)' or susp_ch > s_threshold
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: CompleteUrl
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let s_threshold = 3;
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod in~ ('POST', 'PUT')
Stage 3: extend
| extend susp_ch = countof(ClientRequestURI, '%00')
Stage 4: where
| where ClientRequestURI matches regex @'(alert\()|(alert\%28)|(String\.fromCharCode\()|(expression\(alert)' or susp_ch > s_threshold
Stage 5: extend
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientRequestURI | regex_match |
| field:"ClientRequestURI" kind:regex_match |
HttpRequestMethod | in |
| field:"HttpRequestMethod" kind:in |
susp_ch | gt |
| field:"susp_ch" kind:gt value:"3" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
susp_ch | extend |
CompleteUrl | extend |
CYFIRMA - Attack Surface - Open Ports High Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule is triggered when CYFIRMA identifies open and publicly accessible ports on internet-facing assets. Exposed services may include SSH, RDP, HTTP, or other potentially sensitive ports, increasing the risk of unauthorized access, exploitation, or reconnaissance by threat actors. Monitoring open ports is critical to reducing the external attack surface and preventing misuse through brute force, service vulnerabilities, or protocol exploitation."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Command & Control |
Rule Body
id: 87e7eb3f-bb8e-46e5-8807-d3fc63d0f676
name: CYFIRMA - Attack Surface - Open Ports High Rule
description: |
"This rule is triggered when CYFIRMA identifies open and publicly accessible ports on internet-facing assets. Exposed services may include SSH, RDP, HTTP, or other potentially sensitive ports, increasing the risk of unauthorized access, exploitation, or reconnaissance by threat actors. Monitoring open ports is critical to reducing the external attack surface and preventing misuse through brute force, service vulnerabilities, or protocol exploitation."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaAttackSurfaceAlertsConnector
dataTypes:
- CyfirmaASOpenPortsAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- CommandAndControl
- Discovery
- DefenseEvasion
- Persistence
relevantTechniques:
- T1566
- T1071
- T1505
query: |
// High Severity - Open Ports Exposure Detected
let timeFrame = 5m;
CyfirmaASOpenPortsAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend Description=description, FirstSeen=first_seen, LastSeen=last_seen, RiskScore=risk_score, Domain=sub_domain, TopDomain=top_domain, NetworkIP=ip, AlertUID=alert_uid, UID=uid, WebServer=web_server, WebServerVersion=web_server_version, OpenPorts=open_ports, ProviderName="CYFIRMA", ProductName="DeCYFIR/DeTCT"
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
WebServer,
WebServerVersion,
OpenPorts,
ProductName,
ProviderName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: TopDomain
- identifier: DnsDomain
columnName: Domain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: NetworkIP
customDetails:
TimeGenerated: TimeGenerated
RiskScore: RiskScore
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
WebServer: WebServer
WebServerVersion: WebServerVersion
OpenPorts: OpenPorts
Description: Description
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity Open Ports Exposure Detected on Assets - Domain: {{Domain}}, IP: {{NetworkIP}}"
alertDescriptionFormat: "CYFIRMA - High Severity Open Ports Exposure Detected on Assets - {{Description}}"
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaASOpenPortsAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend Description=description, FirstSeen=first_seen, LastSeen=last_seen, RiskScore=risk_score, Domain=sub_domain, TopDomain=top_domain, NetworkIP=ip, AlertUID=alert_uid, UID=uid, WebServer=web_server, WebServerVersion=web_server_version, OpenPorts=open_ports, ProviderName="CYFIRMA", ProductName="DeCYFIR/DeTCT"
Stage 4: project
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
WebServer,
WebServerVersion,
OpenPorts,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
Description | project |
Domain | project |
FirstSeen | project |
LastSeen | project |
NetworkIP | project |
OpenPorts | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
TimeGenerated | project |
TopDomain | project |
UID | project |
WebServer | project |
WebServerVersion | project |
CYFIRMA - Brand Intelligence - Executive/People Impersonation High Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule detects potential impersonation of executive or high-profile individuals across digital platforms such as social media. Such impersonation can be used to mislead stakeholders, perform social engineering attacks, or cause reputational damage to the organization. Timely detection is crucial to assess risk and take down malicious profiles to protect brand and executive identity."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access |
Rule Body
id: 159d26a1-591c-4f70-b1ca-2843c881aaec
name: CYFIRMA - Brand Intelligence - Executive/People Impersonation High Rule
description: |
"This rule detects potential impersonation of executive or high-profile individuals across digital platforms such as social media.
Such impersonation can be used to mislead stakeholders, perform social engineering attacks, or cause reputational damage to the organization.
Timely detection is crucial to assess risk and take down malicious profiles to protect brand and executive identity."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaBrandIntelligenceAlertsDC
dataTypes:
- CyfirmaBIExecutivePeopleAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Reconnaissance
- ResourceDevelopment
- InitialAccess
relevantTechniques:
- T1589.003
- T1585.001
- T1566.002
query: |
// High severity - Executive/People Impersonation
let timeFrame = 5m;
CyfirmaBIExecutivePeopleAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName
customDetails:
AssetType: AssetType
AssetValue: AssetValue
Description: Description
TimeGenerated: TimeGenerated
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
Impact: Impact
Recommendation: Recommendation
PostedDate: PostedDate
RiskScore: RiskScore
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity Alert - Executive Impersonation - Suspicious Social Media Account Detected - {{AssetValue}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaBIExecutivePeopleAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
PostedDate | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
TimeGenerated | project |
UID | project |
Dataverse - Guest user exfiltration following Power Platform defense impairment
#Identifies a chain of events starting with disablement of Power Platform tenant isolation and removal of an environment's access security group. These events are correlated with Dataverse exfiltration alerts associated with the impacted environment and recently created Microsoft Entra guest users. Note: Activate other Dataverse analytics rules with the MITRE tactic 'Exfiltration' before enabling this rule.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration | |
| Defense Evasion |
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)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: 39efbf4b-b347-4cc7-895e-99a868bf29ea
kind: Scheduled
name: Dataverse - Guest user exfiltration following Power Platform defense impairment
description: |
Identifies a chain of events starting with disablement of Power Platform tenant isolation and removal of an environment's access security group. These events are correlated with Dataverse exfiltration alerts associated with the impacted environment and recently created Microsoft Entra guest users.
Note: Activate other Dataverse analytics rules with the MITRE tactic 'Exfiltration' before enabling this rule.
severity: High
status: Available
requiredDataConnectors:
- connectorId: PowerPlatformAdmin
dataTypes:
- PowerPlatformAdminActivity
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Exfiltration
relevantTechniques:
- T1629
- T1567
query: |
let query_lookback = 14d;
let query_frequncy = 1h;
let defense_evasion_events = PowerPlatformAdminActivity
| where TimeGenerated >= ago(query_lookback)
| where EventOriginalType == "TenantIsolationOperation"
| mv-expand PropertyCollection
| where PropertyCollection.Name == "powerplatform.analytics.resource.tenant.isolation_policy.enabled"
| where PropertyCollection.Value == "False"
| summarize
TenantIsolationRemovalTimestamp = max(TimeGenerated)
by SecurityDisablingUser = ActorName
| join kind=inner (
PowerPlatformAdminActivity
| where TimeGenerated >= ago(query_lookback)
| where EventOriginalType == "EnvironmentPropertyChange"
| where PropertyCollection has "Property: SecurityGroupId, Old Value: , New Value: "
| mv-expand PropertyCollection
| extend
GroupRemovalTimestamp = TimeGenerated,
InstanceUrl = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.url", PropertyCollection.Value, "")),
EnvironmentId = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.name", PropertyCollection.Value, ""))
| summarize InstanceUrl = max(InstanceUrl), EnvironmentId = max(EnvironmentId) by GroupRemovalTimestamp, SecurityDisablingUser = ActorName)
on SecurityDisablingUser
| summarize
GroupRemovalTimestamp = max(GroupRemovalTimestamp),
TenantIsolationRemovalTimestamp = max(TenantIsolationRemovalTimestamp)
by SecurityDisablingUser, InstanceUrl, EnvironmentId;
let exfiltration_alerts = SecurityAlert
| where TimeGenerated >= ago(query_frequncy)
| where Tactics has "Exfiltration"
| where Entities has ('"AppId":32780')
| mv-expand todynamic(Entities)
| extend AlertUPN = iif(Entities.Type == "account", strcat(Entities.Name, "@", Entities.UPNSuffix), "")
| extend InstanceUrl = tostring(iif(Entities.AppId == 32780, Entities.InstanceName, ""))
| join kind=inner defense_evasion_events on InstanceUrl
| where StartTime > TenantIsolationRemovalTimestamp and StartTime > GroupRemovalTimestamp
| summarize InstanceUrl = max(InstanceUrl), AlertUPN = max(AlertUPN) by AlertName, SystemAlertId
| extend AlertDetails = bag_pack("AlertName", AlertName, "SystemAlertId", SystemAlertId)
| summarize AlertDetails = make_set(AlertDetails, 100) by AlertUPN, InstanceUrl
| join kind=inner (
AuditLogs
| where OperationName == "Update user"
| where Identity == "Microsoft Invitation Acceptance Portal"
| mv-expand TargetResources
| extend ModifiedProperties = TargetResources.modifiedProperties
| mv-expand ModifiedProperties
| where ModifiedProperties.displayName == "AcceptedAs"
| summarize RedeemTime = max(TimeGenerated) by GuestUser = tostring(parse_json(replace_regex(tostring(ModifiedProperties.newValue), "\\r", ""))[0]))
on $left.AlertUPN == $right.GuestUser;
defense_evasion_events
| join kind=inner exfiltration_alerts on InstanceUrl
| extend
AccountName = tostring(split(SecurityDisablingUser, "@")[0]),
UPNSuffix = tostring(split(SecurityDisablingUser, "@")[1]),
GuestAccountName = tostring(split(GuestUser, "@")[0]),
GuestUPNSuffix = tostring(split(GuestUser, "@")[0]),
DataverseId = 32780
| project
SecurityDisablingUser,
GuestUser,
AlertDetails,
TenantIsolationRemovalTimestamp,
GroupRemovalTimestamp,
InstanceUrl,
EnvironmentId,
AccountName,
UPNSuffix,
GuestAccountName,
GuestUPNSuffix,
DataverseId
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: Name
columnName: GuestAccountName
- identifier: UPNSuffix
columnName: GuestUPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: DataverseId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: 'Dataverse - exfiltration alerts following defense impairment
in {{InstanceUrl}} '
alertDescriptionFormat: '{{SecurityDisablingUser}} disabled Power Platform tenant
isolation and removed the security group used to control access to {{{InstanceUrl}}.
Exfiltration alerts associated with guest users were then detected from user {{{GuestUser}}'
customDetails:
Environment: EnvironmentId
version: 3.2.0
Stages and Predicates
Parameters
let query_lookback = 14d;
let query_frequncy = 1h;
let defense_evasion_events is inlined into the numbered stages below.
Let Binding: exfiltration_alerts
let exfiltration_alerts = SecurityAlert
| where TimeGenerated >= ago(query_frequncy)
| where Tactics has "Exfiltration"
| where Entities has ('"AppId":32780')
| mv-expand todynamic(Entities)
| extend AlertUPN = iif(Entities.Type == "account", strcat(Entities.Name, "@", Entities.UPNSuffix), "")
| extend InstanceUrl = tostring(iif(Entities.AppId == 32780, Entities.InstanceName, ""))
| join kind=inner defense_evasion_events on InstanceUrl
| where StartTime > TenantIsolationRemovalTimestamp and StartTime > GroupRemovalTimestamp
| summarize InstanceUrl = max(InstanceUrl), AlertUPN = max(AlertUPN) by AlertName, SystemAlertId
| extend AlertDetails = bag_pack("AlertName", AlertName, "SystemAlertId", SystemAlertId)
| summarize AlertDetails = make_set(AlertDetails, 100) by AlertUPN, InstanceUrl
| join kind=inner (
AuditLogs
| where OperationName == "Update user"
| where Identity == "Microsoft Invitation Acceptance Portal"
| mv-expand TargetResources
| extend ModifiedProperties = TargetResources.modifiedProperties
| mv-expand ModifiedProperties
| where ModifiedProperties.displayName == "AcceptedAs"
| summarize RedeemTime = max(TimeGenerated) by GuestUser = tostring(parse_json(replace_regex(tostring(ModifiedProperties.newValue), "\\r", ""))[0]))
on $left.AlertUPN == $right.GuestUser;
Stages 1 to 9 define let defense_evasion_events as the rule's main pipeline source. Stages 10 to 12 use it.
Stage 1: source
PowerPlatformAdminActivity
Stage 2: where
where TimeGenerated >= ago(1209600s)
Stage 3: where
where EventOriginalType == "TenantIsolationOperation"
Stage 4: mv-expand
mv-expand PropertyCollection
Stage 5: where
where PropertyCollection.Name == "powerplatform.analytics.resource.tenant.isolation_policy.enabled"
Stage 6: where
where PropertyCollection.Value == False
Stage 7: summarize
summarize SecurityDisablingUser, TenantIsolationRemovalTimestamp by SecurityDisablingUser
Stage 8: join
| join kind=inner (
PowerPlatformAdminActivity
| where TimeGenerated >= ago(query_lookback)
| where EventOriginalType == "EnvironmentPropertyChange"
| where PropertyCollection has "Property: SecurityGroupId, Old Value: , New Value: "
| mv-expand PropertyCollection
| extend
GroupRemovalTimestamp = TimeGenerated,
InstanceUrl = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.url", PropertyCollection.Value, "")),
EnvironmentId = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.name", PropertyCollection.Value, ""))
| summarize InstanceUrl = max(InstanceUrl), EnvironmentId = max(EnvironmentId) by GroupRemovalTimestamp, SecurityDisablingUser = ActorName)
on SecurityDisablingUser
Stage 9: summarize
summarize GroupRemovalTimestamp, TenantIsolationRemovalTimestamp by SecurityDisablingUser, InstanceUrl, EnvironmentId
Stage 10: join
join kind=inner (exfiltration_alerts) on InstanceUrl
Stage 11: extend
extend AccountName, DataverseId, GuestAccountName, GuestUPNSuffix, UPNSuffix
Stage 12: project
project AccountName, AlertDetails, DataverseId, EnvironmentId, GroupRemovalTimestamp, GuestAccountName, GuestUPNSuffix, GuestUser, InstanceUrl, SecurityDisablingUser, TenantIsolationRemovalTimestamp, UPNSuffix
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AlertDetails | project |
DataverseId | project |
EnvironmentId | project |
GroupRemovalTimestamp | project |
GuestAccountName | project |
GuestUPNSuffix | project |
GuestUser | project |
InstanceUrl | project |
SecurityDisablingUser | project |
TenantIsolationRemovalTimestamp | project |
UPNSuffix | project |
Failover Plan Started
#Detects when a failover plan starts. This might indicate disaster recovery activity or issues with primary systems.
Rule Body
id: e06ec6d7-d9f6-4675-89bf-03fa8a0e0be0
name: Failover Plan Started
description: Detects when a failover plan starts. This might indicate disaster recovery
activity or issues with primary systems.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 26600\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 26600
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"26600" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Flare host results
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'This query searches for events relating to IP, domain or host'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 9cb7c337-f175-4af6-b0e8-b6b7552d762d
name: Flare host results
description: |
'This query searches for events relating to IP, domain or host'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Flare
dataTypes:
- FireworkV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Reconnaissance
relevantTechniques:
- T1596
query: |
FireworkV2_CL
| where notempty(uid) and RiskScore >= 3
| extend index_name = split(uid, "/")[0]
| where index_name == "service"
version: 3.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
FireworkV2_CL
Stage 2: where
| where notempty(uid) and RiskScore >= 3
Stage 3: extend
| extend index_name = split(uid, "/")[0]
Stage 4: where
| where index_name == "service"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RiskScore | ge |
| field:"RiskScore" kind:ge value:"3" |
index_name | eq |
| field:"index_name" kind:eq value:"service" |
uid | is_not_null | field:"uid" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
index_name | extend |
GreyNoise TI Map IP Entity to SigninLogs
#This query maps any GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: f6c76cc9-218c-5b76-9b82-8607f09ea1b4
name: GreyNoise TI Map IP Entity to SigninLogs
version: 1.0.1
kind: Scheduled
description: |
'This query maps any GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: GreyNoise2SentinelAPI
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let aadFunc = (tableName:string){
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where SourceSystem == 'GreyNoise'
// Picking up only IOC's that contain the entities we want
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
// As there is potentially more than 1 indicator type for matching IP, taking NetworkIP first, then others if that is empty.
// Taking the first non-empty value based on potential IOC match availability
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (
table(tableName) | where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails), StatusReason = tostring(Status.failureReason)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
// renaming time column so it is clear the log this came from
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
)
on $left.TI_ipEntity == $right.IPAddress
| where SigninLogs_TimeGenerated < ExpirationDateTime
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, IPAddress
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, StatusReason, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
| extend timestamp = SigninLogs_TimeGenerated, Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
Let Binding: aadFunc
let aadFunc = (tableName:string){
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where SourceSystem == 'GreyNoise'
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
| join kind=innerunique (
table(tableName) | where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails), StatusReason = tostring(Status.failureReason)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
)
on $left.TI_ipEntity == $right.IPAddress
| where SigninLogs_TimeGenerated < ExpirationDateTime
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, IPAddress
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, StatusReason, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
| extend timestamp = SigninLogs_TimeGenerated, Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
};
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: aadSignin, aadNonInt
Leg 1: aadSignin
Leg 2: aadNonInt
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null | |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"GreyNoise" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AppDisplayName | project |
ConfidenceScore | project |
Description | project |
EmailSourceIpAddress | project |
ExpirationDateTime | project |
IPAddress | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
SigninLogs_TimeGenerated | project |
StatusCode | project |
StatusDetails | project |
StatusReason | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
UserPrincipalName | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
Pathlock TDnR - ICF Web Service Changes
#Detects changes to SAP Internet Communication Framework (ICF) web service configuration, forwarded by Pathlock Threat Detection and Response. Unauthorized ICF changes may expose new attack surfaces, enable access to sensitive OData or SOAP services, or re-enable previously disabled dangerous services.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c32
name: Pathlock TDnR - ICF Web Service Changes
kind: Scheduled
description: >-
Detects changes to SAP Internet Communication Framework (ICF) web service configuration,
forwarded by Pathlock Threat Detection and Response. Unauthorized ICF changes may expose new
attack surfaces, enable access to sensitive OData or SOAP services, or re-enable
previously disabled dangerous services.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1505
query: |
Pathlock_TDnR_CL
| where DataSource == "ICF_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 == "ICF_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:"ICF_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 |
Theom - Dark Data with large fin 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 TRIS0032 (Theom has observed data with a large financial value, but that has not been accessed recently. Use this information to enforce data retention policies)"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 545fdcc7-2123-4b8a-baf6-409f29aad4b1
name: Theom - Dark Data with large fin value
description: |
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0032 (Theom has observed data with a large financial value, but that has not been accessed recently. Use this information to 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 == "TRIS0032" 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 == "TRIS0032" 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:"TRIS0032" |
priority_s | eq |
| field:"priority_s" kind:eq |
Theom - Financial data unencrypted
#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 TRIS0003 (Financial data has been observed in unencrypted data stores. Encrypt data at rest to comply with this CIS requirement)"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: b568d2fb-b73c-4e6a-88db-2093457712af
name: Theom - Financial data unencrypted
description: |
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0003 (Financial data has been observed in unencrypted data stores. Encrypt data at rest to comply with this CIS requirement)"
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1213
- T1530
query: |
TheomAlerts_CL
| where customProps_RuleId_s == "TRIS0003" 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 == "TRIS0003" 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:"TRIS0003" |
priority_s | eq |
| field:"priority_s" kind:eq |
TI map IP entity to AWSCloudTrail
#Identifies a match in AWSCloudTrail from any IP IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: f110287e-1358-490d-8147-ed804b328514
name: TI map IP entity to AWSCloudTrail
description: |
Identifies a match in AWSCloudTrail from any IP IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Look back 1 hour for AWSCloudTrail logs
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
// Fetch threat intelligence indicators related to IP addresses
let IP_Indicators = ThreatIntelligenceIndicator
// Filter out indicators without relevant IP address fields
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
| where TimeGenerated >= ago(ioc_lookBack)
// Select the IP entity based on availability of different IP fields
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
// Exclude local addresses using the ipv4_is_private operator and filtering out specific address prefixes
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now();
// Perform a join between IP indicators and AWSCloudTrail logs to identify potential malicious activity
IP_Indicators
// Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
| join kind=innerunique (
AWSCloudTrail
| where TimeGenerated >= ago(dt_lookBack)
| extend AWSCloudTrail_TimeGenerated = TimeGenerated // Rename time column for clarity
)
on $left.TI_ipEntity == $right.SourceIpAddress
// Filter out logs that occurred after the expiration of the corresponding indicator
| where AWSCloudTrail_TimeGenerated < ExpirationDateTime
// Group the results by IndicatorId and SourceIpAddress, and keep the log entry with the latest timestamp
| summarize AWSCloudTrail_TimeGenerated = arg_max(AWSCloudTrail_TimeGenerated, *) by IndicatorId, SourceIpAddress
// Select the desired output fields
| project AWSCloudTrail_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserIdentityUserName, SourceIpAddress,
NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
// Rename the timestamp field
| extend timestamp = AWSCloudTrail_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: ObjectGuid
columnName: UserIdentityUserName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.4.2
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Stages 1 to 7 define let IP_Indicators as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: extend (3 consecutive steps)
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =isnotempty(NetworkIP)NetworkIPNetworkDestinationIPStage 5: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: join
IP_Indicators
| join kind=innerunique (
AWSCloudTrail
| where TimeGenerated >= ago(dt_lookBack)
| extend AWSCloudTrail_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.SourceIpAddress
Stage 9: where
where AWSCloudTrail_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize AWSCloudTrail_TimeGenerated by IndicatorId, SourceIpAddress
Stage 11: project
project AWSCloudTrail_TimeGenerated, ActivityGroupNames, ConfidenceScore, Description, EmailSourceIpAddress, EventName, EventTypeName, ExpirationDateTime, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SourceIpAddress, TI_ipEntity, ThreatType, Type, Url, UserIdentityAccountId, UserIdentityPrincipalid, UserIdentityUserName
Stage 12: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | 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:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSCloudTrail_TimeGenerated | project |
ActivityGroupNames | project |
ConfidenceScore | project |
Description | project |
EmailSourceIpAddress | project |
EventName | project |
EventTypeName | project |
ExpirationDateTime | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
SourceIpAddress | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
UserIdentityAccountId | project |
UserIdentityPrincipalid | project |
UserIdentityUserName | project |
timestamp | extend |
Tomcat - Request to sensitive files
#'Detects request to sensitive files.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 0c851bd4-4875-11ec-81d3-0242ac130003
name: Tomcat - Request to sensitive files
description: |
'Detects request to sensitive files.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);
TomcatEvent
| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
| where File in (forbidden_files)
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);
Stage 1: source
TomcatEvent
Stage 2: extend
| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
Stage 3: where
| where File in (forbidden_files)
Stage 4: extend
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
File | in |
| field:"File" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
File | extend |
FileCustomEntity | extend |
UrlCustomEntity | extend |
VMware SD-WAN Edge - All Cloud Security Service Tunnels DOWN
#This analytics rule collects events where an SD-WAN Edge reports that all Cloud Security Service (CSS) tunnels are down. Losing connectivity to a Secure Service Edge (SSE) service can impact security capabilities.
Rule Body
id: 8d05cc90-d337-41f5-b5fa-614cbfe1a879
name: VMware SD-WAN Edge - All Cloud Security Service Tunnels DOWN
version: 1.0.0
kind: Scheduled
description: This analytics rule collects events where an SD-WAN Edge reports that all Cloud Security Service (CSS) tunnels are down. Losing connectivity to a Secure Service Edge (SSE) service can impact security capabilities.
severity: Medium
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
- connectorId: VMwareSDWAN
dataTypes:
- SDWAN
query: |
VMware_VECO_EventLogs_CL
| where event contains "ALL_CSS_DOWN"
| extend Edge_Serial_Number = extract("edgeSerialNumber:(.*)$", 1, detail)
| project TimeGenerated, Edge_Serial_Number, message, severity
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDescriptionFormat: '{{message}} '
alertDynamicProperties: []
customDetails:
Edge_Serial_Number: Edge_Serial_Number
suppressionDuration: 5h
Stages and Predicates
Stage 1: source
VMware_VECO_EventLogs_CL
Stage 2: where
| where event contains "ALL_CSS_DOWN"
Stage 3: extend
| extend Edge_Serial_Number = extract("edgeSerialNumber:(.*)$", 1, detail)
Stage 4: project
| project TimeGenerated, Edge_Serial_Number, message, severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | contains |
| field:"event" kind:contains value:"ALL_CSS_DOWN" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Edge_Serial_Number | project |
TimeGenerated | project |
message | project |
severity | project |