Detection rules › Kusto

Kusto rules: 1t

AWSCloudTrail - Privilege escalation via CRUD S3 policy

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

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

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

FieldKindValuesSearch
Actioncontains
  • s3:Create
  • s3:Delete
  • s3:Get
  • s3:Put
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNamein
  • PutGroupPolicy corpus 12 (kusto 11, panther 1)
  • PutRolePolicy corpus 12 (kusto 11, panther 1)
  • PutUserPolicy corpus 12 (kusto 11, panther 1)
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
PolicyNameextend
Statementextend
Actionextend
Conditionextend
Effectextend
Resourceextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

Claroty - Multiple failed logins by user

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

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

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.

FieldKindValuesSearch
EventTypematch
  • Login to SRA transforms: term
field:"EventType" kind:match value:"Login to SRA"
FailedLoginsgt
  • 5 transforms: number
field:"FailedLogins" kind:gt value:"5"
SrcUsernameis_not_null
  • (no value, null check)
field:"SrcUsername" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
EventTypematchsucceededexcludes:EventType field:"EventType" value:"succeeded"

Output Fields

These fields are emitted when the rule matches.

FieldSource
FailedLoginssummarize
SrcUsernamesummarize
AccountCustomEntityextend

Cloudflare - XSS probing pattern in request

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

'Detects XSS probing patterns.'

MITRE ATT&CK Coverage

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.

FieldKindValuesSearch
ClientRequestURIregex_match
    • (alert()
    • (alert\%28)
    • (String.fromCharCode()
    • (expression(alert)
field:"ClientRequestURI" kind:regex_match
HttpRequestMethodin
  • POST
  • PUT
field:"HttpRequestMethod" kind:in
susp_chgt
  • 3 transforms: number
field:"susp_ch" kind:gt value:"3"

Output Fields

These fields are emitted when the rule matches.

FieldSource
susp_chextend
CompleteUrlextend

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.

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

"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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
Descriptionproject
Domainproject
FirstSeenproject
LastSeenproject
NetworkIPproject
OpenPortsproject
ProductNameproject
ProviderNameproject
RiskScoreproject
TimeGeneratedproject
TopDomainproject
UIDproject
WebServerproject
WebServerVersionproject

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.

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

"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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
PostedDateproject
ProductNameproject
ProviderNameproject
Recommendationproject
RiskScoreproject
TimeGeneratedproject
UIDproject

Dataverse - Guest user exfiltration following Power Platform defense impairment

#
Status
available
Severity
high
Time window
14d
Group by
EnvironmentId, InstanceUrl, SecurityDisablingUser
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Exfiltration
Defense Evasion

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stage 10

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.

FieldSource
AccountNameproject
AlertDetailsproject
DataverseIdproject
EnvironmentIdproject
GroupRemovalTimestampproject
GuestAccountNameproject
GuestUPNSuffixproject
GuestUserproject
InstanceUrlproject
SecurityDisablingUserproject
TenantIsolationRemovalTimestampproject
UPNSuffixproject

Failover Plan Started

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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

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.

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

'This query searches for events relating to IP, domain or host'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
RiskScorege
  • 3 transforms: number
field:"RiskScore" kind:ge value:"3"
index_nameeq
  • service
field:"index_name" kind:eq value:"service"
uidis_not_null
  • (no value, null check)
field:"uid" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
index_nameextend

GreyNoise TI Map IP Entity to SigninLogs

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

This query maps any GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AppDisplayNameproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIpAddressproject
ExpirationDateTimeproject
IPAddressproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
SigninLogs_TimeGeneratedproject
StatusCodeproject
StatusDetailsproject
StatusReasonproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
UserPrincipalNameproject
Nameextend
UPNSuffixextend
timestampextend

Pathlock TDnR - ICF Web Service Changes

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

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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

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

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.

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

"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

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.

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.

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

"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

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.

TI map IP entity to AWSCloudTrail

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

Identifies a match in AWSCloudTrail from any IP IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
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 =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

Stage 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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSCloudTrail_TimeGeneratedproject
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIpAddressproject
EventNameproject
EventTypeNameproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
SourceIpAddressproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
UserIdentityAccountIdproject
UserIdentityPrincipalidproject
UserIdentityUserNameproject
timestampextend

Tomcat - Request to sensitive files

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

'Detects request to sensitive files.'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
Filein
  • id_rsa
  • passwd
  • shadow
field:"File" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Fileextend
FileCustomEntityextend
UrlCustomEntityextend

VMware SD-WAN Edge - All Cloud Security Service Tunnels DOWN

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

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.

FieldKindValuesSearch
eventcontains
  • ALL_CSS_DOWN
field:"event" kind:contains value:"ALL_CSS_DOWN"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Edge_Serial_Numberproject
TimeGeneratedproject
messageproject
severityproject

Back to all Kusto rules