Detection rules › Kusto

Kusto rules: 19

AWSCloudTrail - Policy version set to default

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

Detects SetDefaultPolicyVersion activity that makes a different IAM policy version active. Attackers can use this to activate previously created permissive versions and escalate access.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Rule Body

id: 874a1762-3fd7-4489-b411-6d4a9e9e8a59
name: AWSCloudTrail - Policy version set to default
description: |
  Detects SetDefaultPolicyVersion activity that makes a different IAM policy version active.
  Attackers can use this to activate previously created permissive versions and escalate access.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation    
relevantTechniques:
  - T1098.003
query: |
    AWSCloudTrail
    | where EventName == "SetDefaultPolicyVersion" and isempty(ErrorCode) and isempty(ErrorMessage)
    | 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]), "")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  EventName: EventName
  EventSource: EventSource
  AWSRegion: AWSRegion
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS IAM default policy version changed by {{AccountName}}'
  alertDescriptionFormat: 'Detected SetDefaultPolicyVersion from {{SourceIpAddress}} in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName == "SetDefaultPolicyVersion" and isempty(ErrorCode) and isempty(ErrorMessage)

Stage 3: 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]), "")

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

blacklens Insights

#

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
Group by
id
Source
github.com/Azure/Azure-Sentinel

'Creates incidents from blacklens.io Attack Surface Management alerts ingested into the blacklens_CL table. Alert severity is mapped dynamically from the source data.'

MITRE ATT&CK Coverage

Rule Body

id: e261b70a-3005-4a1b-a7a2-2c8147fafed7
name: blacklens Insights
description: |
  'Creates incidents from blacklens.io Attack Surface Management alerts ingested into the blacklens_CL table. Alert severity is mapped dynamically from the source data.'
severity: High
status: Available
tactics:
  - Reconnaissance
  - ResourceDevelopment
  - InitialAccess
  - CredentialAccess
  - Collection
  - Exfiltration
  - DefenseEvasion
  - CommandAndControl
relevantTechniques:
  - T1595
  - T1583
  - T1190
  - T1110
  - T1005
  - T1041
  - T1562
  - T1071
requiredDataConnectors:
  - connectorId: blacklens_io
    dataTypes:
      - blacklens_CL
kind: NRT
query: |
  blacklens_CL
  | summarize arg_max(TimeGenerated, *) by id
  | extend AlertSeverity = case(
      tolower(severity) == "critical", "High",
      tolower(severity) == "high", "High",
      tolower(severity) == "medium", "Medium",
      tolower(severity) == "low", "Low",
      "Informational"
  )
suppressionDuration: PT1H
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "{{alert_title}}"
  alertDescriptionFormat: "{{message}}"
  alertSeverityColumnName: AlertSeverity
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: link
version: 1.0.0

Stages and Predicates

Stage 1: source

blacklens_CL

Stage 2: summarize

| summarize arg_max(TimeGenerated, *) by id

Stage 3: extend

| extend AlertSeverity = case(
    tolower(severity) == "critical", "High",
    tolower(severity) == "high", "High",
    tolower(severity) == "medium", "Medium",
    tolower(severity) == "low", "Low",
    "Informational"
)
AlertSeverity =
ifseverity == "critical""High"
elifseverity == "high""High"
elifseverity == "medium""Medium"
elifseverity == "low""Low"
else"Informational"

Output Fields

These fields are emitted when the rule matches.

FieldSource
idsummarize
AlertSeverityextend

BloodHound Attack Path Finding - Large Default Groups With All Extended Privileges

#

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

BloodHound Attack Path Finding - Large Default Groups With All Extended Privileges

Rule Body

id: ab49f284-0b2a-42c9-b5e3-2c0fbf941d8e
name: BloodHound Attack Path Finding - Large Default Groups With All Extended Privileges
description: BloodHound Attack Path Finding - Large Default Groups With All Extended
  Privileges
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "LargeDefaultGroupsAllExtendedRights"
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: domain_name
customDetails:
  Description: ShortDescription
  Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BHEAttackPathsData_CL

Stage 2: where

| where Finding == "LargeDefaultGroupsAllExtendedRights"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Findingeq
  • LargeDefaultGroupsAllExtendedRights
field:"Finding" kind:eq value:"LargeDefaultGroupsAllExtendedRights"

Cisco Cloud Security - Request Allowed to harmful/malicious URI category

#
Status
available
Severity
medium
Time window
10m
Source
github.com/Azure/Azure-Sentinel

'It is reccomended that these Categories shoud be blocked by policies because they provide harmful/malicious content..'

MITRE ATT&CK Coverage

Rule Body

id: d6bf1931-b1eb-448d-90b2-de118559c7ce
name: Cisco Cloud Security - Request Allowed to harmful/malicious URI category
description: |
  'It is reccomended that these Categories shoud be blocked by policies because they provide harmful/malicious content..'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes:
      - Cisco_Umbrella_proxy_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1071.001
  - T1041
query: |
  Cisco_Umbrella
  | where EventType == 'proxylogs'
  | where DvcAction =~ 'Allowed'
  | where UrlCategory contains 'Adult Themes' or
        UrlCategory contains 'Adware' or
        UrlCategory contains 'Alcohol' or
        UrlCategory contains 'Illegal Downloads' or
        UrlCategory contains 'Drugs' or
        UrlCategory contains 'Child Abuse Content' or
        UrlCategory contains 'Hate/Discrimination' or
        UrlCategory contains 'Nudity' or
        UrlCategory contains 'Pornography' or
        UrlCategory contains 'Proxy/Anonymizer' or
        UrlCategory contains 'Sexuality' or
        UrlCategory contains 'Tasteless' or
        UrlCategory contains 'Terrorism' or
        UrlCategory contains 'Web Spam' or
        UrlCategory contains 'German Youth Protection' or
        UrlCategory contains 'Illegal Activities' or
        UrlCategory contains 'Lingerie/Bikini' or
        UrlCategory contains 'Weapons'
  | project TimeGenerated, SrcIpAddr, DstIpAddr, UrlOriginal, Identities

entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlOriginal
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.1.2
kind: Scheduled

Stages and Predicates

Stage 1: source

Cisco_Umbrella

Stage 2: where

| where EventType == 'proxylogs'

Stage 3: where

| where DvcAction =~ 'Allowed'

Stage 4: where

| where UrlCategory contains 'Adult Themes' or
      UrlCategory contains 'Adware' or
      UrlCategory contains 'Alcohol' or
      UrlCategory contains 'Illegal Downloads' or
      UrlCategory contains 'Drugs' or
      UrlCategory contains 'Child Abuse Content' or
      UrlCategory contains 'Hate/Discrimination' or
      UrlCategory contains 'Nudity' or
      UrlCategory contains 'Pornography' or
      UrlCategory contains 'Proxy/Anonymizer' or
      UrlCategory contains 'Sexuality' or
      UrlCategory contains 'Tasteless' or
      UrlCategory contains 'Terrorism' or
      UrlCategory contains 'Web Spam' or
      UrlCategory contains 'German Youth Protection' or
      UrlCategory contains 'Illegal Activities' or
      UrlCategory contains 'Lingerie/Bikini' or
      UrlCategory contains 'Weapons'

Stage 5: project

| project TimeGenerated, SrcIpAddr, DstIpAddr, UrlOriginal, Identities

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DvcActioneq
  • Allowed
field:"DvcAction" kind:eq value:"Allowed"
EventTypeeq
  • proxylogs
field:"EventType" kind:eq value:"proxylogs"
UrlCategorycontains
  • Adult Themes
  • Adware
  • Alcohol
  • Child Abuse Content
  • Drugs
  • German Youth Protection
  • Hate/Discrimination
  • Illegal Activities
  • Illegal Downloads
  • Lingerie/Bikini
  • Nudity
  • Pornography
  • Proxy/Anonymizer
  • Sexuality
  • Tasteless
  • Terrorism
  • Weapons
  • Web Spam
field:"UrlCategory" kind:contains

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
Identitiesproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

Cisco Cloud Security - URI contains IP address

#
Status
available
Severity
medium
Time window
10m
Source
github.com/Azure/Azure-Sentinel

'Malware can use IP address to communicate with C2.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control
Exfiltration

Rule Body

id: ee1818ec-5f65-4991-b711-bcf2ab7e36c3
name: Cisco Cloud Security - URI contains IP address
description: |
  'Malware can use IP address to communicate with C2.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes:
      - Cisco_Umbrella_proxy_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1071
  - T1567
query: |
  Cisco_Umbrella
  | where EventType == 'proxylogs'
  | where DvcAction =~ 'Allowed'
  | where UrlOriginal matches regex @'\Ahttp:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*'
  | project TimeGenerated, SrcIpAddr, DstIpAddr, UrlOriginal, Identities
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlOriginal
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.1.2
kind: Scheduled

Stages and Predicates

Stage 1: source

Cisco_Umbrella

Stage 2: where

| where EventType == 'proxylogs'

Stage 3: where

| where DvcAction =~ 'Allowed'

Stage 4: where

| where UrlOriginal matches regex @'\Ahttp:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*'

Stage 5: project

| project TimeGenerated, SrcIpAddr, DstIpAddr, UrlOriginal, Identities

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
Identitiesproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

Cisco Duo - Multiple users deleted

#
Status
available
Severity
medium
Time window
10m
Group by
ActorName
Source
github.com/Azure/Azure-Sentinel

'Detects when multiple users were deleted.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 6e4f9031-91d3-4fa1-8baf-624935f04ad8
name: Cisco Duo - Multiple users deleted
description: |
  'Detects when multiple users were deleted.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoActivityConnectorDefinition
    dataTypes:
      - DuoActivity_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1531
query: |
  DuoActivity_CL
  | where ActionName =~ "user_delete"
  | summarize count() by ActorName, bin(TimeGenerated, 10m)
  | where count_ > 1
  | extend AccountCustomEntity = ActorName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

DuoActivity_CL

Stage 2: where

| where ActionName =~ "user_delete"

Stage 3: summarize

| summarize count() by ActorName, bin(TimeGenerated, 10m)

Stage 4: where

| where count_ > 1

Stage 5: extend

| extend AccountCustomEntity = ActorName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorNamesummarize
AccountCustomEntityextend

Employee account deleted

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

'This rule will monitor for any employee accounts being deleted. Deleting an employee account can have a big potential impact as all of the data for that user will be removed.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 8a2cc466-342d-4ebb-8871-f9e1d83a24a5
name: Employee account deleted
description: |
  'This rule will monitor for any employee accounts being deleted.
  Deleting an employee account can have a big potential impact as all of the data for that user will be removed.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: LastPass
    dataTypes:
      - LastPassNativePoller_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
  - Impact
relevantTechniques:
 - T1485
query: |
  LastPassNativePoller_CL
  | where Action_s == "Employee Account Deleted" or Action_s == "Remove User From Company"
  | extend AccountCustomEntity = Username_s, IPCustomEntity = IP_Address_s
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

LastPassNativePoller_CL

Stage 2: where

| where Action_s == "Employee Account Deleted" or Action_s == "Remove User From Company"

Stage 3: extend

| extend AccountCustomEntity = Username_s, IPCustomEntity = IP_Address_s

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Action_seq
  • Employee Account Deleted
  • Remove User From Company
field:"Action_s" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

External Upstream Source Added to Azure DevOps Feed

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

'The detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources. An attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: adc32a33-1cd6-46f5-8801-e3ed8337885f
name: External Upstream Source Added to Azure DevOps Feed
description: |
  'The detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources. 
  An attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1199
query: |
  // Add any known allowed sources and source locations to the filter below (the NuGet Gallery has been added here as an example).
  let allowed_sources = dynamic(["NuGet Gallery"]);
  let allowed_locations = dynamic(["https://api.nuget.org/v3/index.json"]);
  ADOAuditLogs
  // Look for feeds created or modified at either the organization or project level
  | where OperationName matches regex "Artifacts.Feed.(Org|Project).Modify"
  | where Details has "UpstreamSources, added"
  | extend FeedName = tostring(Data.FeedName)
  | extend FeedId = tostring(Data.FeedId)
  | extend UpstreamsAdded = Data.UpstreamsAdded
  // As multiple feeds may be added expand these out
  | mv-expand UpstreamsAdded
  // Only focus on external feeds
  | where UpstreamsAdded.UpstreamSourceType !~ "internal"
  | extend SourceLocation = tostring(UpstreamsAdded.Location)
  | extend SourceName = tostring(UpstreamsAdded.Name)
  // Exclude sources and locations in the allow list
  | where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)
  | extend SourceProtocol = tostring(UpstreamsAdded.Protocol)
  | extend SourceStatus = tostring(UpstreamsAdded.Status)
  | project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress
  | extend timestamp = TimeGenerated
  | extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUPN
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let allowed_sources = dynamic(["NuGet Gallery"]);
let allowed_locations = dynamic(["https://api.nuget.org/v3/index.json"]);

Stage 1: source

ADOAuditLogs

Stage 2: where

| where OperationName matches regex "Artifacts.Feed.(Org|Project).Modify"

Stage 3: where

| where Details has "UpstreamSources, added"

Stage 4: extend (3 consecutive steps)

| extend FeedName = tostring(Data.FeedName)
| extend FeedId = tostring(Data.FeedId)
| extend UpstreamsAdded = Data.UpstreamsAdded

Stage 5: mv-expand

| mv-expand UpstreamsAdded

Stage 6: where

| where UpstreamsAdded.UpstreamSourceType !~ "internal"

Stage 7: extend

| extend SourceLocation = tostring(UpstreamsAdded.Location)

Stage 8: extend

| extend SourceName = tostring(UpstreamsAdded.Name)

Stage 9: where

| where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)

Stage 10: extend

| extend SourceProtocol = tostring(UpstreamsAdded.Protocol)

Stage 11: extend

| extend SourceStatus = tostring(UpstreamsAdded.Status)

Stage 12: project-reorder

| project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress

Stage 13: extend

| extend timestamp = TimeGenerated

Stage 14: extend

| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SourceLocationeqallowed_locationsexcludes:SourceLocation field:"SourceLocation" value:"allowed_locations"
SourceNameeqallowed_sourcesexcludes:SourceName field:"SourceName" value:"allowed_sources"

Output Fields

These fields are emitted when the rule matches.

FieldSource
FeedNameextend
FeedIdextend
UpstreamsAddedextend
SourceLocationextend
SourceNameextend
SourceProtocolextend
SourceStatusextend
timestampextend
AccountNameextend
AccountUPNSuffixextend

Guardian- Code Detection Policy Violation Detection

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

'This alert creates an incident when Code Detection Policy Violation detected from the Guardian.'

Rule Body

id: 90f50a0b-6903-4ddc-adcc-c13ebb77ffa7
name: Guardian- Code Detection Policy Violation Detection
description: |
  'This alert creates an incident when Code Detection Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Code Detection'
  | where Severity =~ 'Low'
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: Guardian- Code Detection Policy Violation detection
  alertDescriptionFormat: |
    This query detects Code Detection Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Code Detection'

Stage 3: where

| where Severity =~ 'Low'

Indicators

These rows show field, operator, and value matches.

Illumio VEN Clone Detection Rule

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

'Create Microsoft Sentinel Incident When A Cloned Ven Is Detected'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: b3c4b8f4-c12c-471e-9999-023c05852276
name: Illumio VEN Clone Detection Rule
description: |
  'Create Microsoft Sentinel Incident When A Cloned Ven Is Detected'
severity: High
status: Available
requiredDataConnectors: 
  - connectorId: IllumioSaaSDataConnector
    dataTypes:
      - Illumio_Auditable_Events_CL
  - connectorId: SyslogAma
    datatypes:
      - Syslog      
queryFrequency: 60m
queryPeriod: 60m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
  Illumio_Auditable_Events_CL
  | union IllumioSyslogAuditEvents  
  | where event_type has 'agent.clone_detected'
  | extend hostname = created_by.agent.hostname,
          ven_href = created_by.ven.href
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: hostname
alertDetailsOverride:
  alertDisplayNameFormat: | 
    Illumio VEN Clone Detection Incident for {{hostname}}
  alertDescriptionFormat: | 
    Illumio VEN Clone Detection for {{hostname}} generated at {{TimeGenerated}}
version: 1.0.6
kind: Scheduled

Stages and Predicates

Stage 1: source

Illumio_Auditable_Events_CL

Stage 2: union

union of 1 branches

Stage 3: source

IllumioSyslogAuditEvents

Stage 4: where

where event_type has "agent.clone_detected"

Stage 5: extend

extend hostname, ven_href

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
event_typematch
  • agent.clone_detected transforms: term
field:"event_type" kind:match value:"agent.clone_detected"

Output Fields

These fields are emitted when the rule matches.

FieldSource
hostnameextend
ven_hrefextend

Illusive Incidents Analytic Rule

#
Status
available
Severity
medium
Time window
5m
Group by
AdditionalExtensions, DeviceCustomNumber2, TimeGenerated
Source
github.com/Azure/Azure-Sentinel

'Create a Microsoft Sentinel incident upon a new Illusive alert (incident) and associate all related Illusive events to the relevant Microsoft Sentinel alert. This is done by filtering and processing Illusive Syslog messages.'

MITRE ATT&CK Coverage

Rule Body

id: 1a7dbcf6-21a2-4255-84b2-c8dbbdca4630
name: Illusive Incidents Analytic Rule
description: |
  'Create a Microsoft Sentinel incident upon a new Illusive alert (incident) and associate all related Illusive events to the relevant Microsoft Sentinel alert. This is done by filtering and processing Illusive Syslog messages.'
severity: Medium
status: Available
requiredDataConnectors: 
  - connectorId: Illusive
    dataTypes:
      - CommonSecurityLog
  - connectorId: illusiveAttackManagementSystemAma
    dataTypes:
      - CommonSecurityLog
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
  - CredentialAccess
  - LateralMovement
relevantTechniques:
  - T1078
  - T1098
  - T1548
  - T1021
query: |
  CommonSecurityLog
  | where DeviceProduct == "illusive"
  | extend DeviceCustomNumber2 = coalesce(column_ifexists("FieldDeviceCustomNumber2", long(null)),DeviceCustomNumber2)
  | summarize arg_max(TimeGenerated, *) by DeviceCustomNumber2, AdditionalExtensions, TimeGenerated
  | extend Category = extract(@'cat=([^;]+)(\;|$)', 1, AdditionalExtensions), HasForensics = extract(@'cs7=([^;]+)(\;|$)', 1, AdditionalExtensions)
  | extend Category = coalesce(column_ifexists("DeviceEventCategory",""),Category)	
  | where Category == "illusive:alerts"
  | extend IncidentId = DeviceCustomNumber2, IncidentURL = DeviceCustomString5
  | project TimeGenerated, SourceIP, SourceHostName,Computer , DeviceEventClassID ,HasForensics ,SourceUserName, Activity, DeviceAddress,  DestinationHostName, DestinationUserName, IncidentId, IncidentURL
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: SourceHostName
  - entityType: Host
    fieldMappings:
      - identifier: OMSAgentID
        columnName: Computer
customDetails:
  IllusiveIncidentId: IncidentId
  HasForensics: HasForensics
  Account: SourceUserName
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: | 
    Illusive Incident: {{IncidentId}}
  alertDescriptionFormat: | 
    Illusive Incident {{IncidentId}} generated at {{TimeGenerated}}
version: 1.0.5
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceProduct == "illusive"

Stage 3: extend

| extend DeviceCustomNumber2 = coalesce(column_ifexists("FieldDeviceCustomNumber2", long(null)),DeviceCustomNumber2)

Stage 4: summarize

| summarize arg_max(TimeGenerated, *) by DeviceCustomNumber2, AdditionalExtensions, TimeGenerated

Stage 5: extend

| extend Category = extract(@'cat=([^;]+)(\;|$)', 1, AdditionalExtensions), HasForensics = extract(@'cs7=([^;]+)(\;|$)', 1, AdditionalExtensions)

Stage 6: extend

| extend Category = coalesce(column_ifexists("DeviceEventCategory",""),Category)

Stage 7: where

| where Category == "illusive:alerts"

Stage 8: extend

| extend IncidentId = DeviceCustomNumber2, IncidentURL = DeviceCustomString5

Stage 9: project

| project TimeGenerated, SourceIP, SourceHostName,Computer , DeviceEventClassID ,HasForensics ,SourceUserName, Activity, DeviceAddress,  DestinationHostName, DestinationUserName, IncidentId, IncidentURL

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activityproject
Computerproject
DestinationHostNameproject
DestinationUserNameproject
DeviceAddressproject
DeviceEventClassIDproject
HasForensicsproject
IncidentIdproject
IncidentURLproject
SourceHostNameproject
SourceIPproject
SourceUserNameproject
TimeGeneratedproject

Pathlock TDnR - GRC Access Control Change Documents

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

Detects changes to SAP GRC (Governance, Risk, and Compliance) access control configuration, forwarded by Pathlock Threat Detection and Response. Modifications to GRC settings may indicate attempts to bypass segregation of duties controls or disable risk monitoring.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c10
name: Pathlock TDnR - GRC Access Control Change Documents
kind: Scheduled
description: >-
  Detects changes to SAP GRC (Governance, Risk, and Compliance) access control configuration,
  forwarded by Pathlock Threat Detection and Response. Modifications to GRC settings may indicate
  attempts to bypass segregation of duties controls or disable risk monitoring.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1548
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_GRAC"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "CHANGEDOC_GRAC"

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

Potential Password Spray Attack (Uses Authentication Normalization)

#
Severity
medium
Time window
5m
Group by
SrcDvcIpAddr, SrcGeoCountry
Author
Ofer Shezaf
Source
github.com/Azure/Azure-Sentinel

This query searches for failed attempts to log in from more than 15 various users within a 5 minute timeframe from the same source. This is a potential indication of a password spray attack To use this analytics rule, make sure you have deployed the ASIM normalization parsers

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 6a2e2ff4-5568-475e-bef2-b95f12b9367b
name: Potential Password Spray Attack (Uses Authentication Normalization)
description: |
  'This query searches for failed attempts to log in from more than 15 various users within a 5 minute timeframe from the same source. This is a potential indication of a password spray attack
   To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimAuthentication)'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess 
relevantTechniques:
  - T1110
tags:
  - Id: e27dd7e5-4367-4c40-a2b7-fcd7e7a8a508
    version: 1.0.0
  - Schema: ASIMAuthentication
    SchemaVersion: 0.1.0
query: |
  let FailureThreshold = 15;
  imAuthentication
  | where EventType== 'Logon' and  EventResult== 'Failure'
  // reason: creds 
  | where EventResultDetails in ('No such user or password', 'Incorrect password')
  | summarize UserCount=dcount(TargetUserId), Vendors=make_set(EventVendor), Products=make_set(EventVendor)
    , Users = make_set(TargetUserId,100) 
      by SrcDvcIpAddr, SrcGeoCountry, bin(TimeGenerated, 5m)
  | where UserCount > FailureThreshold

entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcDvcIpAddr
version: 1.1.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Ofer Shezaf
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let FailureThreshold = 15;

Stage 1: source

imAuthentication

Stage 2: where

| where EventType== 'Logon' and  EventResult== 'Failure'

Stage 3: where

| where EventResultDetails in ('No such user or password', 'Incorrect password')

Stage 4: summarize

| summarize UserCount=dcount(TargetUserId), Vendors=make_set(EventVendor), Products=make_set(EventVendor)
  , Users = make_set(TargetUserId,100) 
    by SrcDvcIpAddr, SrcGeoCountry, bin(TimeGenerated, 5m)

Stage 5: where

| where UserCount > FailureThreshold

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventResulteq
  • Failure corpus 2 (kusto 2)
field:"EventResult" kind:eq value:"Failure"
EventResultDetailsin
  • Incorrect password
  • No such user or password
field:"Status" kind:in
EventTypeeq
  • Logon corpus 2 (kusto 2)
field:"ElevatedToken" kind:eq value:"Logon"
UserCountgt
  • 15 transforms: number
field:"UserCount" kind:gt value:"15"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Productssummarize
SrcDvcIpAddrsummarize
SrcGeoCountrysummarize
UserCountsummarize
Userssummarize
Vendorssummarize

Registry Persistence via AppInit DLLs Modification

#
Status
available
Severity
medium
Time window
1h
Group by
Computer, Details, EventID, TargetObject
Source
github.com/Azure/Azure-Sentinel

Adversaries may establish persistence and/or elevate privileges by executing malicious content triggered by AppInit DLLs loaded into processes. Dynamic-link libraries (DLLs) that are specified in the AppInit_DLLs value in the Registry keys HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows or HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows are loaded by user32.dll into every process that loads user32.dll. In practice this is nearly every program, since user32.dll is a very common library. Ref: https://attack.mitre.org/techniques/T1546/010/

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 9367dff0-941d-44e2-8875-cb48570c7add
name: Registry Persistence via AppInit DLLs Modification
description: | 
  'Adversaries may establish persistence and/or elevate privileges by executing malicious content triggered by AppInit DLLs loaded into processes. 
  Dynamic-link libraries (DLLs) that are specified in the AppInit_DLLs value in the Registry keys HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows or HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows are loaded by user32.dll into every process that loads user32.dll. In practice this is nearly every program, since user32.dll is a very common library.
  Ref: https://attack.mitre.org/techniques/T1546/010/'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1546.010
query: |
  Event
  | where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID in (13)
  | parse EventData with * 'TargetObject">' TargetObject "<" * 'Details">' Details "<" * 
  | where TargetObject has "\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs"
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventID, Computer, TargetObject, Details
  | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
  - entityType: RegistryKey
    fieldMappings:
      - identifier: Key
        columnName: TargetObject
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

Event

Stage 2: where

| where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID in (13)

Stage 3: parse

| parse EventData with * 'TargetObject">' TargetObject "<" * 'Details">' Details "<" *

Stage 4: where

| where TargetObject has "\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs"

Stage 5: summarize

| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventID, Computer, TargetObject, Details

Stage 6: extend

| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDin
  • 13 corpus 4 (kusto 4)
field:"EventID" kind:in value:"13"
EventLogeq
  • Microsoft-Windows-Sysmon/Operational corpus 7 (kusto 7)
field:"EventLog" kind:eq value:"Microsoft-Windows-Sysmon/Operational"
TargetObjectmatch
  • \Windows NT\CurrentVersion\Windows\AppInit_DLLs transforms: term
field:"TargetObject" kind:match value:"\Windows NT\CurrentVersion\Windows\AppInit_DLLs"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
Detailssummarize
EndTimeUtcsummarize
EventIDsummarize
StartTimeUtcsummarize
TargetObjectsummarize
DnsDomainextend
HostNameextend

SAP BTP - Cloud Integration JDBC data source changes

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

Identifies deployment and undeployment of JDBC data source configurations in SAP Cloud Integration. JDBC data sources contain database connection credentials and configuration that enable integration flows to access backend databases. Unauthorized JDBC data source manipulation could indicate: - Attacker adding rogue database connections for data exfiltration - Credential theft by accessing stored database passwords - Modification of connection strings to redirect traffic to attacker-controlled systems

MITRE ATT&CK Coverage

TacticTechniques
Credential Access
Lateral Movement

Rule Body

id: b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e
kind: Scheduled
name: SAP BTP - Cloud Integration JDBC data source changes
description: |
  Identifies deployment and undeployment of JDBC data source configurations in SAP Cloud Integration.
  JDBC data sources contain database connection credentials and configuration that enable
  integration flows to access backend databases.
  
  Unauthorized JDBC data source manipulation could indicate:
  - Attacker adding rogue database connections for data exfiltration
  - Credential theft by accessing stored database passwords
  - Modification of connection strings to redirect traffic to attacker-controlled systems
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - LateralMovement
relevantTechniques:
  - T1552
  - T1021
query: |
  SAPBTPAuditLog_CL
  | where Category == "audit.security-events"
  | extend data_s = tostring(Message.data),
           ipAddress = tostring(Message.ip)
  | extend parsedData = parse_json(data_s)
  | extend action = tostring(parsedData.action),
           objectType = tostring(parsedData.objectType),
           objectId = tostring(parsedData.objectId)
  | where objectType == "Data Source"
  | where action in ("PasswordStore", "PasswordDelete")
  | extend normalizedAction = case(
      action == "PasswordStore", "deployed",
      action == "PasswordDelete", "undeployed",
      action
  )
  | extend MessageText = strcat("JDBC data source '", objectId, "' was ", normalizedAction)
  | project
      UpdatedOn,
      UserName,
      MessageText,
      DataSourceName = objectId,
      Action = action,
      Tenant,
      ipAddress,
      CloudApp = "SAP Cloud Integration"
  | extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipAddress
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP Cloud Integration: {{MessageText}}'
  alertDescriptionFormat: |
    {{MessageText}} by {{UserName}} from IP {{ipAddress}}.
    
    JDBC data sources contain database connection credentials. Changes to these configurations
    should be carefully reviewed.
    
    This could indicate:
    - Legitimate database configuration management
    - Unauthorized database connection configuration
    - Attacker establishing lateral movement paths to backend systems
customDetails:
  DataSourceName: DataSourceName
  Action: Action
  SourceIP: ipAddress
version: 1.0.1

Stages and Predicates

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: where

| where Category == "audit.security-events"

Stage 3: extend (3 consecutive steps)

| extend data_s = tostring(Message.data),
         ipAddress = tostring(Message.ip)
| extend parsedData = parse_json(data_s)
| extend action = tostring(parsedData.action),
         objectType = tostring(parsedData.objectType),
         objectId = tostring(parsedData.objectId)

Stage 4: where

| where objectType == "Data Source"

Stage 5: where

| where action in ("PasswordStore", "PasswordDelete")

Stage 6: extend

| extend normalizedAction = case(
    action == "PasswordStore", "deployed",
    action == "PasswordDelete", "undeployed",
    action
)
normalizedAction =
ifaction == "PasswordStore""deployed"
elifaction == "PasswordDelete""undeployed"
elseaction

Stage 7: extend

| extend MessageText = strcat("JDBC data source '", objectId, "' was ", normalizedAction)

Stage 8: project

| project
    UpdatedOn,
    UserName,
    MessageText,
    DataSourceName = objectId,
    Action = action,
    Tenant,
    ipAddress,
    CloudApp = "SAP Cloud Integration"

Stage 9: extend

| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
CloudAppproject
DataSourceNameproject
MessageTextproject
Tenantproject
UpdatedOnproject
UserNameproject
ipAddressproject
AccountNameextend
UPNSuffixextend

SAP LogServ - HANA DB - Deactivation of Audit Trail

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

Identifies deactivation of HANA DB audit log. Source Action: Deactivate Audit Log in HANA DB security defnitions. Data Sources: SAP LogServ - HANA DB (Syslog)

MITRE ATT&CK Coverage

TacticTechniques
PersistenceNo specific technique
StealthNo specific technique
Lateral MovementNo specific technique

Rule Body

id: 8fb9fb88-693f-4906-8be2-4bb9771418fc
kind: Scheduled
name: SAP LogServ - HANA DB - Deactivation of Audit Trail
description: |
  Identifies deactivation of HANA DB audit log.
  
  Source Action: Deactivate Audit Log in HANA DB security defnitions.
  
  *Data Sources: SAP LogServ - HANA DB (Syslog)*
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPLogServ
    dataTypes:
      - Syslog
queryFrequency: 10m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - LateralMovement
  - DefenseEvasion
relevantTechniques: []
query: |
  let AuditTimeAgo = 60m;
  Syslog
  | where TimeGenerated >= ago(AuditTimeAgo)
  | where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"
  | extend raw_split = split(SyslogMessage, ";")
  | extend
    event_timestamp__col_0 = tostring(raw_split[0]), 
    service_name__col_1 = tostring(raw_split[1]), 
    hostname__col_2 = tostring(raw_split[2]), 
    sid__col_3 = tostring(raw_split[3]), 
    instance_number__col_4 = tostring(raw_split[4]), 
    port_number__col_5 = tostring(raw_split[5]), 
    database_name__col_6 = tostring(raw_split[6]), 
    client_ip_address__col_7 = tostring(raw_split[7]), 
    client_name__col_8 = tostring(raw_split[8]), 
    client_process_id__col_9 = tostring(raw_split[9]), 
    client_port_number__col_10 = tostring(raw_split[10]), 
    policy_name__col_11 = tostring(raw_split[11]), 
    audit_level__col_12 = tostring(raw_split[12]), 
    audit_action__col_13 = tostring(raw_split[13]), 
    session_user__col_14 = tostring(raw_split[14]), 
    target_schema__col_15 = tostring(raw_split[15]), 
    target_object__col_16 = tostring(raw_split[16]), 
    privilege_name__col_17 = tostring(raw_split[17]), 
    grantable__col_18 = tostring(raw_split[18]), 
    role_name__col_19 = tostring(raw_split[19]), 
    target_principal__col_20 = tostring(raw_split[20]), 
    action_status__col_21 = tostring(raw_split[21]), 
    component__col_22 = tostring(raw_split[22]), 
    section__col_23 = tostring(raw_split[23]), 
    parameter__col_24 = tostring(raw_split[24]), 
    old_value__col_25 = tostring(raw_split[25]), 
    new_value__col_26 = tostring(raw_split[26]), 
    comment__col_27 = tostring(raw_split[27]), 
    executed_statement__col_28 = tostring(raw_split[28]), 
    session_id__col_29 = tostring(raw_split[29]), 
    application_user_name__col_30 = tostring(raw_split[30]), 
    role_schema_name__col_31 = tostring(raw_split[31]), 
    grantee_schema_name__col_32 = tostring(raw_split[32]), 
    origin_database_name__col_33 = tostring(raw_split[33]), 
    origin_user_name__col_34 = tostring(raw_split[34]), 
    xs_application_user_name__col_35 = tostring(raw_split[35]), 
    application_name__col_36 = tostring(raw_split[36]), 
    statement_user_name__col_37 = tostring(raw_split[37]), 
    create_time__col_38 = tostring(raw_split[38]), 
    xsa_message_ip__col_39 = tostring(raw_split[39]), 
    xsa_tenant__col_40 = tostring(raw_split[40]), 
    xsa_uuid__col_41 = tostring(raw_split[41]), 
    xsa_channel__col_42 = tostring(raw_split[42]), 
    xsa_attachment_id__col_43 = tostring(raw_split[43]), 
    xsa_attachment_name__col_44 = tostring(raw_split[44]), 
    xsa_organization_id__col_45 = tostring(raw_split[45]), 
    xsa_space_id__col_46 = tostring(raw_split[46]), 
    xsa_instance_id__col_47 = tostring(raw_split[47]), 
    xsa_binding_id__col_48 = tostring(raw_split[48]), 
    xsa_object__col_49 = tostring(raw_split[49]), 
    xsa_data_subject__col_50 = tostring(raw_split[50])
  | where 
    audit_action__col_13 contains "AUDIT CONFIGURATION" and 
    parameter__col_24 =~ "global_auditing_state" and
    new_value__col_26 =~ "false"
  | extend AlertRuleUniqueName = 'hanadb-deactivationofaudittrail-logserv'
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: sid__col_3
      - identifier: InstanceName
        columnName: database_name__col_6
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: hostname__col_2
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: client_ip_address__col_7
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP LogServ - HANA DB - Deactivation of Audit Trail'
  alertDescriptionFormat: |
    {{comment__col_27}}
customDetails:
  SAP_User: session_user__col_14
version: 1.0.1

Stages and Predicates

Parameters

let AuditTimeAgo = 60m;

Stage 1: source

Syslog

Stage 2: where

| where TimeGenerated >= ago(AuditTimeAgo)

Stage 3: where

| where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"

Stage 4: extend

| extend raw_split = split(SyslogMessage, ";")

Stage 5: extend

| extend
  event_timestamp__col_0 = tostring(raw_split[0]), 
  service_name__col_1 = tostring(raw_split[1]), 
  hostname__col_2 = tostring(raw_split[2]), 
  sid__col_3 = tostring(raw_split[3]), 
  instance_number__col_4 = tostring(raw_split[4]), 
  port_number__col_5 = tostring(raw_split[5]), 
  database_name__col_6 = tostring(raw_split[6]), 
  client_ip_address__col_7 = tostring(raw_split[7]), 
  client_name__col_8 = tostring(raw_split[8]), 
  client_process_id__col_9 = tostring(raw_split[9]), 
  client_port_number__col_10 = tostring(raw_split[10]), 
  policy_name__col_11 = tostring(raw_split[11]), 
  audit_level__col_12 = tostring(raw_split[12]), 
  audit_action__col_13 = tostring(raw_split[13]), 
  session_user__col_14 = tostring(raw_split[14]), 
  target_schema__col_15 = tostring(raw_split[15]), 
  target_object__col_16 = tostring(raw_split[16]), 
  privilege_name__col_17 = tostring(raw_split[17]), 
  grantable__col_18 = tostring(raw_split[18]), 
  role_name__col_19 = tostring(raw_split[19]), 
  target_principal__col_20 = tostring(raw_split[20]), 
  action_status__col_21 = tostring(raw_split[21]), 
  component__col_22 = tostring(raw_split[22]), 
  section__col_23 = tostring(raw_split[23]), 
  parameter__col_24 = tostring(raw_split[24]), 
  old_value__col_25 = tostring(raw_split[25]), 
  new_value__col_26 = tostring(raw_split[26]), 
  comment__col_27 = tostring(raw_split[27]), 
  executed_statement__col_28 = tostring(raw_split[28]), 
  session_id__col_29 = tostring(raw_split[29]), 
  application_user_name__col_30 = tostring(raw_split[30]), 
  role_schema_name__col_31 = tostring(raw_split[31]), 
  grantee_schema_name__col_32 = tostring(raw_split[32]), 
  origin_database_name__col_33 = tostring(raw_split[33]), 
  origin_user_name__col_34 = tostring(raw_split[34]), 
  xs_application_user_name__col_35 = tostring(raw_split[35]), 
  application_name__col_36 = tostring(raw_split[36]), 
  statement_user_name__col_37 = tostring(raw_split[37]), 
  create_time__col_38 = tostring(raw_split[38]), 
  xsa_message_ip__col_39 = tostring(raw_split[39]), 
  xsa_tenant__col_40 = tostring(raw_split[40]), 
  xsa_uuid__col_41 = tostring(raw_split[41]), 
  xsa_channel__col_42 = tostring(raw_split[42]), 
  xsa_attachment_id__col_43 = tostring(raw_split[43]), 
  xsa_attachment_name__col_44 = tostring(raw_split[44]), 
  xsa_organization_id__col_45 = tostring(raw_split[45]), 
  xsa_space_id__col_46 = tostring(raw_split[46]), 
  xsa_instance_id__col_47 = tostring(raw_split[47]), 
  xsa_binding_id__col_48 = tostring(raw_split[48]), 
  xsa_object__col_49 = tostring(raw_split[49]), 
  xsa_data_subject__col_50 = tostring(raw_split[50])

Stage 6: where

| where 
  audit_action__col_13 contains "AUDIT CONFIGURATION" and 
  parameter__col_24 =~ "global_auditing_state" and
  new_value__col_26 =~ "false"

Stage 7: extend

| extend AlertRuleUniqueName = 'hanadb-deactivationofaudittrail-logserv'

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
raw_splitextend
action_status__col_21extend
application_name__col_36extend
application_user_name__col_30extend
audit_action__col_13extend
audit_level__col_12extend
client_ip_address__col_7extend
client_name__col_8extend
client_port_number__col_10extend
client_process_id__col_9extend
comment__col_27extend
component__col_22extend
create_time__col_38extend
database_name__col_6extend
event_timestamp__col_0extend
executed_statement__col_28extend
grantable__col_18extend
grantee_schema_name__col_32extend
hostname__col_2extend
instance_number__col_4extend
new_value__col_26extend
old_value__col_25extend
origin_database_name__col_33extend
origin_user_name__col_34extend
parameter__col_24extend
policy_name__col_11extend
port_number__col_5extend
privilege_name__col_17extend
role_name__col_19extend
role_schema_name__col_31extend
section__col_23extend
service_name__col_1extend
session_id__col_29extend
session_user__col_14extend
sid__col_3extend
statement_user_name__col_37extend
target_object__col_16extend
target_principal__col_20extend
target_schema__col_15extend
xs_application_user_name__col_35extend
xsa_attachment_id__col_43extend
xsa_attachment_name__col_44extend
xsa_binding_id__col_48extend
xsa_channel__col_42extend
xsa_data_subject__col_50extend
xsa_instance_id__col_47extend
xsa_message_ip__col_39extend
xsa_object__col_49extend
xsa_organization_id__col_45extend
xsa_space_id__col_46extend
xsa_tenant__col_40extend
xsa_uuid__col_41extend
AlertRuleUniqueNameextend

SUNBURST suspicious SolarWinds child processes (Normalized Process Events)

#
Severity
medium
Time window
1d
Author
Yuval Naor
Source
github.com/Azure/Azure-Sentinel

Identifies suspicious child processes of SolarWinds.Orion.Core.BusinessLayer.dll that may be evidence of the SUNBURST backdoor References: - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f To use this analytics rule, make sure you have deployed the ASIM normalization parsers'

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 631d02df-ab51-46c1-8d72-32d0cfec0720
name: SUNBURST suspicious SolarWinds child processes (Normalized Process Events)
description: |
  Identifies suspicious child processes of SolarWinds.Orion.Core.BusinessLayer.dll that may be evidence of the SUNBURST backdoor
  References:
  - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html
  - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
  To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimProcessEvent)'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - Persistence
relevantTechniques:
  - T1059
  - T1543
tags:
  - Id: 4a3073ac-7383-48a9-90a8-eb6716183a54
    version: 1.0.0
  - Schema: ASIMProcessEvent
    SchemaVersion: 0.1.0
  - Solorigate
  - NOBELIUM

query:  |
  let excludeProcs = dynamic([@"\SolarWinds\Orion\APM\APMServiceControl.exe", @"\SolarWinds\Orion\ExportToPDFCmd.Exe", @"\SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe", @"\SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe", @"\SolarWinds\Orion\Database-Maint.exe", @"\SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe", @"\Windows\SysWOW64\WerFault.exe"]);
  imProcessCreate
  | where Process hassuffix 'solarwinds.businesslayerhost.exe'
  | where not(Process has_any (excludeProcs))
  | extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountNTDomain = tostring(split(ActorUsername, @'\')[0])
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUsername
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: AlgorithmType
      - identifier: Value
        columnName: TargetFileMD5
version: 1.1.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yuval Naor
    support:
        tier: Community
    categories:
        domains: [ "Security - 0-day Vulnerability" ]

Stages and Predicates

Let Binding: excludeProcs used in Stage 3

let excludeProcs = dynamic([@"\SolarWinds\Orion\APM\APMServiceControl.exe", @"\SolarWinds\Orion\ExportToPDFCmd.Exe", @"\SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe", @"\SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe", @"\SolarWinds\Orion\Database-Maint.exe", @"\SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe", @"\Windows\SysWOW64\WerFault.exe"]);

Stage 1: source

imProcessCreate

Stage 2: where

| where Process hassuffix 'solarwinds.businesslayerhost.exe'

Stage 3: where

| where not(Process has_any (excludeProcs))

Stage 4: extend (3 consecutive steps)

| extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountNTDomain = tostring(split(ActorUsername, @'\')[0])
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)

Stage 5: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Processends_with
  • solarwinds.businesslayerhost.exe
field:"Process" kind:ends_with value:"solarwinds.businesslayerhost.exe"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Processmatch\SolarWinds\Orion\APM\APMServiceControl.exe, \SolarWinds\Orion\ExportToPDFCmd.Exe, \SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe, \SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe, \SolarWinds\Orion\Database-Maint.exe, \SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe, \Windows\SysWOW64\WerFault.exeexcludes:Process

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend

Tenable.ad user accounts issues

#
Severity
low
Time window
2h
Group by
Severity
Source
github.com/Azure/Azure-Sentinel

'Searches for triggered Indicators of Exposures related to user accounts issues.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 4f8ed6f3-8815-437d-9462-f0def9dc70d6
name: Tenable.ad user accounts issues
description: |
    'Searches for triggered Indicators of Exposures related to user accounts issues.'
severity: Low
requiredDataConnectors:
  - connectorId: Tenable.ad
    dataTypes:
      - Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  // For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
  // Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
  // Then, create the Kusto Function with alias afad_parser
  let SeverityTable=datatable(Severity:string,Level:int) [
  "low", 1,
  "medium", 2,
  "high", 3,
  "critical", 4
  ];
  let codeNameList = datatable(Codename:string)["C-ACCOUNTS-DANG-SID-HISTORY", "C-PRE-WIN2000-ACCESS-MEMBERS", "C-PASSWORD-DONT-EXPIRE", "C-SLEEPING-ACCOUNTS", "C-DANG-PRIMGROUPID", "C-PASSWORD-NOT-REQUIRED", "C-USER-PASSWORD"];
  afad_parser
  | where MessageType == 0 and Codename in~ (codeNameList)
  | lookup kind=leftouter SeverityTable on Severity
  | order by Level
  | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let Binding: SeverityTable used in Stage 3

let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];

Let Binding: codeNameList used in Stage 2

let codeNameList = datatable(Codename:string)["C-ACCOUNTS-DANG-SID-HISTORY", "C-PRE-WIN2000-ACCESS-MEMBERS", "C-PASSWORD-DONT-EXPIRE", "C-SLEEPING-ACCOUNTS", "C-DANG-PRIMGROUPID", "C-PASSWORD-NOT-REQUIRED", "C-USER-PASSWORD"];

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 0 and Codename in~ (codeNameList)

Stage 3: kusto:lookup

lookup kind=leftouter (SeverityTable) on Severity

Stage 4: sort

sort by Level

Stage 5: extend

extend DnsDomain, HostName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

TI Map Domain entity to Dns Events (ASIM DNS Schema)

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

Identifies a match in DNS events from any Domain IOC from TI This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM DNS schema'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule Body

id: 7c1ea2e6-6210-412c-92e4-180803a741b4
name: TI Map Domain entity to Dns Events (ASIM DNS Schema)
description: |
  Identifies a match in DNS events from any Domain IOC from TI
  This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM DNS schema'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: InfobloxNIOS
    dataTypes: 
      - Syslog
  - connectorId: GCPDNSDataConnector
    dataTypes: 
      - GCP_DNS_CL
  - connectorId: NXLogDnsLogs
    dataTypes: 
      - NXLog_DNS_Server_CL
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes: 
      - Cisco_Umbrella_dns_CL
  - connectorId: Corelight
    dataTypes: 
      - Corelight_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
tags:
  - Id: 85aca4d1-5d15-4001-abd9-acb86ca1786a
    version: 1.0.0
  - Schema: ASIMDns
    SchemaVersion: 0.1.1
query: |
  let HAS_ANY_MAX = 10000;
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let DomainTIs= ThreatIntelIndicators
    // Picking up only IOC's that contain the entities we want
    //extract key part of kv pair
    | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
    | extend IndicatorId = tostring(split(Id, "--")[2])
    | where IndicatorType == "domain-name"
    | extend DomainName = ObservableValue
    | extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
    
    | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
    | extend ThreatList = tostring(parse_json(Data).indicator_types[0])
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId, ObservableValue
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil));
  let Domains = DomainTIs  |summarize NDomains=dcount(DomainName), DomainsList=make_set(DomainName) 
    | project DomainList = iff(NDomains > HAS_ANY_MAX, dynamic([]), DomainsList);
  DomainTIs
  | project-reorder *, ThreatList, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
    | join (
        _Im_Dns(starttime=ago(dt_lookBack), domain_has_any=toscalar(Domains))
        | extend DNS_TimeGenerated = TimeGenerated
  ) on $left.DomainName==$right.DnsQuery
  | where DNS_TimeGenerated < ValidUntil
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ValidUntil, Confidence, DNS_TimeGenerated, Dvc, SrcIpAddr, Domain, DnsQuery, DnsQueryType, Url
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain        
customDetails:
  LatestIndicatorTime: LatestIndicatorTime
  Description: Description
  ActivityGroupNames: ActivityGroupNames
  IndicatorId: IndicatorId
  ExpirationDateTime: ValidUntil
  ConfidenceScore: Confidence
  DNSRequestTime: DNS_TimeGenerated
  SourceIPAddress: SrcIpAddr
  DnsQuery: DnsQuery
  QueryType: DnsQueryType
version: 1.2.4
kind: Scheduled

Stages and Predicates

Parameters

let HAS_ANY_MAX = 10000;
let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let DomainTIs is inlined into the numbered stages below.

Let Binding: Domains used in Stage 10

let Domains = DomainTIs  |summarize NDomains=dcount(DomainName), DomainsList=make_set(DomainName) 
  | project DomainList = iff(NDomains > HAS_ANY_MAX, dynamic([]), DomainsList);

Stages 1 to 8 define let DomainTIs as the rule's main pipeline source. Stages 9 to 16 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: extend

| extend IndicatorId = tostring(split(Id, "--")[2])

Stage 4: where

| where IndicatorType == "domain-name"

Stage 5: extend (4 consecutive steps)

| extend DomainName = ObservableValue
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend ThreatList = tostring(parse_json(Data).indicator_types[0])

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId, ObservableValue

Stage 8: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 9: project-reorder

DomainTIs
| project-reorder *, ThreatList, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type

Stage 10: join

| join (
      _Im_Dns(starttime=ago(dt_lookBack), domain_has_any=toscalar(Domains))
      | extend DNS_TimeGenerated = TimeGenerated
) on $left.DomainName==$right.DnsQuery

Stage 11: where

where DNS_TimeGenerated < ValidUntil

Stage 12: extend

extend Description

Stage 13: extend

extend ActivityGroupNames

Stage 14: project

project ActivityGroupNames, Confidence, DNS_TimeGenerated, Description, DnsQuery, DnsQueryType, Domain, Dvc, IndicatorId, LatestIndicatorTime, SrcIpAddr, Url, ValidUntil

Stage 15: extend

extend DomainIndex, HostName

Stage 16: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(Dvc, (DomainIndex + 1))
elseDvc

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Confidenceproject
DNS_TimeGeneratedproject
Descriptionproject
DnsQueryproject
DnsQueryTypeproject
Domainproject
Dvcproject
IndicatorIdproject
LatestIndicatorTimeproject
SrcIpAddrproject
Urlproject
ValidUntilproject
DomainIndexextend
HostNameextend
HostNameDomainextend

TI Map IP entity to Cloud App Events

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

Identifies compromises and attacks and detect malicious activities in one's IP entity from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: 16a45aee-5e39-4d1b-b508-40f847c99353
name: TI Map IP entity to Cloud App Events
description: |
   'Identifies compromises and attacks and detect malicious activities in one's IP entity from TI'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - CloudAppEvents
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1d;
  let ioc_lookBack = 14d; 
  let IP_Indicators = ThreatIntelIndicators
  //extract key part of kv pair
       | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
       | where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
       | extend NetworkSourceIP = toupper(ObservableValue)
       | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
    | extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
    | extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity);
  //  | extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity);
  IP_Indicators
     | project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
    | join kind=innerunique (
    CloudAppEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_ipEntity == $right.IPAddress
      | where CloudAppEvents_TimeGenerated < ValidUntil
      | summarize CloudAppEventsTimeGenerated = argmax(CloudAppEvents_TimeGenerated, *) by Id, IPAddress
      | extend
        Description = column_ifexists("max_CloudAppEvents_TimeGenerated_Description", ""),
        ActivityGroupNames = column_ifexists("max_CloudAppEvents_TimeGenerated_ActivityGroupNames", ""),
        ThreatType = column_ifexists("max_CloudAppEvents_TimeGenerated_ThreatType", ""),
        ExpirationDateTime = column_ifexists("max_CloudAppEvents_TimeGenerated_ExpirationDateTime", ""),
        ConfidenceScore = column_ifexists("max_CloudAppEvents_TimeGenerated_ConfidenceScore", ""),
        TI_ipEntity = column_ifexists("max_CloudAppEvents_TimeGenerated_TI_ipEntity", ""),
        NetworkDestinationIP = column_ifexists("max_CloudAppEvents_TimeGenerated_NetworkDestinationIP", ""),
        NetworkSourceIP = column_ifexists("max_CloudAppEvents_TimeGenerated_NetworkSourceIP", ""),
        EmailSourceIPAddress = column_ifexists("max_CloudAppEvents_TimeGenerated_EmailSourceIpAddress", "")
      | project CloudAppEventsTimeGenerated, Description, ActivityGroupNames, Id, ThreatType, ExpirationDateTime, ConfidenceScore, TI_ipEntity, NetworkDestinationIP, NetworkSourceIP, EmailSourceIPAddress
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: TI_ipEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkDestinationIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkSourceIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: EmailSourceIPAddress
version: 1.0.8
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1d;
let ioc_lookBack = 14d;

let IP_Indicators is inlined into the numbered stages below.

Stages 1 to 10 define let IP_Indicators as the rule's main pipeline source. Stages 11 to 16 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")

Stage 4: extend

| extend NetworkSourceIP = toupper(ObservableValue)

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 8: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 9: extend

| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
TI_ipEntity =
ifisnotempty(NetworkSourceIP)NetworkSourceIP
elseNetworkSourceIP

Stage 10: extend

| extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
TIipEntity =
ifisempty(TI_ipEntity) and isnotempty(NetworkSourceIP)NetworkSourceIP
elseTI_ipEntity

Stage 11: project-reorder

IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity

Stage 12: join

| join kind=innerunique (
  CloudAppEvents
    | where TimeGenerated >= ago(dt_lookBack)
    | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_ipEntity == $right.IPAddress

Stage 13: where

where CloudAppEvents_TimeGenerated < ValidUntil

Stage 14: summarize

summarize CloudAppEventsTimeGenerated by Id, IPAddress

Stage 15: extend

extend ActivityGroupNames, ConfidenceScore, Description, EmailSourceIPAddress, ExpirationDateTime, NetworkDestinationIP, NetworkSourceIP, TI_ipEntity, ThreatType

Stage 16: project

project ActivityGroupNames, CloudAppEventsTimeGenerated, ConfidenceScore, Description, EmailSourceIPAddress, ExpirationDateTime, Id, NetworkDestinationIP, NetworkSourceIP, TI_ipEntity, ThreatType

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IndicatorTypein
  • ipv4-addr
  • ipv6-addr
  • network-traffic
field:"IndicatorType" kind:in
IsActiveeq
  • true transforms: boolean
field:"IsActive" kind:eq value:"true"
ValidUntilis_null
  • (no value, null check)
field:"ValidUntil" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
CloudAppEventsTimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIPAddressproject
ExpirationDateTimeproject
Idproject
NetworkDestinationIPproject
NetworkSourceIPproject
TI_ipEntityproject
ThreatTypeproject

TI Map IP Entity to Duo Security

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

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

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: d23ed927-5be3-4902-a9c1-85f841eb4fa1
name: TI Map IP Entity to Duo Security
description: |
  'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DuoSecurity.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: CiscoDuoSecurity
    dataTypes:
      - CiscoDuo
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  ThreatIntelligenceIndicator
  // Picking up only IOC's that contain the entities we want
  | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
  | where TimeGenerated >= ago(ioc_lookBack)
  // 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)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  | join (
      DuoSecurityAuthentication_CL
      | where TimeGenerated >= ago(dt_lookBack)
      | where isnotempty(access_device_ip_s)
      // renaming time column so it is clear the log this came from
      | extend Duo_TimeGenerated = isotimestamp_t
  )
  on $left.TI_ipEntity == $right.access_device_ip_s
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  | project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, Duo_TimeGenerated,
  TI_ipEntity, user_name_s, factor_s, result_s, application_name_s, event_type_s, txid_g, user_key_s, access_device_ip_s, access_device_location_city_s, access_device_location_state_s, access_device_location_country_s
  | extend timestamp = Duo_TimeGenerated, Name = tostring(split(user_name_s, '@', 0)[0]), UPNSuffix = tostring(split(user_name_s, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: user_name_s
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: access_device_ip_s
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

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: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 6: where

| where Active == true and ExpirationDateTime > now()

Stage 7: join

| join (
    DuoSecurityAuthentication_CL
    | where TimeGenerated >= ago(dt_lookBack)
    | where isnotempty(access_device_ip_s)
    | extend Duo_TimeGenerated = isotimestamp_t
)
on $left.TI_ipEntity == $right.access_device_ip_s

Stage 8: where

where TimeGenerated >= ago(1209600s)

Stage 9: summarize

summarize LatestIndicatorTime by IndicatorId

Stage 10: where

where Active == true

Stage 11: project

project ActivityGroupNames, ConfidenceScore, Description, Duo_TimeGenerated, ExpirationDateTime, IndicatorId, LatestIndicatorTime, TI_ipEntity, ThreatType, Url, access_device_ip_s, access_device_location_city_s, access_device_location_country_s, access_device_location_state_s, application_name_s, event_type_s, factor_s, result_s, txid_g, user_key_s, user_name_s

Stage 12: extend

extend Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
Duo_TimeGeneratedproject
ExpirationDateTimeproject
IndicatorIdproject
LatestIndicatorTimeproject
TI_ipEntityproject
ThreatTypeproject
Urlproject
access_device_ip_sproject
access_device_location_city_sproject
access_device_location_country_sproject
access_device_location_state_sproject
application_name_sproject
event_type_sproject
factor_sproject
result_sproject
txid_gproject
user_key_sproject
user_name_sproject
Nameextend
UPNSuffixextend
timestampextend

VMware vCenter - Root login

#
Status
available
Severity
high
Time window
14d
Source
github.com/Azure/Azure-Sentinel

'Detects when root user login from uncommon IP address.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Privilege Escalation

Rule Body

id: 03e8a895-b5ba-49a0-aed3-f9a997d92fbe
name: VMware vCenter - Root login
description: |
  'Detects when root user login from uncommon IP address.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - vcenter_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1078
query: |
  let p_lookback = 14d;
  let t_lookback = 1h;
  let root_ips = vCenter
  | where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
  | where EventType has_all ('UserLoginSessionEvent', 'root', 'logged in')
  | summarize make_set(SourceIP,128);
  vCenter
  | where TimeGenerated > ago(t_lookback)
  | where EventType   has_all ('UserLoginSessionEvent', 'root', 'logged in')
  | where SourceIP   !in (root_ips)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let p_lookback = 14d;
let t_lookback = 1h;

Let Binding: root_ips used in Stage 4

let root_ips = vCenter
| where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
| where EventType has_all ('UserLoginSessionEvent', 'root', 'logged in')
| summarize make_set(SourceIP,128);

Stage 1: source

vCenter

Stage 2: where

| where TimeGenerated > ago(t_lookback)

Stage 3: where

| where EventType   has_all ('UserLoginSessionEvent', 'root', 'logged in')

Stage 4: where

| where SourceIP   !in (root_ips)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypematch
  • UserLoginSessionEvent transforms: term
  • logged in transforms: term
  • root transforms: term
field:"EventType" kind:match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SourceIPeqroot_ipsexcludes:SourceIP field:"SourceIP" value:"root_ips"

VTI - High Severity SHA1 Collision Detection

#
Severity
high
Time window
1h
Group by
$left.InitiatingProcessSHA1, $right.Value
Source
github.com/Azure/Azure-Sentinel

This will alert when a collision is detected for DeviceFileEvents events with VTI high severity SHA1 IoCs

MITRE ATT&CK Coverage

TacticTechniques
Execution

Telemetry Coverage

Rule Body

id: dbd9e28f-973d-47f3-a8c3-9e18da846870
name: VTI - High Severity SHA1 Collision Detection
version: 1.0.0
kind: Scheduled
description: |-
  This will alert when a collision is detected for DeviceFileEvents events with VTI high severity SHA1 IoCs
severity: High
requiredDataConnectors:
  - connectorId: VisaThreatIntelligence
    dataTypes:
      - VisaThreatIntelligenceIOC_CL
      - DeviceFileEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |-
  DeviceFileEvents
  | join kind=inner (VisaThreatIntelligenceIOC_CL  | where IOCType == "sha1" and SourceSeverity == "High")
  on $left.InitiatingProcessSHA1 == $right.Value
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
suppressionDuration: 1h
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: DeviceName
- entityType: FileHash
  fieldMappings:
  - identifier: Algorithm
    columnName: SHA1
  - identifier: Value
    columnName: InitiatingProcessSHA1

Stages and Predicates

Stage 1: source

DeviceFileEvents

Stage 2: join

| join kind=inner (VisaThreatIntelligenceIOC_CL  | where IOCType == "sha1" and SourceSeverity == "High")
on $left.InitiatingProcessSHA1 == $right.Value

Indicators

These rows show field, operator, and value matches.

Whisper Security - BGP Route Anomaly with Traffic Spike

#
Status
available
Severity
high
Time window
1d
Group by
$left.DestinationIP, $right.indicator, DestinationIP
Source
github.com/Azure/Azure-Sentinel

Detects BGP origin AS changes for IP prefixes correlated with network traffic volume spikes. May indicate BGP hijacking or adversary-in-the-middle attacks.

MITRE ATT&CK Coverage

TacticTechniques
Collection

Rule Body

id: 1c08a7cb-7ff4-5a37-a961-39c29f8d07bd
name: Whisper Security - BGP Route Anomaly with Traffic Spike
description: |
  Detects BGP origin AS changes for IP prefixes correlated with network traffic volume spikes. May indicate BGP hijacking or adversary-in-the-middle attacks.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperHistory_CL
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1557
query: |
  // MITRE ATT&CK: T1557 - Adversary-in-the-Middle
  // Tactic: Collection
  // Detects BGP route origin changes correlated with traffic volume anomalies
  let lookbackPeriod = 1d;
  let bgpChanges = WhisperHistory_CL
      | where TimeGenerated > ago(lookbackPeriod)
      | where isnotempty(bgpOrigin)
      | serialize
      | extend PreviousOrigin = prev(bgpOrigin), PreviousIndicator = prev(indicator)
      | where indicator == PreviousIndicator
      | where bgpOrigin != PreviousOrigin
      | where isnotempty(PreviousOrigin)
      | project indicator, bgpPrefix, OldOrigin = PreviousOrigin, NewOrigin = bgpOrigin, ChangeTime = snapshotDate;
  let CommonSecurityLogSafe = union isfuzzy=true CommonSecurityLog, (datatable(TimeGenerated:datetime, DestinationIP:string, SentBytes:long, ReceivedBytes:long)[]);
  let trafficBaseline = CommonSecurityLogSafe
      | where TimeGenerated > ago(lookbackPeriod)
      | where isnotempty(DestinationIP)
      | summarize BaselineBytes = avg(SentBytes + ReceivedBytes), BaselineCount = count() by DestinationIP, bin(TimeGenerated, 1h)
      | summarize AvgBaselineBytes = avg(BaselineBytes), AvgBaselineCount = avg(BaselineCount) by DestinationIP;
  let recentTraffic = CommonSecurityLogSafe
      | where TimeGenerated > ago(15m)
      | where isnotempty(DestinationIP)
      | summarize RecentBytes = sum(SentBytes + ReceivedBytes), RecentCount = count() by DestinationIP;
  recentTraffic
      | join kind=inner (trafficBaseline) on DestinationIP
      | where RecentBytes > AvgBaselineBytes * 3 or RecentCount > AvgBaselineCount * 3
      | extend TrafficChange = iff(AvgBaselineBytes > 0, round((RecentBytes - AvgBaselineBytes) / AvgBaselineBytes * 100, 2), 0.0)
      | join kind=inner (bgpChanges) on $left.DestinationIP == $right.indicator
      | project TimeGenerated = ChangeTime, IPAddress = DestinationIP, Prefix = bgpPrefix, OriginASN = NewOrigin, OldOriginASN = OldOrigin, TrafficChange, RecentBytes, AvgBaselineBytes
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: IPAddress
customDetails:
  Prefix: Prefix
  OriginASN: OriginASN
  TrafficChange: TrafficChange
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lookbackPeriod = 1d;

let CommonSecurityLogSafe and let recentTraffic are inlined into the numbered stages below.

Let Binding: bgpChanges used in Stage 10

let bgpChanges = WhisperHistory_CL
    | where TimeGenerated > ago(lookbackPeriod)
    | where isnotempty(bgpOrigin)
    | serialize
    | extend PreviousOrigin = prev(bgpOrigin), PreviousIndicator = prev(indicator)
    | where indicator == PreviousIndicator
    | where bgpOrigin != PreviousOrigin
    | where isnotempty(PreviousOrigin)
    | project indicator, bgpPrefix, OldOrigin = PreviousOrigin, NewOrigin = bgpOrigin, ChangeTime = snapshotDate;

Let Binding: trafficBaseline used in Stage 7

let trafficBaseline = CommonSecurityLogSafe
    | where TimeGenerated > ago(lookbackPeriod)
    | where isnotempty(DestinationIP)
    | summarize BaselineBytes = avg(SentBytes + ReceivedBytes), BaselineCount = count() by DestinationIP, bin(TimeGenerated, 1h)
    | summarize AvgBaselineBytes = avg(BaselineBytes), AvgBaselineCount = avg(BaselineCount) by DestinationIP;

Stage 1: union

union of 2 branches

Stage 2: source

CommonSecurityLog

Stage 3: source

datatable(...)

Stage 4: where

where TimeGenerated > ago(900s)

Stage 5: where

where isnotempty(DestinationIP)

Stage 6: summarize

summarize RecentBytes, RecentCount by DestinationIP

Stage 7: join

join kind=inner (trafficBaseline) on DestinationIP

Stage 8: where

where (RecentBytes > (AvgBaselineBytes * 3) or RecentCount > (AvgBaselineCount * 3))

Stage 9: extend

extend TrafficChange
TrafficChange =
ifAvgBaselineBytes > 0round((((RecentBytes - AvgBaselineBytes) / AvgBaselineBytes) * 100), 2)
else0.0

Stage 10: join

join kind=inner (bgpChanges) on $left.DestinationIP, $right.indicator

Stage 11: project

project AvgBaselineBytes, IPAddress, OldOriginASN, OriginASN, Prefix, RecentBytes, TimeGenerated, TrafficChange

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DestinationIPis_not_null
  • (no value, null check)
field:"DestinationIP" kind:is_not_null
PreviousOriginis_not_null
  • (no value, null check)
field:"PreviousOrigin" kind:is_not_null
bgpOriginis_not_null
  • (no value, null check)
field:"bgpOrigin" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AvgBaselineBytesproject
IPAddressproject
OldOriginASNproject
OriginASNproject
Prefixproject
RecentBytesproject
TimeGeneratedproject
TrafficChangeproject

XbowNewAssetDiscovered

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

Alerts when a new asset is registered in XBOW for the first time. This is detected by matching assets whose CreatedAt timestamp falls within the current query window, indicating the asset was newly added rather than updated. This helps track shadow IT, new deployments, and any unexpected expansion of the external attack surface.

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: e4c6a8b2-9d7f-4285-a1e3-6b9c2e4f1a85
name: XbowNewAssetDiscovered
description: |
  Alerts when a new asset is registered in XBOW for the first time. This is detected by
  matching assets whose CreatedAt timestamp falls within the current query window,
  indicating the asset was newly added rather than updated. This helps track shadow IT,
  new deployments, and any unexpected expansion of the external attack surface.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: XbowSecurityConnector
    dataTypes:
      - XbowAssets_CL
queryFrequency: 30m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
  - Discovery
relevantTechniques:
  - T1595
query: |
  XbowAssets_CL
  | where TimeGenerated > ago(1h)
  // Only match assets created within the query window (new, not updated)
  | where todatetime(CreatedAt) > ago(1h)
  | summarize arg_max(TimeGenerated, *) by AssetId
  | project
      TimeGenerated,
      AssetId,
      AssetName,
      StartUrl,
      Lifecycle,
      Sku,
      OrganizationId,
      CreatedAt,
      UpdatedAt,
      AssetReachableState,
      AssetReachableMessage
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'XBOW New Asset: {{AssetName}}'
  alertDescriptionFormat: 'New asset registered in XBOW: {{AssetName}} ({{StartUrl}}). Reachability: {{AssetReachableState}}'
customDetails:
  AssetID: AssetId
  AssetName: AssetName
  StartUrl: StartUrl
  Lifecycle: Lifecycle
  Sku: Sku
  OrganizationID: OrganizationId
  CreatedAt: CreatedAt
  AssetReachableState: AssetReachableState
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 6h
    matchingMethod: Selected
    groupByCustomDetails:
      - AssetID
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: StartUrl
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

XbowAssets_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where todatetime(CreatedAt) > ago(1h)

Stage 4: summarize

| summarize arg_max(TimeGenerated, *) by AssetId

Stage 5: project

| project
    TimeGenerated,
    AssetId,
    AssetName,
    StartUrl,
    Lifecycle,
    Sku,
    OrganizationId,
    CreatedAt,
    UpdatedAt,
    AssetReachableState,
    AssetReachableMessage

Output Fields

These fields are emitted when the rule matches.

FieldSource
AssetIdproject
AssetNameproject
AssetReachableMessageproject
AssetReachableStateproject
CreatedAtproject
Lifecycleproject
OrganizationIdproject
Skuproject
StartUrlproject
TimeGeneratedproject
UpdatedAtproject

Back to all Kusto rules