Detection rules › Kusto

Kusto rules: 0k

Bitglass - User login from new geo location

#
Status
available
Severity
medium
Time window
14d
Group by
User
Source
github.com/Azure/Azure-Sentinel

'Detects user login from new geo location.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 34401e66-9fe9-476b-a443-3a3f89e4f3b0
name: Bitglass - User login from new geo location
description: |
  'Detects user login from new geo location.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Bitglass
    dataTypes:
      - Bitglass
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  Bitglass
  | where EventType =~ 'access'
  | where EventMessage =~ 'Login'
  | summarize g_loc = makeset(SrcGeoLocation) by User
  | join (Bitglass
          | where EventType =~ 'access'
          | where EventMessage =~ 'Login') on User
  | where g_loc !contains SrcGeoLocation
  | extend AccountCustomEntity = User, FileCustomEntity = SrcFileName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Bitglass

Stage 2: where

| where EventType =~ 'access'

Stage 3: where

| where EventMessage =~ 'Login'

Stage 4: summarize

| summarize g_loc = makeset(SrcGeoLocation) by User

Stage 5: join

| join (Bitglass
        | where EventType =~ 'access'
        | where EventMessage =~ 'Login') on User

Stage 6: where

where g_loc !contains SrcGeoLocation

Stage 7: extend

extend AccountCustomEntity, FileCustomEntity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Usersummarize
g_locsummarize
AccountCustomEntityextend
FileCustomEntityextend

GWorkspace - User access has been changed

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

Detects user access change.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 92fae638-5da8-11ec-bf63-0242ac130002
name: GWorkspace - User access has been changed
description: |
  'Detects user access change.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: GoogleWorkspaceReportsAPI
    dataTypes:
      - GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  GWorkspaceActivityReports
  | where EventMessage has "change_user_access"
  | extend AccountCustomEntity = ActorEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GWorkspaceActivityReports

Stage 2: where

| where EventMessage has "change_user_access"

Stage 3: extend

| extend AccountCustomEntity = ActorEmail

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventMessagematch
  • change_user_access transforms: term
field:"EventMessage" kind:match value:"change_user_access"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

NGINX - Known malicious user agent

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

'Detects known malicious user agents'

MITRE ATT&CK Coverage

Rule Body

id: a10c6551-bbf2-492c-aa8a-fe6efd8c9cc1
name: NGINX - Known malicious user agent
description: |
  'Detects known malicious user agents'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - NGINX_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let mal_ua_list = dynamic(['Nikto', '(hydra)', '.nasl', 'absinthe', 'advanced email extractor', 'arachni/', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project/1.0', 'crimscanner/', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core 1.', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'user-agent:', 'vega/', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc/', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', '(BOT for JCE)']);
  NGINXHTTPServer
  | where HttpUserAgentOriginal has_any (mal_ua_list)
  | extend IPCustomEntity = SrcIpAddr, MalwareCustomEntity = HttpUserAgentOriginal
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Malware
    fieldMappings:
      - identifier: Name
        columnName: MalwareCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: mal_ua_list used in Stage 2

let mal_ua_list = dynamic(['Nikto', '(hydra)', '.nasl', 'absinthe', 'advanced email extractor', 'arachni/', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project/1.0', 'crimscanner/', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core 1.', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'user-agent:', 'vega/', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc/', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', '(BOT for JCE)']);

Stage 1: source

NGINXHTTPServer

Stage 2: where

| where HttpUserAgentOriginal has_any (mal_ua_list)

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr, MalwareCustomEntity = HttpUserAgentOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentOriginalmatch
  • (BOT for JCE) transforms: term
  • (hydra) transforms: term
  • .nasl transforms: term
  • BFAC transforms: term
  • BigCliqueBOT transforms: term
  • FooBar/42 transforms: term
  • Nikto transforms: term
  • SF/ transforms: term
  • ScanAlert transforms: term
  • WPScan transforms: term
  • Webscanner transforms: term
  • Webster transforms: term
  • XSpider transforms: term
  • absinthe transforms: term
  • advanced email extractor transforms: term
  • arachni/ transforms: term
  • autogetcontent transforms: term
  • bilbo transforms: term
  • brutus transforms: term
  • brutus/aet transforms: term
  • bsqlbf transforms: term
  • cgichk transforms: term
  • cisco-torch transforms: term
  • commix transforms: term
  • core-project/1.0 transforms: term
  • crimscanner/ transforms: term
  • datacha0s transforms: term
  • dirbuster transforms: term
  • domino hunter transforms: term
  • dotdotpwn transforms: term
  • email extractor transforms: term
  • fantomBrowser transforms: term
  • fantomCrew transforms: term
  • fhscan core 1. transforms: term
  • floodgate transforms: term
  • get-minimal transforms: term
  • gootkit auto-rooter scanner transforms: term
  • grabber transforms: term
  • grendel-scan transforms: term
  • havij transforms: term
  • +51 more values (see full rule source)
field:"HttpUserAgentOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend
MalwareCustomEntityextend

NIST SP 800-53 Posture Changed

#
Status
available
Severity
medium
Time window
7d
Group by
AssessedResourceId, ComplianceDomain, Failed, Passed, PassedControlsPercentage, RecommendationName, Total
Source
github.com/Azure/Azure-Sentinel

'This alert is desinged to monitor Azure policies aligned with the NIST SP 800-53 Regulatory Compliance initative. The alert triggers when policy compliance falls below 70% within a 1 week timeframe.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: dd834c97-4638-4bb3-a4e3-807e8b0580dc
name: NIST SP 800-53 Posture Changed
description: |
  'This alert is desinged to monitor Azure policies aligned with the NIST SP 800-53 Regulatory Compliance initative. The alert triggers when policy compliance falls below 70% within a 1 week timeframe.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: 
  let Last_Evaluated=SecurityRecommendation
  | join kind=fullouter(SecurityRegulatoryCompliance| where ComplianceStandard == "NIST-SP-800-53-R4") on RecommendationName
  | summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
  | extend ComplianceDomain=iff(ComplianceControl contains "AC.", "Access Control", iff(ComplianceControl contains "AT.", "Security Awareness & Training", iff(ComplianceControl contains "AU.", "Audit & Accountability", iff(ComplianceControl contains "CA.", "Security Assessment", iff(ComplianceControl contains "CM.", "Configuration Management", iff(ComplianceControl contains "CP.", "Contingency Planning", iff(ComplianceControl contains "IA.", "Identification & Authentication", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "MA.", "System Maintenance", iff(ComplianceControl contains "MP.", "Media Protection", iff(ComplianceControl contains "PE.", "Physical Protection", iff(ComplianceControl contains "PL.", "Security Planning", iff(ComplianceControl contains "PS.", "Personnel Security",iff(ComplianceControl contains "RA.", "Risk Assessment",iff(ComplianceControl contains "SA.", "System & Services Acquisition",iff(ComplianceControl contains "SC.", "System & Communications Protection",iff(ComplianceControl contains "SI.", "System & Information Integrity","Other")))))))))))))))))
  | summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain, TimeGenerated;
  SecurityRecommendation
  | join kind=fullouter(SecurityRegulatoryCompliance| where ComplianceStandard == "NIST-SP-800-53-R4") on RecommendationName
  | summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
  | extend ComplianceDomain=iff(ComplianceControl contains "AC.", "Access Control", iff(ComplianceControl contains "AT.", "Security Awareness & Training", iff(ComplianceControl contains "AU.", "Audit & Accountability", iff(ComplianceControl contains "CA.", "Security Assessment", iff(ComplianceControl contains "CM.", "Configuration Management", iff(ComplianceControl contains "CP.", "Contingency Planning", iff(ComplianceControl contains "IA.", "Identification & Authentication", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "MA.", "System Maintenance", iff(ComplianceControl contains "MP.", "Media Protection", iff(ComplianceControl contains "PE.", "Physical Protection", iff(ComplianceControl contains "PL.", "Security Planning", iff(ComplianceControl contains "PS.", "Personnel Security",iff(ComplianceControl contains "RA.", "Risk Assessment",iff(ComplianceControl contains "SA.", "System & Services Acquisition",iff(ComplianceControl contains "SC.", "System & Communications Protection",iff(ComplianceControl contains "SI.", "System & Information Integrity","Other")))))))))))))))))
  | summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain
  | extend PassedControlsPercentage = (Passed/todouble(Total))*100
  | join (Last_Evaluated) on ComplianceDomain
  | project ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed, LastEvaluated=TimeGenerated
  | summarize arg_max(LastEvaluated, *) by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
  | where PassedControlsPercentage < 70 
  | sort by PassedControlsPercentage, Passed desc
  | extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
  | extend URLCustomEntity = RemediationLink
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: Last_Evaluated used in Stage 7

let Last_Evaluated = SecurityRecommendation | join kind=fullouter(SecurityRegulatoryCompliance| where ComplianceStandard == "NIST-SP-800-53-R4") on RecommendationName | summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName | extend ComplianceDomain=iff(ComplianceControl contains "AC.", "Access Control", iff(ComplianceControl contains "AT.", "Security Awareness & Training", iff(ComplianceControl contains "AU.", "Audit & Accountability", iff(ComplianceControl contains "CA.", "Security Assessment", iff(ComplianceControl contains "CM.", "Configuration Management", iff(ComplianceControl contains "CP.", "Contingency Planning", iff(ComplianceControl contains "IA.", "Identification & Authentication", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "MA.", "System Maintenance", iff(ComplianceControl contains "MP.", "Media Protection", iff(ComplianceControl contains "PE.", "Physical Protection", iff(ComplianceControl contains "PL.", "Security Planning", iff(ComplianceControl contains "PS.", "Personnel Security",iff(ComplianceControl contains "RA.", "Risk Assessment",iff(ComplianceControl contains "SA.", "System & Services Acquisition",iff(ComplianceControl contains "SC.", "System & Communications Protection",iff(ComplianceControl contains "SI.", "System & Information Integrity","Other"))))))))))))))))) | summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain, TimeGenerated;

Stage 1: source

SecurityRecommendation

Stage 2: join

join kind=fullouter (SecurityRegulatoryCompliance) on RecommendationName

Stage 3: summarize

summarize by AssessedResourceId, RecommendationName

Stage 4: extend

extend ComplianceDomain
ComplianceDomain =
ifComplianceControl contains "AC.""Access Control"
elseiff((ComplianceControl contains "AT."), "Security Awareness & Training", iff((ComplianceControl contains "AU."), "Audit & Accountability", iff((ComplianceControl contains "CA."), "Security Assessment", iff((ComplianceControl contains "CM."), "Configuration Management", iff((ComplianceControl contains "CP."), "Contingency Planning", iff((ComplianceControl contains "IA."), "Identification & Authentication", iff((ComplianceControl contains "IR."), "Incident Response", iff((ComplianceControl contains "MA."), "System Maintenance", iff((ComplianceControl contains "MP."), "Media Protection", iff((ComplianceControl contains "PE."), "Physical Protection", iff((ComplianceControl contains "PL."), "Security Planning", iff((ComplianceControl contains "PS."), "Personnel Security", iff((ComplianceControl contains "RA."), "Risk Assessment", iff((ComplianceControl contains "SA."), "System & Services Acquisition", iff((ComplianceControl contains "SC."), "System & Communications Protection", iff((ComplianceControl contains "SI."), "System & Information Integrity", "Other"))))))))))))))))

Stage 5: summarize

summarize Failed, Passed, Total by ComplianceDomain

Stage 6: extend

extend PassedControlsPercentage

Stage 7: join

join (Last_Evaluated) on ComplianceDomain

Stage 8: project

project ComplianceDomain, Failed, LastEvaluated, Passed, PassedControlsPercentage, Total

Stage 9: summarize

summarize by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed

Stage 10: where

where PassedControlsPercentage < 70

Stage 11: sort

sort by Passed, PassedControlsPercentage

Stage 12: extend

extend RemediationLink

Stage 13: extend

extend URLCustomEntity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ComplianceDomainsummarize
Failedsummarize
Passedsummarize
PassedControlsPercentagesummarize
Totalsummarize
RemediationLinkextend
URLCustomEntityextend

OracleDBAudit - Connection to database from external IP

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

'Detects when connection to database is from external IP source.'

MITRE ATT&CK Coverage

Rule Body

id: 54aa2c17-acfd-4e3a-a1c4-99c88cf34ebe
name: OracleDBAudit - Connection to database from external IP
description: |
  'Detects when connection to database is from external IP source.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Collection
  - Exfiltration
relevantTechniques:
  - T1190
  - T1133
  - T1078
  - T1119
  - T1029
query: |
  OracleDatabaseAuditEvent
  | where isnotempty(SrcIpAddr)
  | where isnotempty(Action)
  | where DbAction =~ 'connect'
  | where ipv4_is_private(SrcIpAddr) == 'false'
  | extend AccountCustomEntity = DstUserName
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

OracleDatabaseAuditEvent

Stage 2: where

| where isnotempty(SrcIpAddr)

Stage 3: where

| where isnotempty(Action)

Stage 4: where

| where DbAction =~ 'connect'

Stage 5: where

| where ipv4_is_private(SrcIpAddr) == 'false'

Stage 6: extend

| extend AccountCustomEntity = DstUserName

Stage 7: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actionis_not_null
  • (no value, null check)
field:"Action" kind:is_not_null
DbActioneq
  • connect
field:"DbAction" kind:eq value:"connect"
SrcIpAddris_not_null
  • (no value, null check)
field:"src_ip" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SrcIpAddrcidr_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:SrcIpAddr

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

PROD (TM002.1) - OBJECT - Created or Deleted a Tier Level Object

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

A Tier Model object was created or deleted

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: b58adf25-fb44-4296-9b6c-0e6b97139dad
name: 'PROD (TM002.1) - OBJECT - Created or Deleted a Tier Level Object'
description: |
  A Tier Model object was created or deleted
severity: Low
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1078.002
query: |
  SecurityEvent 
  | where (EventID == 5137
      or EventID == 5141)
      and (EventData matches regex @"(?i)OU=Domain Controllers"
          or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
          or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
          or EventData matches regex @"(?i)OU=Tier 2 Accounts"
          or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
          or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
          or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
          or EventData matches regex @"(?i)OU=Tier 0 Groups"
          or EventData matches regex @"(?i)OU=Tier 1 Groups"
          or EventData matches regex @"(?i)OU=Tier 2 Groups"
          or EventData matches regex @"(?i)OU=Tier 0 PAW"
          or EventData matches regex @"(?i)OU=Tier 1 PAW"
          or EventData matches regex @"(?i)OU=Tier 2 PAW"
          or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
          or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
          or EventData matches regex @"(?i)CN=Builtin"
          or EventData matches regex @"(?i)CN=Computers"
          or EventData matches regex @"(?i)CN=Users")
  | extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
  | extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend ObjectTier = extract("(?i)OU=(Tier [^ ,]+)", 1, ObjectTier_e)
  | extend ObjectTier = iff(ObjectTier == "", "Outside Tier Model", ObjectTier)
  | extend ObjectTier = iff(ObjectTier_e has "OU=Domain Controllers", "Tier 0", ObjectTier)
  | extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
  | where ObjectClass !has "organizationalUnit" and ObjectClass !has "msFVE-RecoveryInformation"
  | extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
  | extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
  | extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
  | extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
  | extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
  | project TimeGenerated
          , ObjectName
          , ObjectTier
          , ObjectClass
          , ActivityType
          , Account
          , Domain
          , Computer
          , Channel
          , EventID
          , EventData
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ObjectName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
customDetails:
  ObjectName: ObjectName
  ObjectTier: ObjectTier
  ObjectClass: ObjectClass
  ActivityType: ActivityType
  Account: Account
  Domain: Domain
  Computer: Computer
  Channel: Channel
  EventID: EventID
  EventData: EventData
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5M
    matchingMethod: Selected
    groupByEntities:
      - Account
    groupByCustomDetails:
      - ObjectName
alertDetailsOverride:
  alertDisplayNameFormat: '(TM002.1) A {{ObjectTier}} {{ObjectClass}} was {{ActivityType}}'
  alertDescriptionFormat: 'Tier Model TM002 - A Tier Model object was created or deleted.'
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where (EventID == 5137
    or EventID == 5141)
    and (EventData matches regex @"(?i)OU=Domain Controllers"
        or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 Groups"
        or EventData matches regex @"(?i)OU=Tier 1 Groups"
        or EventData matches regex @"(?i)OU=Tier 2 Groups"
        or EventData matches regex @"(?i)OU=Tier 0 PAW"
        or EventData matches regex @"(?i)OU=Tier 1 PAW"
        or EventData matches regex @"(?i)OU=Tier 2 PAW"
        or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
        or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
        or EventData matches regex @"(?i)CN=Builtin"
        or EventData matches regex @"(?i)CN=Computers"
        or EventData matches regex @"(?i)CN=Users")

Stage 3: extend (7 consecutive steps)

| extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
| extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = extract("(?i)OU=(Tier [^ ,]+)", 1, ObjectTier_e)
| extend ObjectTier = iff(ObjectTier == "", "Outside Tier Model", ObjectTier)
| extend ObjectTier = iff(ObjectTier_e has "OU=Domain Controllers", "Tier 0", ObjectTier)
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)

Stage 4: where

| where ObjectClass !has "organizationalUnit" and ObjectClass !has "msFVE-RecoveryInformation"

Stage 5: extend (5 consecutive steps)

| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
| extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
| extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)

Stage 6: project

| project TimeGenerated
        , ObjectName
        , ObjectTier
        , ObjectClass
        , ActivityType
        , Account
        , Domain
        , Computer
        , Channel
        , EventID
        , EventData

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDataregex_match
  • (?i)CN=Builtin
  • (?i)CN=Computers
  • (?i)CN=Users
  • (?i)OU=Domain Controllers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 0 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 0 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 0 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 1 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 1 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 1 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 2 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 2 Service Accounts corpus 4 (kusto 4)
field:"EventData" kind:regex_match
EventIDeq
  • 5137 corpus 8 (kusto 5, elastic 3)
  • 5141 corpus 2 (kusto 2)
field:"EventID" kind:eq

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ObjectClassmatchmsFVE-RecoveryInformationexcludes:ObjectClass field:"ObjectClass" value:"msFVE-RecoveryInformation"
ObjectClassmatchorganizationalUnitexcludes:ObjectClass field:"ObjectClass" value:"organizationalUnit"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
ActivityTypeproject
Channelproject
Computerproject
Domainproject
EventDataproject
EventIDproject
ObjectClassproject
ObjectNameproject
ObjectTierproject
TimeGeneratedproject

SlackAudit - Public link created for file which can contain sensitive information.

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

'Detects public links created for files that may contain sensitive data such as passwords, authentication tokens, secret keys, or private configuration files. Tune exclusions using the SlackAuditSensitiveFile_Allowlist_File and SlackAuditSensitiveFile_Allowlist_Account watchlists when known benign files or accounts generate expected public-link activity.'

MITRE ATT&CK Coverage

Rule Body

id: 279316e8-8965-47d2-9788-b94dc352c853
name: SlackAudit - Public link created for file which can contain sensitive information.
description: |
  'Detects public links created for files that may contain sensitive data such as passwords, authentication tokens,
  secret keys, or private configuration files. Tune exclusions using the SlackAuditSensitiveFile_Allowlist_File and SlackAuditSensitiveFile_Allowlist_Account
  watchlists when known benign files or accounts generate expected public-link activity.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SlackAuditAPI
    dataTypes:
      - SlackAudit_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
  - T1567.002
query: |
  let AllowedFiles = toscalar(_GetWatchlist('SlackAuditSensitiveFile_Allowlist_File') | summarize make_set(tostring(SearchKey)));
  let AllowedUsers = toscalar(_GetWatchlist('SlackAuditSensitiveFile_Allowlist_Account') | summarize make_set(tostring(SearchKey)));
  SlackAudit
  | where Action =~ 'file_public_link_created'
  | extend FileNameLower = tolower(EntityFileName), UserLower = tolower(SrcUserName)
  | where EntityFileName in~ ('id_rsa')
      or FileNameLower has_any ('password', 'secret', 'token', 'credential', 'private key', 'api key')
      or FileNameLower has_any ('.ssh', '.npmrc', '.muttrc', '.gitconfig', '.netrc', 'package.json', 'Gemfile', 'bower.json', 'config.gypi', 'travis.yml', 'config.json')
  | where isempty(AllowedFiles) or EntityFileName !in~ (AllowedFiles)
  | where isempty(AllowedUsers) or UserLower !in~ (AllowedUsers)
  | extend AccountCustomEntity = SrcUserName
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
alertDetailsOverride:
  alertDisplayNameFormat: Slack public link created for sensitive file {{EntityFileName}} by {{SrcUserName}}
  alertDescriptionFormat: Public link created for {{EntityFileName}} by {{SrcUserName}} from {{SrcIpAddr}}
customDetails:
  FileName: EntityFileName
  Action: Action
  SourceUser: SrcUserName
  SourceIP: SrcIpAddr
  ActorEntity: AccountCustomEntity
  IpEntity: IPCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let Binding: AllowedFiles used in Stage 5

let AllowedFiles = toscalar(_GetWatchlist('SlackAuditSensitiveFile_Allowlist_File') | summarize make_set(tostring(SearchKey)));

Let Binding: AllowedUsers used in Stage 6

let AllowedUsers = toscalar(_GetWatchlist('SlackAuditSensitiveFile_Allowlist_Account') | summarize make_set(tostring(SearchKey)));

Stage 1: source

SlackAudit

Stage 2: where

| where Action =~ 'file_public_link_created'

Stage 3: extend

| extend FileNameLower = tolower(EntityFileName), UserLower = tolower(SrcUserName)

Stage 4: where

| where EntityFileName in~ ('id_rsa')
    or FileNameLower has_any ('password', 'secret', 'token', 'credential', 'private key', 'api key')
    or FileNameLower has_any ('.ssh', '.npmrc', '.muttrc', '.gitconfig', '.netrc', 'package.json', 'Gemfile', 'bower.json', 'config.gypi', 'travis.yml', 'config.json')

Stage 5: where

| where isempty(AllowedFiles) or EntityFileName !in~ (AllowedFiles)

Stage 6: where

| where isempty(AllowedUsers) or UserLower !in~ (AllowedUsers)

Stage 7: extend

| extend AccountCustomEntity = SrcUserName

Stage 8: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • file_public_link_created
field:"Action" kind:eq value:"file_public_link_created"
AllowedFilesis_null
  • (no value, null check)
field:"AllowedFiles" kind:is_null
AllowedUsersis_null
  • (no value, null check)
field:"AllowedUsers" kind:is_null
EntityFileNamein
  • id_rsa
field:"EntityFileName" kind:in value:"id_rsa"
FileNameLowermatch
  • .gitconfig transforms: term
  • .muttrc transforms: term
  • .netrc transforms: term
  • .npmrc transforms: term
  • .ssh transforms: term
  • Gemfile transforms: term
  • api key transforms: term
  • bower.json transforms: term
  • config.gypi transforms: term
  • config.json transforms: term
  • credential transforms: term
  • package.json transforms: term
  • password transforms: term
  • private key transforms: term
  • secret transforms: term
  • token transforms: term
  • travis.yml transforms: term
field:"FileNameLower" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
FileNameLowerextend
UserLowerextend
AccountCustomEntityextend
IPCustomEntityextend

Suspicious Network Connections - Supply Chain Attack

#
Group by
DeviceName, RemoteIP, RemotePort
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below query detects unusual network conenctions from servers that have 3rd party software installed.
You can further improve the query by using a list of servers that have privileges across the whole domain.

MITRE ATT&CK Coverage

TacticTechniques
Command & ControlNo specific technique

Telemetry Coverage

Rule Body

// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Query parameters:
let lookback = 14d; 
// Generate list of all Servers
let server_list = 
    DeviceInfo
    | where Timestamp > ago(lookback)
    | where isnotempty(OSPlatform)
    | where DeviceType <> "Workstation" and OSPlatform <> "macOS"
    | summarize make_set(DeviceName)
    ;
// Generate list of servers that have 3rd party software installed.
// Criteria: if a software is installed on less than 10 servers, it's probably a 3rd party software.
// There are probably(hopefully) just a few servers or server groups that have privileges across the whole domain.
// You can change the threshold according to your environment.
let ServersWithThirdPartyApps = materialize (
    DeviceNetworkEvents
    | where Timestamp > ago(lookback)
    | where DeviceName in (server_list)
    | where ActionType <> "ListeningConnectionCreated"
    | where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
    | summarize dcount(DeviceName) by InitiatingProcessVersionInfoCompanyName
    | where dcount_DeviceName < 10
    | join kind=inner (
        DeviceNetworkEvents
        | where Timestamp > ago(lookback)
        | where DeviceName in (server_list)
        | where ActionType <> "ListeningConnectionCreated"
        | where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
        )
        on InitiatingProcessVersionInfoCompanyName
        | summarize make_set(DeviceName)
        )
        ;
// Get network connection statistics
let baseline = materialize (
    DeviceNetworkEvents
    | where Timestamp > ago(lookback)
    | where DeviceName in (ServersWithThirdPartyApps)
    | where ActionType <> "ListeningConnectionCreated"
    | where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
    | summarize hint.strategy=shuffle Count=count(), starttime = min(Timestamp), endtime = max(Timestamp) by DeviceName, RemoteIP, RemotePort
    )
    ;
// Get destination IP 
let Destinations = baseline | summarize make_set(RemoteIP);
// Filter connections that was not seen before last 1d 
// Generate prevalence info, URL info(if available) and enrich results
// Filter based on prevalence and URL information and display everything by hostname
baseline
| where starttime > ago(1d)
| lookup kind=leftouter (
    DeviceNetworkEvents
    | where Timestamp > ago(5d)
    | where RemoteIP in (Destinations)
    | summarize hint.strategy=shuffle Prevalence = dcount(DeviceId), URLs=make_set(RemoteUrl) by RemoteIP
    )
    on RemoteIP
| where Prevalence < 6 or isempty( Prevalence)
// If you want to see all the events in distinct rows, remove the below 2 lines
// filter out results based on trusted URLs if you like. 
| extend Details = pack('RemoteIP',RemoteIP, 'RemotePort',RemotePort, 'Count',Count, 'Prevalence',Prevalence, 'URLs',URLs)
| summarize make_set(Details) by DeviceName

Stages and Predicates

Parameters

let lookback = 14d;

let baseline is inlined into the numbered stages below.

Let Binding: server_list used in Stage 1

let server_list = DeviceInfo
    | where Timestamp > ago(lookback)
    | where isnotempty(OSPlatform)
    | where DeviceType <> "Workstation" and OSPlatform <> "macOS"
    | summarize make_set(DeviceName);

Let Binding: ServersWithThirdPartyApps used in Stages 2, 7

let ServersWithThirdPartyApps = materialize (
    DeviceNetworkEvents
    | where Timestamp > ago(lookback)
    | where DeviceName in (server_list)
    | where ActionType <> "ListeningConnectionCreated"
    | where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
    | summarize dcount(DeviceName) by InitiatingProcessVersionInfoCompanyName
    | where dcount_DeviceName < 10
    | join kind=inner (
        DeviceNetworkEvents
        | where Timestamp > ago(lookback)
        | where DeviceName in (server_list)
        | where ActionType <> "ListeningConnectionCreated"
        | where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
        )
        on InitiatingProcessVersionInfoCompanyName
        | summarize make_set(DeviceName)
        );

Let Binding: Destinations used in Stage 4

let Destinations = baseline | summarize make_set(RemoteIP);

Stage 1: source

let server_list

Stage 2: source

let ServersWithThirdPartyApps

Stage 3: source

let baseline

Stage 4: source

let Destinations

Stage 5: source

DeviceNetworkEvents

Stage 6: where

where Timestamp > ago(1209600s)

Stage 7: where

where DeviceName == "ServersWithThirdPartyApps"

Stage 8: where

where ActionType != "ListeningConnectionCreated"

Stage 9: where

where not ((RemoteIP startswith "::ffff:" and RemoteIPType == "FourToSixMapping")) and not (RemoteIPType in ("Loopback", "Private"))

Stage 10: summarize

summarize Count, endtime, starttime by DeviceName, RemoteIP, RemotePort

Stage 11: where

where starttime > ago(86400s)

Stage 12: kusto:lookup

lookup kind=leftouter (DeviceNetworkEvents) on RemoteIP

Stage 13: where

where (isempty(Prevalence) or Prevalence < 6)

Stage 14: extend

extend Details

Stage 15: summarize

summarize by DeviceName

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
DeviceNamesummarize

TI Map IP entity to DNS Events (ASIM DNS schema)

#
Severity
medium
Time window
14d
Group by
Id, IoC, ObservableValue
Source
github.com/Azure/Azure-Sentinel

This rule identifies DNS requests for which response IP address is a known IoC. 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: b306fba8-1a28-449f-aa24-30362e16d4f5
name: TI Map IP entity to DNS Events (ASIM DNS schema)
description: |
  'This rule identifies DNS requests for which response IP address is a known IoC. 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: CiscoUmbrellaDataConnector
    dataTypes: 
      - Cisco_Umbrella_dns_CL
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: Corelight
    dataTypes: 
      - Corelight_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
tags:
  - ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/ThreatIntelligenceIndicator/IPEntity_DnsEvents.yaml
    version: 1.0.0
  - Schema: ASIMDns
    SchemaVersion: 0.1.1
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let IP_TI = 
  ThreatIntelIndicators
  | where TimeGenerated >= ago(ioc_lookBack)
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "ipv4-addr"
  | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  | extend NetworkSourceIP = ObservableValue
  | extend IoC = NetworkSourceIP
  | extend IndicatorId = tostring(split(Id, "--")[2])
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil));
  IP_TI
   | project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, IoC
  | join kind=innerunique // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  (
  _Im_Dns(starttime=ago(dt_lookBack))
  | where isnotempty(DnsResponseName)
  | summarize imDns_mintime=min(TimeGenerated), imDns_maxtime=max(TimeGenerated) by SrcIpAddr, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor
  | extend addresses = extract_all (@'(\d+\.\d+\.\d+\.\d+)', DnsResponseName)
  | mv-expand IoC = addresses to typeof(string)
  )
  on IoC
  | where imDns_mintime < ValidUntil
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | extend ThreatType = tostring(Data.indicator_types[0])
  | project imDns_mintime, imDns_maxtime, Description, ActivityGroupNames, IndicatorId, ThreatType, LatestIndicatorTime, ValidUntil, Confidence, 
  SrcIpAddr, IoC, Dvc, EventVendor, EventProduct, DnsQuery, DnsResponseName, Type
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IoC
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
customDetails:
  LatestIndicatorTime: LatestIndicatorTime
  Description: Description
  ActivityGroupNames: ActivityGroupNames
  IndicatorId: IndicatorId
  ThreatType: ThreatType
  ExpirationDateTime: ValidUntil
  ConfidenceScore: Confidence
  DNSRequestTime: imDns_mintime
  SourceIPAddress: SrcIpAddr
  DnsQuery: DnsQuery
alertDetailsOverride:
  alertDisplayNameFormat: The response {{IoC}} to DNS query matched an IoC 
  alertDescriptionFormat: The response address {{IoC}} to a DNS query matched a known indicator of compromise of {{ThreatType}}. Consult the threat intelligence blade for more information on the indicator.
version: 1.2.11
kind: Scheduled

Stages and Predicates

Parameters

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

let IP_TI is inlined into the numbered stages below.

Stages 1 to 7 define let IP_TI as the rule's main pipeline source. Stages 8 to 12 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: extend

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

Stage 4: where

| where IndicatorType == "ipv4-addr"

Stage 5: extend (4 consecutive steps)

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend NetworkSourceIP = ObservableValue
| extend IoC = NetworkSourceIP
| extend IndicatorId = tostring(split(Id, "--")[2])

Stage 6: summarize

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

Stage 7: where

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

Stage 8: project-reorder

IP_TI
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, IoC

Stage 9: join

| join kind=innerunique
(
_Im_Dns(starttime=ago(dt_lookBack))
| where isnotempty(DnsResponseName)
| summarize imDns_mintime=min(TimeGenerated), imDns_maxtime=max(TimeGenerated) by SrcIpAddr, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor
| extend addresses = extract_all (@'(\d+\.\d+\.\d+\.\d+)', DnsResponseName)
| mv-expand IoC = addresses to typeof(string)
)
on IoC

Stage 10: where

where imDns_mintime < ValidUntil

Stage 11: extend (3 consecutive steps)

extend ActivityGroupNames, Description, ThreatType

Stage 12: project

project ActivityGroupNames, Confidence, Description, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor, IndicatorId, IoC, LatestIndicatorTime, SrcIpAddr, ThreatType, Type, ValidUntil, imDns_maxtime, imDns_mintime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Confidenceproject
Descriptionproject
DnsQueryproject
DnsResponseNameproject
Dvcproject
EventProductproject
EventVendorproject
IndicatorIdproject
IoCproject
LatestIndicatorTimeproject
SrcIpAddrproject
ThreatTypeproject
Typeproject
ValidUntilproject
imDns_maxtimeproject
imDns_mintimeproject

TI map IP entity to Network Session Events (ASIM Network Session schema)

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

This rule identifies a match Network Sessions for which the source or destination IP address is a known IoC. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM NetworkSession schema

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: e2399891-383c-4caf-ae67-68a008b9f89e
name: TI map IP entity to Network Session Events (ASIM Network Session schema)
description: |
  'This rule identifies a match Network Sessions for which the source or destination IP address is a known IoC. This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM NetworkSession schema'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWSS3
    dataTypes:
      - AWSVPCFlow
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftSysmonForLinux
    dataTypes:
      - Syslog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: AzureMonitor(VMInsights)
    dataTypes:
      - VMConnection
  - connectorId: AzureFirewall
    dataTypes:
      - AzureDiagnostics
  - connectorId: AzureNSG
    dataTypes:
      - AzureDiagnostics
  - connectorId: CiscoASA
    dataTypes:
      - CommonSecurityLog
  - connectorId: Corelight
    dataTypes:
      - Corelight_CL
  - connectorId: AIVectraStream
    dataTypes:
      - VectraStream
  - connectorId: CheckPoint
    dataTypes:
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: CiscoMeraki
    dataTypes:
      - Syslog
      - CiscoMerakiNativePoller
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator

queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let IP_TI = materialize (
    ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")
    | where TI_ipEntity != "NO_IP"
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
  );
  IP_TI
    // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique 
  (
    _Im_NetworkSession (starttime=ago(dt_lookBack))
    | where isnotempty(SrcIpAddr)
    | summarize imNWS_mintime=min(TimeGenerated), imNWS_maxtime=max(TimeGenerated) by SrcIpAddr, DstIpAddr, Dvc, EventProduct, EventVendor  
    | lookup (IP_TI | project TI_ipEntity, Active) on $left.SrcIpAddr == $right.TI_ipEntity
    | project-rename SrcMatch = Active
    | lookup (IP_TI | project TI_ipEntity, Active) on $left.DstIpAddr == $right.TI_ipEntity
    | project-rename DstMatch = Active
    | where SrcMatch or DstMatch
    | extend 
        IoCIP = iff(SrcMatch, SrcIpAddr, DstIpAddr),
        IoCDirection = iff(SrcMatch, "Source", "Destination")
  )on $left.TI_ipEntity == $right.IoCIP
  | where imNWS_mintime < ExpirationDateTime
  | project imNWS_mintime, imNWS_maxtime, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SrcIpAddr, DstIpAddr, IoCDirection, IoCIP, Dvc, EventVendor, EventProduct
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IoCIP

customDetails:
  EventStartTime: imNWS_mintime
  EventEndTime: imNWS_maxtime
  IoCDescription: Description
  ActivityGroupNames: ActivityGroupNames
  IndicatorId: IndicatorId
  ThreatType: ThreatType
  IoCExpirationTime: ExpirationDateTime
  IoCConfidenceScore: ConfidenceScore
  IoCIPDirection: IoCDirection

alertDetailsOverride:
  alertDisplayNameFormat: A network session {{IoCDirection}} address {{IoCIP}} matched an IoC. 
  alertDescriptionFormat: The {{IoCDirection}} address {{IoCIP}} of a network session  matched a known indicator of compromise of {{ThreatType}}. Consult the threat intelligence blead for more information on the indicator.

tags:
  - Schema: ASIMNetworkSession
    SchemaVersion: 0.2.4

version: 1.2.6
kind: Scheduled

Stages and Predicates

Parameters

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

let IP_TI is inlined into the numbered stages below.

Stages 1 to 6 define let IP_TI as the rule's main pipeline source. Stages 7 to 9 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: extend

| extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")

Stage 4: where

| where TI_ipEntity != "NO_IP"

Stage 5: summarize

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

Stage 6: where

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

Stage 7: join

join kind=innerunique (_Im_NetworkSession) on $left.TI_ipEntity, $right.IoCIP

Stage 8: where

where imNWS_mintime < ExpirationDateTime

Stage 9: project

project ActivityGroupNames, ConfidenceScore, Description, DstIpAddr, Dvc, EventProduct, EventVendor, ExpirationDateTime, IndicatorId, IoCDirection, IoCIP, SrcIpAddr, ThreatType, imNWS_maxtime, imNWS_mintime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
DstIpAddrproject
Dvcproject
EventProductproject
EventVendorproject
ExpirationDateTimeproject
IndicatorIdproject
IoCDirectionproject
IoCIPproject
SrcIpAddrproject
ThreatTypeproject
imNWS_maxtimeproject
imNWS_mintimeproject

Utimaco ESKM - Burst of KMIP DESTROY operations by a single user

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

Identifies a single KMIP user issuing 20 or more DESTROY operations within 10 minutes on Utimaco ESKM. May indicate mass key/object deletion, insider sabotage, or ransomware-style cryptographic-material destruction.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 9b1a3b3e-7e16-4a3b-8a8f-7f1f2b1c0a03
name: Utimaco ESKM - Burst of KMIP DESTROY operations by a single user
description: |
  Identifies a single KMIP user issuing 20 or more DESTROY operations within 10 minutes on Utimaco ESKM. May indicate mass key/object deletion, insider sabotage, or ransomware-style cryptographic-material destruction.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: UtimacoESKMConnector
    dataTypes:
      - UtimacoESKMKmipServerLogs_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
query: |
  UtimacoESKMKmipServerLogs_CL
  | where TimeGenerated > ago(10m)
  | where Operation == "DESTROY"
  | where Result == "SUCCESS"
  | where isnotempty(User)
  | summarize DestroyCount = count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ObjectTypes = make_set(ObjectType, 25), UUIDs = make_set(UUID, 100), SourceIPs = make_set(IP, 25) by User
  | where DestroyCount >= 20
  | extend AccountCustomEntity = User
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

UtimacoESKMKmipServerLogs_CL

Stage 2: where

| where TimeGenerated > ago(10m)

Stage 3: where

| where Operation == "DESTROY"

Stage 4: where

| where Result == "SUCCESS"

Stage 5: where

| where isnotempty(User)

Stage 6: summarize

| summarize DestroyCount = count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ObjectTypes = make_set(ObjectType, 25), UUIDs = make_set(UUID, 100), SourceIPs = make_set(IP, 25) by User

Stage 7: where

| where DestroyCount >= 20

Stage 8: extend

| extend AccountCustomEntity = User

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestroyCountsummarize
EndTimesummarize
ObjectTypessummarize
SourceIPssummarize
StartTimesummarize
UUIDssummarize
Usersummarize
AccountCustomEntityextend

VMware SD-WAN Edge - IDS/IPS Alert triggered (Search API)

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

The VMware SD-WAN Edge appliance captured a potentially malicious traffic flow. Please investigate the IOC information available. This analytics rule analyses Search API streams. Search API queries report only IDS/IPS Alerts. In case you would also need Network Flood Protection, please enable Syslog collection using AMA.

MITRE ATT&CK Coverage

TacticTechniques
Lateral Movement

Rule Body

id: 44f78dbf-9f29-4ec0-aaca-ab5bf0b559af
name: VMware SD-WAN Edge - IDS/IPS Alert triggered (Search API)
version: 1.0.0
kind: Scheduled
description: |-
  The VMware SD-WAN Edge appliance captured a potentially malicious traffic flow. Please investigate the IOC information available.

  This analytics rule analyses Search API streams. Search API queries report only IDS/IPS Alerts. In case you would also need Network Flood Protection, please enable Syslog collection using AMA.
severity: High
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
  - connectorId: VMwareSDWAN
    dataTypes:
      - SDWAN
tactics:
- LateralMovement
relevantTechniques:
- T1210
query: |
  VMware_SDWAN_FirewallLogs_CL
  | project TimeGenerated, signature, severity, category, signatureId, actionTaken, edgeName, sourceIp, protocol, sourcePort, destinationIp, destinationPort, domainName, attackSource, attackTarget, firewallPolicyName
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDynamicProperties: []
customDetails:
  IDPS_Signature: signature
  IDPS_Event_Category: category
entityMappings:
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: domainName
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: sourceIp
suppressionDuration: 5h

Stages and Predicates

Stage 1: source

VMware_SDWAN_FirewallLogs_CL

Stage 2: project

| project TimeGenerated, signature, severity, category, signatureId, actionTaken, edgeName, sourceIp, protocol, sourcePort, destinationIp, destinationPort, domainName, attackSource, attackTarget, firewallPolicyName

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
actionTakenproject
attackSourceproject
attackTargetproject
categoryproject
destinationIpproject
destinationPortproject
domainNameproject
edgeNameproject
firewallPolicyNameproject
protocolproject
severityproject
signatureproject
signatureIdproject
sourceIpproject
sourcePortproject

XbowMediumFindings

#

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

Creates an incident for each Medium severity finding reported by XBOW that is currently in an open state. These findings represent moderate security risks that should be addressed in a timely manner. Each alert is deduplicated per finding so re-ingestion of the same finding does not produce duplicate incidents.

MITRE ATT&CK Coverage

TacticTechniques
ReconnaissanceNo specific technique
Credential AccessNo specific technique
DiscoveryNo specific technique

Rule Body

id: b3c5e2f9-6a8d-4127-9b2e-4f6a8c9d0e12
name: XbowMediumFindings
description: |
  Creates an incident for each Medium severity finding reported by XBOW that is currently
  in an open state. These findings represent moderate security risks that should be
  addressed in a timely manner. Each alert is deduplicated per finding so re-ingestion
  of the same finding does not produce duplicate incidents.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: XbowSecurityConnector
    dataTypes:
      - XbowFindings_CL
      - XbowAssets_CL
queryFrequency: 30m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Reconnaissance
  - CredentialAccess
relevantTechniques: []
query: |
  XbowFindings_CL
  | where TimeGenerated > ago(1h)
  | where tolower(Severity) == 'medium'
  | where isempty(State) or tolower(State) == 'open'
  | summarize arg_max(TimeGenerated, *) by FindingId
  | join kind=leftouter (
      XbowAssets_CL
      | summarize arg_max(TimeGenerated, *) by AssetId
      | project AssetId, StartUrl
  ) on AssetId
  | project
      TimeGenerated,
      FindingId,
      FindingName,
      Severity,
      State,
      Summary,
      Impact,
      Mitigations,
      Recipe,
      AssetId,
      AssetName,
      OrganizationId,
      CreatedAt,
      StartUrl
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'XBOW Medium: {{FindingName}}'
  alertDescriptionFormat: 'Medium severity finding on asset {{AssetName}} ({{AssetId}}). {{Summary}}'
customDetails:
  FindingID: FindingId
  FindingName: FindingName
  Severity: Severity
  State: State
  AssetID: AssetId
  AssetName: AssetName
  OrganizationID: OrganizationId
  CreatedAt: CreatedAt
  Mitigations: Mitigations
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 24h
    matchingMethod: Selected
    groupByCustomDetails:
      - FindingID
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: StartUrl
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

XbowFindings_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where tolower(Severity) == 'medium'

Stage 4: where

| where isempty(State) or tolower(State) == 'open'

Stage 5: summarize

| summarize arg_max(TimeGenerated, *) by FindingId

Stage 6: join

| join kind=leftouter (
    XbowAssets_CL
    | summarize arg_max(TimeGenerated, *) by AssetId
    | project AssetId, StartUrl
) on AssetId

Stage 7: project

project AssetId, AssetName, CreatedAt, FindingId, FindingName, Impact, Mitigations, OrganizationId, Recipe, Severity, StartUrl, State, Summary, TimeGenerated

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Severityeq
  • medium transforms: tolower
field:"Severity" kind:eq value:"medium"
Stateeq
  • open transforms: tolower
field:"State" kind:eq value:"open"
Stateis_null
  • (no value, null check)
field:"State" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AssetIdproject
AssetNameproject
CreatedAtproject
FindingIdproject
FindingNameproject
Impactproject
Mitigationsproject
OrganizationIdproject
Recipeproject
Severityproject
StartUrlproject
Stateproject
Summaryproject
TimeGeneratedproject

Back to all Kusto rules