Detection rules › Kusto

Kusto rules: 35

Awake Security - Model With Multiple Destinations

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

This query searches for devices with multiple possibly malicious destinations.

Rule Body

id: dfa3ec92-bdae-410f-b675-fe1814e4d43e
name: Awake Security - Model With Multiple Destinations
description: This query searches for devices with multiple possibly malicious destinations.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  CommonSecurityLog | where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"
  | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
    DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName
  | where array_length(DestinationIPs) > 1
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: Awake Security - Model Matches With Multiple Destinations On Device {{SourceHostName}}
  alertDescriptionFormat: |
    Device {{SourceHostName}} communicated with multiple possibly malicious destinations.  The destination IPs were:

    {{DestinationIPs}}

    The associated with Awake model(s) were:

    {{Models}}
  alertTacticsColumnName: null
  alertSeverityColumnName: null
customDetails:
  Matched_Models: Models
  Matches_ASP_URLs: ASPMatchURLs
  Device: SourceHostName
  Matches_Count: ModelMatchCount
  Matches_Max_Severity: MaxSeverity
  Matches_Dest_IPs: DestinationIPs
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SourceHostName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIPs
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: true
    lookbackDuration: 3d
    matchingMethod: Selected
    groupByEntities:
      - Host
    groupByAlertDetails: []
    groupByCustomDetails:
      - Device
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"

Stage 3: summarize

| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
  DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName

Stage 4: where

| where array_length(DestinationIPs) > 1

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ASPMatchURLssummarize
DestinationIPssummarize
EndTimesummarize
MaxSeveritysummarize
ModelMatchCountsummarize
Modelssummarize
SourceHostNamesummarize
SourceIPssummarize
StartTimesummarize

Box - Item shared to external entity

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

'Detects when an item was shared to external entity.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 3b803560-f8a6-4db4-89cb-617d89724ba1
name: Box - Item shared to external entity
description: |
  'Detects when an item was shared to external entity.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BoxDataConnector
    dataTypes:
      - BoxEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1537
query: |
  BoxEvents
  | where EventType =~ 'COLLABORATION_INVITE'
  | extend corp_domain = tolower(extract(@'@(.*)', 1, SrcUserUpn))
  | extend accessibleby_domain = tolower(extract(@'@(.*)', 1, AccessibleByLogin))
  | where corp_domain != accessibleby_domain
  | extend AccountCustomEntity = SrcUserUpn
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BoxEvents

Stage 2: where

| where EventType =~ 'COLLABORATION_INVITE'

Stage 3: extend

| extend corp_domain = tolower(extract(@'@(.*)', 1, SrcUserUpn))

Stage 4: extend

| extend accessibleby_domain = tolower(extract(@'@(.*)', 1, AccessibleByLogin))

Stage 5: where

| where corp_domain != accessibleby_domain

Stage 6: extend

| extend AccountCustomEntity = SrcUserUpn

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypeeq
  • COLLABORATION_INVITE
field:"EventType" kind:eq value:"COLLABORATION_INVITE"

Output Fields

These fields are emitted when the rule matches.

FieldSource
corp_domainextend
accessibleby_domainextend
AccountCustomEntityextend

Box - Many items deleted by user

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

'Detects when a user deletes many items in short period of time.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 1b212329-6f2c-46ca-9071-de3464f3d88d
name: Box - Many items deleted by user
description: |
  'Detects when a user deletes many items in short period of time.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BoxDataConnector
    dataTypes:
      - BoxEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
query: |
  let threshold = 100;
  BoxEvents
  | where EventType =~ 'DELETE'
  | summarize deleted_items = dcount(SourceItemName) by SrcUserName, bin(EventEndTime, 5m)
  | where deleted_items > threshold
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 100;

Stage 1: source

BoxEvents

Stage 2: where

| where EventType =~ 'DELETE'

Stage 3: summarize

| summarize deleted_items = dcount(SourceItemName) by SrcUserName, bin(EventEndTime, 5m)

Stage 4: where

| where deleted_items > threshold

Stage 5: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUserNamesummarize
deleted_itemssummarize
AccountCustomEntityextend

CloudNGFW By Palo Alto Networks - possible internal to external port scanning

#
Status
available
Severity
low
Time window
1h
Group by
Action, Application, DestinationIP, DestinationPort, FirewallName_s, Protocol, Reason, SourceIP, ident_s
Source
github.com/Azure/Azure-Sentinel

'Identifies a list of internal Source IPs (10.x.x.x Hosts) that have triggered 10 or more non-graceful tcp server resets from one or more Destination IPs which results in an "app = incomplete" designation. The server resets coupled with an "Incomplete" app designation can be an indication of internal to external port scanning or probing attack. References: https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClUvCAK https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClTaCAK'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 5b72f527-e3f6-4a00-9908-8e4fee14da9f
name: CloudNGFW By Palo Alto Networks - possible internal to external port scanning
description: |
  'Identifies a list of internal Source IPs (10.x.x.x Hosts) that have triggered 10 or more non-graceful tcp server resets from one or more Destination IPs which results in an "app = incomplete" designation. The server resets coupled with an "Incomplete" app designation can be an indication of internal to external port scanning or probing attack.
  References: https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClUvCAK
  https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClTaCAK'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: AzureCloudNGFWByPaloAltoNetworks
    dataTypes:
      - fluentbit_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1046
query: |
  fluentbit_CL
  | where ident_s == "TRAFFIC"
  | where isnotempty(FirewallName_s)
  | extend message = parse_json(Message)
  | extend DestinationPort = tostring(message.dport)
  | extend SourceIP = tostring(message.src_ip)
  | extend DestinationIP = tostring(message.dst_ip)
  | extend Application = tostring(message.app)
  | extend Protocol = tostring(message.proto)
  | extend Action = tostring(message.action)
  | where isnotempty(DestinationPort) and message.action !in ("reset-both", "deny")
  | where DestinationPort !in ("443", "53", "389", "80", "0", "880", "8888", "8080")
  | where message.app == "incomplete"
  | where toint(DestinationPort) !between (49512 .. 65535)
  | where message.dst_ip !startswith "10."
  | extend Reason = coalesce(column_ifexists("Reason", ""), tostring(message.session_end_reason), "")
  | where Reason !has "aged-out"
  | where Reason !has "tcp-fin"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by FirewallName_s, SourceIP, Application, Reason, DestinationPort, Protocol, ident_s, Action, DestinationIP
  | where count_ >= 10
  | summarize StartTime = min(StartTime), EndTime = max(EndTime), makeset(DestinationIP), totalcount = sum(count_) by FirewallName_s, SourceIP, Application, Reason, DestinationPort, Protocol, ident_s, Action
  | extend IPAddress = SourceIP
  | extend HostName = tostring(split(FirewallName_s, ".")[0]), DomainIndex = toint(indexof(FirewallName_s, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(FirewallName_s, DomainIndex + 1), FirewallName_s)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: FirewallName_s
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.6
kind: Scheduled

Stages and Predicates

Stage 1: source

fluentbit_CL

Stage 2: where

| where ident_s == "TRAFFIC"

Stage 3: where

| where isnotempty(FirewallName_s)

Stage 4: extend (7 consecutive steps)

| extend message = parse_json(Message)
| extend DestinationPort = tostring(message.dport)
| extend SourceIP = tostring(message.src_ip)
| extend DestinationIP = tostring(message.dst_ip)
| extend Application = tostring(message.app)
| extend Protocol = tostring(message.proto)
| extend Action = tostring(message.action)

Stage 5: where

| where isnotempty(DestinationPort) and message.action !in ("reset-both", "deny")

Stage 6: where

| where DestinationPort !in ("443", "53", "389", "80", "0", "880", "8888", "8080")

Stage 7: where

| where message.app == "incomplete"

Stage 8: where

| where toint(DestinationPort) !between (49512 .. 65535)

Stage 9: where

| where message.dst_ip !startswith "10."

Stage 10: extend

| extend Reason = coalesce(column_ifexists("Reason", ""), tostring(message.session_end_reason), "")

Stage 11: where

| where Reason !has "aged-out"

Stage 12: where

| where Reason !has "tcp-fin"

Stage 13: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by FirewallName_s, SourceIP, Application, Reason, DestinationPort, Protocol, ident_s, Action, DestinationIP

Stage 14: where

| where count_ >= 10

Stage 15: summarize

| summarize StartTime = min(StartTime), EndTime = max(EndTime), makeset(DestinationIP), totalcount = sum(count_) by FirewallName_s, SourceIP, Application, Reason, DestinationPort, Protocol, ident_s, Action

Stage 16: extend (3 consecutive steps)

| extend IPAddress = SourceIP
| extend HostName = tostring(split(FirewallName_s, ".")[0]), DomainIndex = toint(indexof(FirewallName_s, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(FirewallName_s, DomainIndex + 1), FirewallName_s)

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsummarize
Applicationsummarize
DestinationPortsummarize
EndTimesummarize
FirewallName_ssummarize
Protocolsummarize
Reasonsummarize
SourceIPsummarize
StartTimesummarize
ident_ssummarize
totalcountsummarize
IPAddressextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Cross-tenant Access Settings Organization Inbound Direct Settings Changed

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

Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when Organization Inbound Direct Settings are changed for "Users & Groups" and for "Applications".

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 276d5190-38de-4eb2-9933-b3b72f4a5737
name: Cross-tenant Access Settings Organization Inbound Direct Settings Changed
description: |
  'Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when Organization Inbound Direct Settings are changed for "Users & Groups" and for "Applications".'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Persistence
  - Discovery
relevantTechniques:
  - T1078.004
  - T1136.003
  - T1087.004
query: |
  // In User & Groups and in Applications, the following "AccessType" values in columns PremodifiedInboundSettings and ModifiedInboundSettings are interpreted accordingly
  // When Access Type in premodified inbound settings value was 1 that means that the initial access was allowed. When Access Type in premodified inbound settings value was 2 that means that the initial access was blocked.
  // When Access Type in modified inbound settings value is 1 that means that now access is allowed. When Access Type in modified inbound settings value is 2 that means that now access is blocked.
  AuditLogs
  | where OperationName has "Update a partner cross-tenant access setting"
  | mv-apply TargetResource = TargetResources on
    (
        where TargetResource.type =~ "Policy"
        | extend Properties = TargetResource.modifiedProperties
    )
  | mv-apply Property = Properties on
    (
        where Property.displayName =~ "b2bDirectConnectInbound"
        | extend PremodifiedInboundSettings = trim('"',tostring(Property.oldValue)),
                 ModifiedInboundSettings = trim(@'"',tostring(Property.newValue))
    )
  | where PremodifiedInboundSettings != ModifiedInboundSettings
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName has "Update a partner cross-tenant access setting"

Stage 3: kusto:mv-apply

| mv-apply TargetResource = TargetResources on
  (
      where TargetResource.type =~ "Policy"
      | extend Properties = TargetResource.modifiedProperties
  )

Stage 4: kusto:mv-apply

| mv-apply Property = Properties on
  (
      where Property.displayName =~ "b2bDirectConnectInbound"
      | extend PremodifiedInboundSettings = trim('"',tostring(Property.oldValue)),
               ModifiedInboundSettings = trim(@'"',tostring(Property.newValue))
  )

Stage 5: where

| where PremodifiedInboundSettings != ModifiedInboundSettings

Stage 6: extend (6 consecutive steps)

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNamematch
  • Update a partner cross-tenant access setting transforms: term corpus 4 (kusto 4)
field:"OperationName" kind:match value:"Update a partner cross-tenant access setting"
Property.displayNameeq
  • b2bDirectConnectInbound transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"b2bDirectConnectInbound"
TargetResource.typeeq
  • Policy transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"Policy"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

Cyble Vision Alerts OT/ICS Threat Activity Detected

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'This alert indicates detection of OT/ICS-related network activity involving industrial control protocols (e.g., IEC104). May indicate probing, reconnaissance, or attempted access against critical infrastructure assets.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery
Collection

Rule Body

id: c1ebc79d-7f46-429e-bf2c-8bb0b75ba6b2
name: Cyble Vision Alerts OT/ICS Threat Activity Detected
description: |
  'This alert indicates detection of OT/ICS-related network activity involving industrial control protocols (e.g., IEC104). May indicate probing, reconnaissance, or attempted access against critical infrastructure assets.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Discovery
- Collection
relevantTechniques:
- T0842
- T0830
query: |
  Alerts_ot_ics  
  | where Service == "ot_ics" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: OT/ICS Activity Detected from {{OT_SourceIP}}
  description: |
    Cyble detected industrial control system related traffic involving protocol {{OT_DataType}} from IP {{OT_SourceIP}}. Destination port {{OT_DestPort}}. Country. {{OT_Country}}. This may indicate reconnaissance or hostile probing of OT/ICS infrastructure.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  OT_ASN: OT_ASN
  OT_Country: OT_Country
  OT_SourceIP: OT_SourceIP
  OT_DataType: OT_DataType
  OT_DestPort: OT_DestPort
  OT_IP_Reputation: OT_IP_Reputation
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: OT_SourceIP
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_ot_ics

Stage 2: where

| where Service == "ot_ics"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - Medium severity Malicious Network Indicators with Monitor Action Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"This rule detects Network indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role. These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."

MITRE ATT&CK Coverage

Rule Body

id: 52c2f8d4-1dc8-4141-9152-614c036390a0
name: CYFIRMA - Medium severity Malicious Network Indicators with Monitor Action Rule
description:  |
  "This rule detects Network indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role. 
  These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Network Indicators with Monitor Action 
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Reconnaissance
  - Impact
relevantTechniques:
  - T1566
  - T1189
  - T1204
  - T1595
  - T1498
  - T1566.002
  - T1204.001
  - T1595.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Network Indicators with Monitor Action Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))

Stage 3: extend (6 consecutive steps)

| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend 
    extension_id = extensionKeyStr,
    ASN_Owner = props.asn_owner,
    ASN = props.asn,
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 6: project

| project
    IPv4,
    IPv6,
    URL,
    Domain,
    ThreatActors,
    RecommendedActions,
    Sources,
    Roles,
    Country,
    IPAbuse,
    name,
    Description,
    ConfidenceScore,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    SecurityVendors,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

Cynerio - Medical device scanning

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

Medical device is scanned with vulnerability scanner

MITRE ATT&CK Coverage

Rule Body

id: 211e9f49-3fca-4598-bc6e-e2c28d86e72c
name: Cynerio - Medical device scanning
description: Medical device is scanned with vulnerability scanner
severity: Medium
requiredDataConnectors:
  - connectorId: CynerioSecurityEvents
    dataTypes:
      - CynerioEvent_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T0866
query: |
  CynerioEvent_CL
  | where TimeGenerated > ago(24h)
  | where module_s == 'IDS'
  | where title_s == 'Scanner Activity'
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: asset_ip_s
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails: 
  DeviceType: asset_type_s
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CynerioEvent_CL

Stage 2: where

| where TimeGenerated > ago(24h)

Stage 3: where

| where module_s == 'IDS'

Stage 4: where

| where title_s == 'Scanner Activity'

Indicators

These rows show field, operator, and value matches.

Dataverse - Mass deletion of records

#
Status
available
Severity
medium
Time window
14d
Group by
ClientIp, EntityName, InstanceUrl, TotalEvents, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies large scale record delete operations based on a predefined threshold and also detects scheduled bulk deletion jobs.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 716cf6d4-97ad-407b-923e-6790083acb58
kind: Scheduled
name: Dataverse - Mass deletion of records
description: Identifies large scale record delete operations based on a predefined
  threshold and also detects scheduled bulk deletion jobs.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
query: |
  let mass_delete_threshold = 10000;
  let query_frequency = 1d;
  let delete_activities = DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "Delete";
  union
      (
      delete_activities
      | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl
      | where TotalEvents > mass_delete_threshold
      | join kind=inner (
          delete_activities
          | summarize DeleteCount = count() by UserId, InstanceUrl, ClientIp, EntityName)
          on UserId, InstanceUrl
      | extend Entities = bag_pack("Entity", EntityName, "Count", DeleteCount)
      | summarize Details = make_set(Entities, 100), FirstEvent = min(FirstEvent) by UserId, InstanceUrl, ClientIp, TotalEvents
      ),
      (
      DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "BulkDelete"
      | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl, ClientIp
      | extend Details = todynamic("Bulk delete scheduled")
      )
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      FirstEvent,
      UserId,
      ClientIp,
      TotalEvents,
      Details,
      InstanceUrl,
      CloudAppId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - mass deletion or bulk deletion job detected
    in {{InstanceUrl}} '
  alertDescriptionFormat: '{{UserId}} triggered the mass deletion detection with the
    following information: {{Details}}'
version: 3.2.0

Stages and Predicates

Parameters

let mass_delete_threshold = 10000;
let query_frequency = 1d;

Union (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: delete_activities, DataverseActivity

Leg 1: delete_activities

delete_activities
    | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl
    | where TotalEvents > mass_delete_threshold
    | join kind=inner (
        delete_activities
        | summarize DeleteCount = count() by UserId, InstanceUrl, ClientIp, EntityName)
        on UserId, InstanceUrl
    | extend Entities = bag_pack("Entity", EntityName, "Count", DeleteCount)
    | summarize Details = make_set(Entities, 100), FirstEvent = min(FirstEvent) by UserId, InstanceUrl, ClientIp, TotalEvents

Leg 2: DataverseActivity

DataverseActivity
    | where TimeGenerated >= ago(query_frequency)
    | where Message == "BulkDelete"
    | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl, ClientIp
    | extend Details = todynamic("Bulk delete scheduled")

Applied to the Combined Result

| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])
| project
    FirstEvent,
    UserId,
    ClientIp,
    TotalEvents,
    Details,
    InstanceUrl,
    CloudAppId,
    AccountName,
    UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Messageeq
  • BulkDelete
  • Delete
field:"Message" kind:eq
TotalEventsgt
  • 10000 transforms: number
field:"TotalEvents" kind:gt value:"10000"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIpsummarize
EntityNamesummarize
InstanceUrlsummarize
UserIdsummarize

Failed sign-ins into LastPass due to MFA

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

This rule will check if a sign-in failed into LastPass due to MFA. An incident can indicate the potential brute forcing of a LastPass account. The use of MFA is identified by combining the sign-in logs, this rule assumes LastPass is federated to Entra ID.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 760b8467-e6cc-4006-9149-5696845c1a54
name: Failed sign-ins into LastPass due to MFA
description: |
  'This rule will check if a sign-in failed into LastPass due to MFA.
  An incident can indicate the potential brute forcing of a LastPass account. 
  The use of MFA is identified by combining the sign-in logs, this rule assumes LastPass is federated to Entra ID.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: LastPass
    dataTypes:
      - LastPassNativePoller_CL
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
  - InitialAccess
relevantTechniques:
 - T1078
 - T1190
query: |
  LastPassNativePoller_CL
  | where Action_s == "Log in"
  | join (SigninLogs | where AppDisplayName == "LastPass Enterprise") on $left.IP_Address_s == $right.IPAddress and $left.Username_s == $right.UserPrincipalName
  | where ResultType in (50074, 50076)
  | 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.1
kind: Scheduled

Stages and Predicates

Stage 1: source

LastPassNativePoller_CL

Stage 2: where

| where Action_s == "Log in"

Stage 3: join

| join (SigninLogs | where AppDisplayName == "LastPass Enterprise") on $left.IP_Address_s == $right.IPAddress and $left.Username_s == $right.UserPrincipalName

Stage 4: where

where ResultType in (50074, 50076)

Stage 5: extend

extend AccountCustomEntity, IPCustomEntity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

Flare google dork results

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

'This query searches for Google dork search results that indicate publicly accessible information about your organization.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 9cb7c337-f174-4af6-b0e8-b6b7552d762d
name: Flare google dork results
description: |
    'This query searches for Google dork search results that indicate publicly accessible information about your organization.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Flare
    dataTypes:
      - FireworkV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
relevantTechniques:
  - T1593
query: |
  FireworkV2_CL
  | where notempty(uid) and RiskScore >= 3
  | extend index_name = split(uid, "/")[0]
  | extend category_name = split(uid, "/")[1]
  | where (index_name == "driller_google") or (index_name == "driller" and category_name contains "google")
version: 3.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

FireworkV2_CL

Stage 2: where

| where notempty(uid) and RiskScore >= 3

Stage 3: extend

| extend index_name = split(uid, "/")[0]

Stage 4: extend

| extend category_name = split(uid, "/")[1]

Stage 5: where

| where (index_name == "driller_google") or (index_name == "driller" and category_name contains "google")

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
index_nameextend
category_nameextend

Guardian- Security Integrity Checks Prompt Injection Policy Violation Detection

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

'This alert creates an incident when Security Integrity Checks Prompt Injection Policy Violation detected from the Guardian.'

Rule Body

id: d6b3372a-0fc4-40ec-9630-b96efb527fba
name: Guardian- Security Integrity Checks Prompt Injection Policy Violation Detection
description: |
  'This alert creates an incident when Security Integrity Checks Prompt Injection Policy Violation detected from the Guardian.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Prompt Injection / Jailbreaks'
  | where Severity =~ 'High'
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- Security Integrity Checks Prompt Injection Policy Violation detection
  alertDescriptionFormat: |
    This query detects Security Integrity Checks Prompt Injection Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Prompt Injection / Jailbreaks'

Stage 3: where

| where Severity =~ 'High'

Indicators

These rows show field, operator, and value matches.

Host Settings Updated

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

Detects when host settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: ac1a85f1-0e3f-4ae8-9f59-a240fc04b03f
name: Host Settings Updated
description: Detects when host settings are updated in Veeam Backup & Replication.
  This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28400\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n  MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 28400

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
  MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

Jira - User removed from group

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

'Detects when a user was removed from group.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: c13ecb19-4317-4d87-9a1c-52660dd44a7d
name: Jira - User removed from group
description: |
  'Detects when a user was removed from group.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: JiraAuditAPI
    dataTypes:
      - JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1531
query: |
  JiraAudit
  | where EventMessage =~ 'User removed from group'
  | extend user = todynamic(AssociatedItems)[0]['name']
  | project EventCreationTime, ObjectItemName, user
  | extend AccountCustomEntity = user
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

JiraAudit

Stage 2: where

| where EventMessage =~ 'User removed from group'

Stage 3: extend

| extend user = todynamic(AssociatedItems)[0]['name']

Stage 4: project

| project EventCreationTime, ObjectItemName, user

Stage 5: extend

| extend AccountCustomEntity = user

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCreationTimeproject
ObjectItemNameproject
userproject
AccountCustomEntityextend

Lumen TI IPAddress in DeviceEvents

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

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

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: fa566691-42a2-4136-6a8b-ffa3ea510000
name: Lumen TI IPAddress in DeviceEvents
displayName: Lumen TI IPAddress in DeviceEvents
description: |
  This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DeviceEvents.
severity: Medium
requiredDataConnectors:
  - connectorId: LumenThreatFeedConnector
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceUploadIndicatorsAPI
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 4h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: true
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1d;  // Data lookback for DeviceEvents
  let ioc_lookBack = 14d; // TI lookback
  let IP_Indicators = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack)
    | where IsActive == true and ValidUntil > now()
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
    | where SourceSystem == 'Lumen'
    | where ObservableKey == 'ipv4-addr:value'
    | extend TI_ipEntity = ObservableValue
    | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.';
  IP_Indicators
  | join kind=innerunique (
      DeviceEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | extend DE_ipEntity = RemoteIP
      | extend DeviceEvents_TimeGenerated = TimeGenerated
    ) on $left.TI_ipEntity == $right.DE_ipEntity
  | where DeviceEvents_TimeGenerated < ValidUntil
  | summarize arg_max(DeviceEvents_TimeGenerated, *), StartTime = min(DeviceEvents_TimeGenerated), EndTime = max(DeviceEvents_TimeGenerated) by Id, DE_ipEntity
  | project timestamp = EndTime, StartTime, EndTime, DeviceName, RemoteIP, ActionType, ReportId, Id, Tags, ValidUntil, Confidence, TI_ipEntity, DE_ipEntity, Type
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DE_ipEntity
version: 1.0.0
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 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 to 12 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: where

| where IsActive == true and ValidUntil > now()

Stage 4: summarize

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

Stage 5: where

| where SourceSystem == 'Lumen'

Stage 6: where

| where ObservableKey == 'ipv4-addr:value'

Stage 7: extend

| extend TI_ipEntity = ObservableValue

Stage 8: where

| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.'

Stage 9: join

IP_Indicators
| join kind=innerunique (
    DeviceEvents
    | where TimeGenerated >= ago(dt_lookBack)
    | extend DE_ipEntity = RemoteIP
    | extend DeviceEvents_TimeGenerated = TimeGenerated
  ) on $left.TI_ipEntity == $right.DE_ipEntity

Stage 10: where

where DeviceEvents_TimeGenerated < ValidUntil

Stage 11: summarize

summarize EndTime, StartTime by Id, DE_ipEntity

Stage 12: project

project ActionType, Confidence, DE_ipEntity, DeviceName, EndTime, Id, RemoteIP, ReportId, StartTime, TI_ipEntity, Tags, Type, ValidUntil, timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IsActiveeq
  • true corpus 12 (kusto 12)
field:"IsActive" kind:eq value:"true"
ObservableKeyeq
  • ipv4-addr:value corpus 10 (kusto 10)
field:"ObservableKey" kind:eq value:"ipv4-addr:value"
SourceSystemeq
  • Lumen corpus 8 (kusto 8)
field:"SourceSystem" kind:eq value:"Lumen"

Exclusions

The rule actively suppresses these predicates.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActionTypeproject
Confidenceproject
DE_ipEntityproject
DeviceNameproject
EndTimeproject
Idproject
RemoteIPproject
ReportIdproject
StartTimeproject
TI_ipEntityproject
Tagsproject
Typeproject
ValidUntilproject
timestampproject

Netskope - Large Outbound Data Transfer / Sensitive Upload (DLP)

#
Status
available
Severity
high
Time window
1h
Group by
CsUsername, XCCountry, XCDevice
Source
github.com/Azure/Azure-Sentinel

Detects large outbound data transfers and sensitive file uploads. Monitors for potential data exfiltration via cloud applications.

MITRE ATT&CK Coverage

Rule Body

id: 71e6586e-0d3f-4e33-b390-faa50b5e08fa
name: Netskope - Large Outbound Data Transfer / Sensitive Upload (DLP)
description: |
  Detects large outbound data transfers and sensitive file uploads. Monitors for potential data exfiltration via cloud applications.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
  - T1048
query: |
  let uploadThresholdMB = 100;
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername)
  | where XCsAppActivity =~ 'Upload' or XCsAppActivity =~ 'Post' or XCsAppActivity =~ 'Share' 
      or CsMethod =~ 'POST' or CsMethod =~ 'PUT'
  | where CsBytes > 0 or XRsFileSize > 0
  | extend UploadBytes = coalesce(CsBytes, XRsFileSize, 0)
  | summarize 
      TotalUploadBytes = sum(UploadBytes),
      FileCount = dcount(XCsAppObjectName),
      Files = make_set(XCsAppObjectName, 10),
      FileTypes = make_set(XRsFileType),
      Apps = make_set(XCsApp),
      Destinations = make_set(XCsAppInstanceName),
      Countries = make_set(XSCountry)
      by CsUsername, XCDevice, XCCountry, bin(TimeGenerated, 1h)
  | extend TotalUploadMB = round(TotalUploadBytes / 1048576.0, 2)
  | where TotalUploadMB > uploadThresholdMB
  | extend AlertSeverity = case(
      TotalUploadMB > 1000, 'Critical',
      TotalUploadMB > 500, 'High',
      TotalUploadMB > 100, 'Medium',
      'Low')
  | project 
      TimeGenerated,
      User = CsUsername,
      UploadVolumeMB = TotalUploadMB,
      FileCount,
      Files,
      FileTypes,
      Applications = Apps,
      DestinationInstances = Destinations,
      SourceCountry = XCCountry,
      DestinationCountries = Countries,
      Device = XCDevice,
      AlertSeverity
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let uploadThresholdMB = 100;

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername)

Stage 4: where

| where XCsAppActivity =~ 'Upload' or XCsAppActivity =~ 'Post' or XCsAppActivity =~ 'Share' 
    or CsMethod =~ 'POST' or CsMethod =~ 'PUT'

Stage 5: where

| where CsBytes > 0 or XRsFileSize > 0

Stage 6: extend

| extend UploadBytes = coalesce(CsBytes, XRsFileSize, 0)

Stage 7: summarize

| summarize 
    TotalUploadBytes = sum(UploadBytes),
    FileCount = dcount(XCsAppObjectName),
    Files = make_set(XCsAppObjectName, 10),
    FileTypes = make_set(XRsFileType),
    Apps = make_set(XCsApp),
    Destinations = make_set(XCsAppInstanceName),
    Countries = make_set(XSCountry)
    by CsUsername, XCDevice, XCCountry, bin(TimeGenerated, 1h)

Stage 8: extend

| extend TotalUploadMB = round(TotalUploadBytes / 1048576.0, 2)

Stage 9: where

| where TotalUploadMB > uploadThresholdMB

Stage 10: extend

| extend AlertSeverity = case(
    TotalUploadMB > 1000, 'Critical',
    TotalUploadMB > 500, 'High',
    TotalUploadMB > 100, 'Medium',
    'Low')
AlertSeverity =
ifTotalUploadMB > 1000'Critical'
elifTotalUploadMB > 500'High'
elifTotalUploadMB > 100'Medium'
else'Low'

Stage 11: project

| project 
    TimeGenerated,
    User = CsUsername,
    UploadVolumeMB = TotalUploadMB,
    FileCount,
    Files,
    FileTypes,
    Applications = Apps,
    DestinationInstances = Destinations,
    SourceCountry = XCCountry,
    DestinationCountries = Countries,
    Device = XCDevice,
    AlertSeverity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertSeverityproject
Applicationsproject
DestinationCountriesproject
DestinationInstancesproject
Deviceproject
FileCountproject
FileTypesproject
Filesproject
SourceCountryproject
TimeGeneratedproject
UploadVolumeMBproject
Userproject

PaloAlto - File type changed

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

'Detects when file type changed.'

MITRE ATT&CK Coverage

Rule Body

id: 9150ad68-51c8-11ec-bf63-0242ac130002
name: PaloAlto - File type changed
description: |
  'Detects when file type changed.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  PaloAltoCDLEvent
  | where EventResourceId =~ 'THREAT'
  | where EventResult =~ 'file'
  | where FileType != OldFileType
  | extend FileCustomEntity = SrcFileName, AccountCustomEntity = DstUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

PaloAltoCDLEvent

Stage 2: where

| where EventResourceId =~ 'THREAT'

Stage 3: where

| where EventResult =~ 'file'

Stage 4: where

| where FileType != OldFileType

Stage 5: extend

| extend FileCustomEntity = SrcFileName, AccountCustomEntity = DstUsername

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
FileCustomEntityextend

Phishing link click observed in Network Traffic

#
Severity
medium
Time window
1d
Group by
ProductName, SystemAlertId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'The purpose of this content is to identify successful phishing links accessed by users. Once a user clicks on a phishing link, we observe successful network activity originating from non-Microsoft network devices. These devices may include Palo Alto Networks, Fortinet, Check Point, and Zscaler devices.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 2fed0668-6d43-4c78-87e6-510f96f12145
name: Phishing link click observed in Network Traffic
description: |
  'The purpose of this content is to identify successful phishing links accessed by users. Once a user clicks on a phishing link, we observe successful network activity originating from non-Microsoft network devices. These devices may include Palo Alto Networks, Fortinet, Check Point, and Zscaler devices.'
severity: Medium
requiredDataConnectors:
  - connectorId:  OfficeATP
    dataTypes:
    - SecurityAlert
  - connectorId:  PaloAltoNetworks
    dataTypes:
    - CommonSecurityLog (PaloAlto)
  - connectorId:  Fortinet
    dataTypes:
    - CommonSecurityLog (Fortinet)
  - connectorId:  CheckPoint
    dataTypes:
    - CommonSecurityLog (CheckPoint)
  - connectorId:  Zscaler
    dataTypes:
    - CommonSecurityLog (Zscaler)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
    //Finding MDO Security alerts and extracting the Entities user, Domain, Ip, and URL.
    let Alert_List= dynamic([
    "Phishing link click observed in Network Traffic",
    "Phish delivered due to an IP allow policy",
    "A potentially malicious URL click was detected",
    "High Risk Sign-in Observed in Network Traffic",
    "A user clicked through to a potentially malicious URL",
    "Suspicious network connection to AitM phishing site",
    "Messages containing malicious entity not removed after delivery",
    "Email messages containing malicious URL removed after delivery",
    "Email reported by user as malware or phish",
    "Phish delivered due to an ETR override",
    "Phish not zapped because ZAP is disabled"]);
    SecurityAlert
    |where ProviderName in~ ("Office 365 Advanced Threat Protection", "OATP")
    | where AlertName in~ (Alert_List)
    //extracting Alert Entities
     | extend Entities = parse_json(Entities)
    | mv-apply Entity = Entities on
    (
    where Entity.Type == 'account'
    | extend EntityUPN = iff(isempty(Entity.UserPrincipalName), tostring(strcat(Entity.Name, "@", tostring (Entity.UPNSuffix))), tostring(Entity.UserPrincipalName))
    )
    | mv-apply Entity = Entities on
    (
    where Entity.Type == 'url'
    | extend EntityUrl = tostring(Entity.Url)
    )
    | summarize AccountUpn=tolower(tostring(take_any(EntityUPN))),Url=tostring(tolower(take_any(EntityUrl))),AlertTime= min(TimeGenerated)by SystemAlertId, ProductName
    // filtering 3pnetwork devices
    | join kind= inner (CommonSecurityLog
    | where DeviceVendor has_any  ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
    | where DeviceAction != "Block"
    | where DeviceProduct startswith "FortiGate" or DeviceProduct startswith  "PAN" or DeviceProduct startswith  "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith  "NSSWeblog" or DeviceProduct startswith "URL"
    | where isnotempty(RequestURL)
    | where isnotempty(SourceUserName)
    | extend SourceUserName = tolower(SourceUserName)
    | project
    3plogTime=TimeGenerated,
    DeviceVendor,
    DeviceProduct,
    Activity,
    DestinationHostName,
    DestinationIP,
    RequestURL=tostring(tolower(RequestURL)),
    MaliciousIP,
    Name = tostring(split(SourceUserName,"@")[0]),
    UPNSuffix =tostring(split(SourceUserName,"@")[1]),
    SourceUserName,
    IndicatorThreatType,
    ThreatSeverity,AdditionalExtensions,
    ThreatConfidence)on $left.Url == $right.RequestURL and $left.AccountUpn == $right.SourceUserName
    // Applied the condition where alert trigger 1st and then the 3p Network activity execution
    | where AlertTime between ((3plogTime - 1h) .. (3plogTime + 1h))
entityMappings:
    - entityType: Account
      fieldMappings:
      - identifier: FullName
        columnName: SourceUserName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
    - entityType: IP
      fieldMappings:
      - identifier: Address
        columnName: DestinationIP
    - entityType: DNS
      fieldMappings:
      - identifier: DomainName
        columnName: DestinationHostName
kind: Scheduled
version: 1.0.4
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Let Binding: Alert_List used in Stage 3

let Alert_List = dynamic([
"Phishing link click observed in Network Traffic",
"Phish delivered due to an IP allow policy",
"A potentially malicious URL click was detected",
"High Risk Sign-in Observed in Network Traffic",
"A user clicked through to a potentially malicious URL",
"Suspicious network connection to AitM phishing site",
"Messages containing malicious entity not removed after delivery",
"Email messages containing malicious URL removed after delivery",
"Email reported by user as malware or phish",
"Phish delivered due to an ETR override",
"Phish not zapped because ZAP is disabled"]);

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName in~ ("Office 365 Advanced Threat Protection", "OATP")

Stage 3: where

| where AlertName in~ (Alert_List)

Stage 4: extend

| extend Entities = parse_json(Entities)

Stage 5: kusto:mv-apply

| mv-apply Entity = Entities on
(
where Entity.Type == 'account'
| extend EntityUPN = iff(isempty(Entity.UserPrincipalName), tostring(strcat(Entity.Name, "@", tostring (Entity.UPNSuffix))), tostring(Entity.UserPrincipalName))
)

Stage 6: kusto:mv-apply

| mv-apply Entity = Entities on
(
where Entity.Type == 'url'
| extend EntityUrl = tostring(Entity.Url)
)

Stage 7: summarize

| summarize AccountUpn=tolower(tostring(take_any(EntityUPN))),Url=tostring(tolower(take_any(EntityUrl))),AlertTime= min(TimeGenerated)by SystemAlertId, ProductName

Stage 8: join

| join kind= inner (CommonSecurityLog
| where DeviceVendor has_any  ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
| where DeviceAction != "Block"
| where DeviceProduct startswith "FortiGate" or DeviceProduct startswith  "PAN" or DeviceProduct startswith  "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith  "NSSWeblog" or DeviceProduct startswith "URL"
| where isnotempty(RequestURL)
| where isnotempty(SourceUserName)
| extend SourceUserName = tolower(SourceUserName)
| project
3plogTime=TimeGenerated,
DeviceVendor,
DeviceProduct,
Activity,
DestinationHostName,
DestinationIP,
RequestURL=tostring(tolower(RequestURL)),
MaliciousIP,
Name = tostring(split(SourceUserName,"@")[0]),
UPNSuffix =tostring(split(SourceUserName,"@")[1]),
SourceUserName,
IndicatorThreatType,
ThreatSeverity,AdditionalExtensions,
ThreatConfidence)on $left.Url == $right.RequestURL and $left.AccountUpn == $right.SourceUserName

Stage 9: where

where /* macro: (AlertTime between ((3plogTime - 1h) .. (3plogTime + 1h))) */

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamein
  • A potentially malicious URL click was detected
  • A user clicked through to a potentially malicious URL
  • Email messages containing malicious URL removed after delivery
  • Email reported by user as malware or phish
  • High Risk Sign-in Observed in Network Traffic
  • Messages containing malicious entity not removed after delivery
  • Phish delivered due to an ETR override
  • Phish delivered due to an IP allow policy
  • Phish not zapped because ZAP is disabled
  • Phishing link click observed in Network Traffic
  • Suspicious network connection to AitM phishing site
field:"AlertName" kind:in
DeviceActionne
  • Block
field:"DeviceAction" kind:ne value:"Block"
DeviceProductstarts_with
  • FireWall
  • FortiGate
  • NSSWeblog
  • PAN
  • URL
  • VPN
field:"DeviceProduct" kind:starts_with
DeviceVendormatch
  • Check Point transforms: term
  • Fortinet transforms: term
  • Palo Alto Networks transforms: term
  • Zscaler transforms: term
field:"DeviceVendor" kind:match
Entity.Typeeq
  • account transforms: kusto_dynamic_path
  • url transforms: kusto_dynamic_path
field:"Entity.Type" kind:eq
ProviderNamein
  • OATP
  • Office 365 Advanced Threat Protection
field:"ProviderName" kind:in
RequestURLis_not_null
  • (no value, null check)
field:"RequestURL" kind:is_not_null
SourceUserNameis_not_null
  • (no value, null check)
field:"SourceUserName" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountUpnsummarize
AlertTimesummarize
ProductNamesummarize
SystemAlertIdsummarize
Urlsummarize

Potential Kerberoasting

#
Severity
medium
Time window
1d
Group by
ClientIPAddress, Computer, Status, TargetDomainName, TargetUserName, TicketEncryptionType, TicketOptions
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

A service principal name (SPN) is used to uniquely identify a service instance in a Windows environment. Each SPN is usually associated with a service account. Organizations may have used service accounts with weak passwords in their environment. An attacker can try requesting Kerberos ticket-granting service (TGS) service tickets for any SPN from a domain controller (DC) which contains a hash of the Service account. This can then be used for offline cracking. This hunting query looks for accounts that are generating excessive requests to different resources within the last hour compared with the previous 24 hours. Normal users would not make an unusually large number of request within a small time window. This is based on 4769 events which can be very noisy so environment based tweaking might be needed.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 1572e66b-20a7-4012-9ec4-77ec4b101bc8
name: Potential Kerberoasting
description: |
  'A service principal name (SPN) is used to uniquely identify a service instance in a Windows environment.
  Each SPN is usually associated with a service account. Organizations may have used service accounts with weak passwords in their environment.
  An attacker can try requesting Kerberos ticket-granting service (TGS) service tickets for any SPN from a domain controller (DC) which contains a hash of the Service account. This can then be used for offline cracking.
  This hunting query looks for accounts that are generating excessive requests to different resources within the last hour compared with the previous 24 hours.  Normal users would not make an unusually large number of request within a small time window. This is based on 4769 events which can be very noisy so environment based tweaking might be needed.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1558
query: |
  let starttime = 1d;
  let endtime = 1h;
  let prev23hThreshold = 4;
  let prev1hThreshold = 15;
  let Kerbevent = (union isfuzzy=true
  (SecurityEvent
  | where TimeGenerated >= ago(starttime)
  | where EventID == 4769
  | parse EventData with * 'TicketEncryptionType">' TicketEncryptionType "<" *
  | where TicketEncryptionType == '0x17'
  | parse EventData with * 'TicketOptions">' TicketOptions "<" *
  | where TicketOptions == '0x40810000'
  | parse EventData with * 'Status">' Status "<" *
  | where Status == '0x0'
  | parse EventData with * 'ServiceName">' ServiceName "<" *
  | where ServiceName !contains "$" and ServiceName !contains "krbtgt"
  | parse EventData with * 'TargetUserName">' TargetUserName "<" *
  | where TargetUserName !contains "$@" and TargetUserName !contains ServiceName
  | parse EventData with * 'IpAddress">::ffff:' ClientIPAddress "<" *
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(starttime)
  | where EventID == 4769 and EventData has '0x17' and EventData has '0x40810000' and EventData has 'krbtgt'
  | extend TicketEncryptionType = tostring(EventData.TicketEncryptionType)
  | where TicketEncryptionType == '0x17'
  | extend TicketOptions = tostring(EventData.TicketOptions)
  | where TicketOptions == '0x40810000'
  | extend Status = tostring(EventData.Status)
  | where Status == '0x0'
  | extend ServiceName = tostring(EventData.ServiceName)
  | where ServiceName !contains "$" and ServiceName !contains "krbtgt"
  | extend TargetUserName = tostring(EventData.TargetUserName)
  | where TargetUserName !contains "$@" and TargetUserName !contains ServiceName
  | extend ClientIPAddress = tostring(EventData.IpAddress)
  ));
  let Kerbevent23h = Kerbevent
  | where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime)
  | summarize ServiceNameCountPrev23h = dcount(ServiceName), ServiceNameSet23h = makeset(ServiceName)
  by Computer, TargetUserName,TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status
  | where ServiceNameCountPrev23h < prev23hThreshold;
  let Kerbevent1h =
  Kerbevent
  | where TimeGenerated >= ago(endtime)
  | summarize min(TimeGenerated), max(TimeGenerated), ServiceNameCountPrev1h = dcount(ServiceName), ServiceNameSet1h = makeset(ServiceName)
  by Computer, TargetUserName, TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status;
  Kerbevent1h
  | join kind=leftanti
  (
  Kerbevent23h
  ) on TargetUserName, TargetDomainName
  // Threshold value set above is based on testing, this value may need to be changed for your environment.
  | where ServiceNameCountPrev1h > prev1hThreshold
  | project StartTime = min_TimeGenerated, EndTime = max_TimeGenerated, TargetUserName, Computer, ClientIPAddress, TicketOptions,
  TicketEncryptionType, Status, ServiceNameCountPrev1h, ServiceNameSet1h, TargetDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend TargetAccount = strcat(TargetDomainName,  "\\", TargetUserName)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIPAddress
version: 1.1.7
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let starttime = 1d;
let endtime = 1h;
let prev23hThreshold = 4;
let prev1hThreshold = 15;

let Kerbevent and let Kerbevent1h are inlined into the numbered stages below.

Let Binding: Kerbevent23h used in Stage 34

let Kerbevent23h = Kerbevent
| where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime)
| summarize ServiceNameCountPrev23h = dcount(ServiceName), ServiceNameSet23h = makeset(ServiceName)
by Computer, TargetUserName,TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status
| where ServiceNameCountPrev23h < prev23hThreshold;

Stage 1: source

let Kerbevent

Stage 2: source

let Kerbevent1h

Stage 3: union

union of 2 branches

Stage 4: source

SecurityEvent

Stage 5: where

where TimeGenerated >= ago(86400s)

Stage 6: where

where EventID == 4769

Stage 7: parse

parse

Stage 8: where

where TicketEncryptionType == 0x17

Stage 9: parse

parse

Stage 10: where

where TicketOptions == 0x40810000

Stage 11: parse

parse

Stage 12: where

where Status == 0x0

Stage 13: parse

parse

Stage 14: where

where not (ServiceName contains "$") and not (ServiceName contains "krbtgt")

Stage 15: parse

parse

Stage 16: where

where not (TargetUserName contains "$@") and TargetUserName !contains ServiceName

Stage 17: parse

parse

Stage 18: source

WindowsEvent

Stage 19: where

where TimeGenerated >= ago(86400s)

Stage 20: where

where EventData has 0x17 and EventData has 0x40810000 and EventData has "krbtgt" and EventID == 4769

Stage 21: extend

extend TicketEncryptionType

Stage 22: where

where TicketEncryptionType == 0x17

Stage 23: extend

extend TicketOptions

Stage 24: where

where TicketOptions == 0x40810000

Stage 25: extend

extend Status

Stage 26: where

where Status == 0x0

Stage 27: extend

extend ServiceName

Stage 28: where

where not (ServiceName contains "$") and not (ServiceName contains "krbtgt")

Stage 29: extend

extend TargetUserName

Stage 30: where

where not (TargetUserName contains "$@") and TargetUserName !contains ServiceName

Stage 31: extend

extend ClientIPAddress

Stage 32: where

where TimeGenerated >= ago(3600s)

Stage 33: summarize

summarize ServiceNameCountPrev1h, ServiceNameSet1h by Computer, TargetUserName, TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status

Stage 34: join (negated)

join kind=leftanti (Kerbevent23h) on TargetUserName, TargetDomainName

Stage 35: where

where ServiceNameCountPrev1h > 15

Stage 36: project

project ClientIPAddress, Computer, EndTime, ServiceNameCountPrev1h, ServiceNameSet1h, StartTime, Status, TargetDomainName, TargetUserName, TicketEncryptionType, TicketOptions

Stage 37: extend (3 consecutive steps)

extend DomainIndex, HostName, HostNameDomain, TargetAccount

Stage 38: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPAddressproject
Computerproject
EndTimeproject
ServiceNameCountPrev1hproject
ServiceNameSet1hproject
StartTimeproject
Statusproject
TargetDomainNameproject
TargetUserNameproject
TicketEncryptionTypeproject
TicketOptionsproject
HostNameextend
HostNameDomainextend
TargetAccountextend

Successful logins to SOC Prime platform from bad IP addresses

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

'This rule identifies successful logins from IP addresses previously flagged as malicious (e.g., botnets, TOR exit nodes, or known malicious IPs)'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: f8e7d6c5-b4a3-4122-8110-0987654321fe
name: Successful logins to SOC Prime platform from bad IP addresses
description: |
  'This rule identifies successful logins from IP addresses previously flagged as malicious (e.g., botnets, TOR exit nodes, or known malicious IPs)'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SOCPrimeAuditLogsDataConnector
    dataTypes:
      - SOCPrimeAuditLogs_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let blacklistIPs = _GetWatchlist('blacklistOfIps')
  | project IPAddress = column_ifexists('ip','IPAddress');
  SOCPrimeAuditLogs_CL
  | where EventName == "Logged in to the SOC Prime Platform"
  | where SourceIp in (blacklistIPs)
  | project TimeGenerated, EventName, UserEmail, UserName, SourceIp, Uri, Type
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: blacklistIPs used in Stage 3

let blacklistIPs = _GetWatchlist('blacklistOfIps')
| project IPAddress = column_ifexists('ip','IPAddress');

Stage 1: source

SOCPrimeAuditLogs_CL

Stage 2: where

| where EventName == "Logged in to the SOC Prime Platform"

Stage 3: where

| where SourceIp in (blacklistIPs)

Stage 4: project

| project TimeGenerated, EventName, UserEmail, UserName, SourceIp, Uri, Type

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventNameproject
SourceIpproject
TimeGeneratedproject
Typeproject
Uriproject
UserEmailproject
UserNameproject

Suspicious Sign In Followed by MFA Modification

#
Status
available
Severity
medium
Time window
1d
Group by
$left.UserPrincipalName, $right.InitiatorUPN
Source
github.com/Azure/Azure-Sentinel

This query looks uses Microsoft Sentinel's UEBA features to look for suspicious logons followed by modifications to MFA settings by that user.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: aec77100-25c5-4254-a20a-8027ed92c46c
name: Suspicious Sign In Followed by MFA Modification
description: |
  'This query looks uses Microsoft Sentinel's UEBA features to look for suspicious logons followed by modifications to MFA settings by that user.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - BehaviorAnalytics
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
status: Available
tactics:
  - InitialAccess
  - DefenseEvasion
relevantTechniques:
  - T1078.004
  - T1556.006
query: |
  let PriorityScore = 9;
  BehaviorAnalytics
  | where ActionType == "Sign-in"
  | where InvestigationPriority > PriorityScore
  | extend UserPrincipalName = tolower(UserPrincipalName)
  | extend LogOnTime = TimeGenerated
  | join kind=inner (AuditLogs
  | where Category =~ "UserManagement" 
  | where OperationName in~ ("Admin registered security info", "Admin updated security info", "Admin deleted security info", "User registered security info", "User changed default security info", "User deleted security info","User registered all required security info","User started security info registration") 
  | extend InitiatorUPN = tolower(tostring(InitiatedBy.user.userPrincipalName))
  | extend InitiatorID = tostring(InitiatedBy.user.id)
  | extend FromIP = tostring(InitiatedBy.user.ipAddress) 
  | extend TargetUPN = tolower(tostring(TargetResources[0].userPrincipalName))
  | extend TargetId = tostring(TargetResources[0].id)
  | extend MFAModTime = TimeGenerated
  | where isnotempty(InitiatorUPN)) on $left.UserPrincipalName == $right.InitiatorUPN
  | where MFAModTime between((LogOnTime-30m)..(LogOnTime+1h))
  | extend InitiatorName = tostring(split(InitiatorUPN, "@")[0]), InitiatorUPNSuffix = tostring(split(InitiatorUPN, "@")[1]), TargetName = tostring(split(TargetUPN, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatorUPN
      - identifier: Name
        columnName: InitiatorName
      - identifier: UPNSuffix
        columnName: InitiatorUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatorID
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUPN
      - identifier: Name
        columnName: TargetName
      - identifier: UPNSuffix
        columnName: TargetUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: TargetId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: FromIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIPAddress
alertDetailsOverride:
  alertDisplayNameFormat: Suspicious Sign In by {{InitiatorUPN}} Followed by MFA Modification to {{TargetUPN}}
  alertDescriptionFormat: |
    This query looks uses Microsoft Sentinel's UEBA features to look for suspicious logons followed by modifications to MFA settings by that user.
    In this case {{InitiatorUPN}} logged in followed by a modification to MFA settings for {{TargetUPN}}.
    The sign in was from {{SourceIPAddress}}.
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let PriorityScore = 9;

Stage 1: source

BehaviorAnalytics

Stage 2: where

| where ActionType == "Sign-in"

Stage 3: where

| where InvestigationPriority > PriorityScore

Stage 4: extend

| extend UserPrincipalName = tolower(UserPrincipalName)

Stage 5: extend

| extend LogOnTime = TimeGenerated

Stage 6: join

| join kind=inner (AuditLogs
| where Category =~ "UserManagement" 
| where OperationName in~ ("Admin registered security info", "Admin updated security info", "Admin deleted security info", "User registered security info", "User changed default security info", "User deleted security info","User registered all required security info","User started security info registration") 
| extend InitiatorUPN = tolower(tostring(InitiatedBy.user.userPrincipalName))
| extend InitiatorID = tostring(InitiatedBy.user.id)
| extend FromIP = tostring(InitiatedBy.user.ipAddress) 
| extend TargetUPN = tolower(tostring(TargetResources[0].userPrincipalName))
| extend TargetId = tostring(TargetResources[0].id)
| extend MFAModTime = TimeGenerated
| where isnotempty(InitiatorUPN)) on $left.UserPrincipalName == $right.InitiatorUPN

Stage 7: where

where /* macro: (MFAModTime between ((LogOnTime - 30m) .. (LogOnTime + 1h))) */

Stage 8: extend

extend InitiatorName, InitiatorUPNSuffix, TargetName, TargetUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • Sign-in
field:"ActionType" kind:eq value:"Sign-in"
Categoryeq
  • UserManagement
field:"Category" kind:eq value:"UserManagement"
InitiatorUPNis_not_null
  • (no value, null check)
field:"InitiatorUPN" kind:is_not_null
InvestigationPrioritygt
  • 9 transforms: number
field:"InvestigationPriority" kind:gt value:"9"
OperationNamein
  • Admin deleted security info
  • Admin registered security info
  • Admin updated security info
  • User changed default security info
  • User deleted security info
  • User registered all required security info
  • User registered security info
  • User started security info registration
field:"OperationName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserPrincipalNameextend
LogOnTimeextend
InitiatorNameextend
InitiatorUPNSuffixextend
TargetNameextend
TargetUPNSuffixextend

Back to all Kusto rules