Detection rules › Kusto

Kusto rules: 0x

AWS Security Hub - Detect root user lacking MFA

#
Status
available
Severity
high
Time window
1h
Group by
AwsAccountId, AwsRegion, AwsSecurityFindingDescription, AwsSecurityFindingId, AwsSecurityFindingTitle, ComplianceSecurityControlId, RootUserARN
Source
github.com/Azure/Azure-Sentinel

This query detects AWS accounts where the root user does not have multi-factor authentication (MFA) enabled, using AWS Security Hub control IAM.9 findings. Lack of MFA on the root user increases the risk of unauthorized access and privilege abuse.

MITRE ATT&CK Coverage

Rule Body

id: 6b3b9b1d-0d5d-4d4a-9f0f-8d1e2c7a5f44
name: AWS Security Hub - Detect root user lacking MFA
description: |
  This query detects AWS accounts where the root user does not have multi-factor authentication (MFA) enabled, using AWS Security Hub control IAM.9 findings.
  Lack of MFA on the root user increases the risk of unauthorized access and privilege abuse.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWSSecurityHub
    dataTypes:
      - AWSSecurityHubFindings
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - Persistence
  - CredentialAccess
  - DefenseEvasion
relevantTechniques:
  - T1098
  - T1110
  - T1556.006
tags:
  - PCI DSS v3.2.1
  - NIST 800-53 r5
  - CIS AWS Foundations Benchmark v1.4.0
query: |
  AWSSecurityHubFindings
  | where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
  | where tostring(AwsSecurityFindingGeneratorId) == "security-control/IAM.9"
    or tostring(ComplianceSecurityControlId) == "IAM.9"
  | extend RootUserARN = tostring(Resources[0].Id)
  | summarize TimeGenerated = max(TimeGenerated)
      by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
         AwsSecurityFindingId, ComplianceSecurityControlId, RootUserARN
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AwsAccountId
      - identifier: CloudAppAccountId
        columnName: RootUserARN
customDetails:
  ComplianceControlId: ComplianceSecurityControlId
  Region: AwsRegion
  FindingId: AwsSecurityFindingId
  RootUserARN: RootUserARN
alertDetailsOverride:
  alertDisplayNameFormat: "AWS Account {{AwsAccountId}} root user lacks MFA"
  alertDescriptionFormat: "AWS Account {{AwsAccountId}} has root user without MFA (Resource: {{RootUserARN}})."
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSSecurityHubFindings

Stage 2: where

| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"

Stage 3: where

| where tostring(AwsSecurityFindingGeneratorId) == "security-control/IAM.9"
  or tostring(ComplianceSecurityControlId) == "IAM.9"

Stage 4: extend

| extend RootUserARN = tostring(Resources[0].Id)

Stage 5: summarize

| summarize TimeGenerated = max(TimeGenerated)
    by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
       AwsSecurityFindingId, ComplianceSecurityControlId, RootUserARN

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AwsAccountIdsummarize
AwsRegionsummarize
AwsSecurityFindingDescriptionsummarize
AwsSecurityFindingIdsummarize
AwsSecurityFindingTitlesummarize
ComplianceSecurityControlIdsummarize
RootUserARNsummarize
TimeGeneratedsummarize

AWSCloudTrail - Changes to AWS Elastic Load Balancer security groups

#
Status
available
Severity
low
Time window
1d
Group by
AWSRegion, AccountName, AccountUPNSuffix, AdditionalEventData, EventName, EventSource, RecipientAccountId, ResponseElements, SessionMfaAuthenticated, SourceIpAddress, UserAgent, UserIdentityAccountId, UserIdentityPrincipalid, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

Detects AWS CloudTrail events that modify Elastic Load Balancer security group associations. Unauthorized changes can expose services to unintended network access or weaken cloud boundary protections. Validate these updates against approved change control and deployment activity.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: c7bfadd4-34a6-4fa5-82f8-3691a32261e8
name: AWSCloudTrail - Changes to AWS Elastic Load Balancer security groups
description: |
  Detects AWS CloudTrail events that modify Elastic Load Balancer security group associations. Unauthorized
  changes can expose services to unintended network access or weaken cloud boundary protections. Validate these
  updates against approved change control and deployment activity.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AWSS3
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562.007
query: |
  let EventNameList = dynamic(["ApplySecurityGroupsToLoadBalancer", "SetSecurityGroups"]);
  AWSCloudTrail
  | where EventName in~ (EventNameList)
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
  | summarize EventCount=count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated)
  by EventSource, EventName, UserIdentityType,  SourceIpAddress, UserAgent, SessionMfaAuthenticated, AWSRegion,
  AdditionalEventData, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, ResponseElements
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  EventName: EventName
  EventSource: EventSource
  AWSRegion: AWSRegion
  UserAgent: UserAgent
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS ELB security group change by {{AccountName}} in {{AWSRegion}}'
  alertDescriptionFormat: 'Detected {{EventName}} from {{SourceIpAddress}} for account {{RecipientAccountId}} affecting ELB security group settings.'
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["ApplySecurityGroupsToLoadBalancer", "SetSecurityGroups"]);

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName in~ (EventNameList)

Stage 3: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Stage 4: summarize

| summarize EventCount=count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated)
by EventSource, EventName, UserIdentityType,  SourceIpAddress, UserAgent, SessionMfaAuthenticated, AWSRegion,
AdditionalEventData, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, ResponseElements

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventNamein
  • ApplySecurityGroupsToLoadBalancer
  • SetSecurityGroups
field:"aws::eventName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSRegionsummarize
AccountNamesummarize
AccountUPNSuffixsummarize
AdditionalEventDatasummarize
EndTimeUtcsummarize
EventCountsummarize
EventNamesummarize
EventSourcesummarize
RecipientAccountIdsummarize
ResponseElementssummarize
SessionMfaAuthenticatedsummarize
SourceIpAddresssummarize
StartTimeUtcsummarize
UserAgentsummarize
UserIdentityAccountIdsummarize
UserIdentityPrincipalidsummarize
UserIdentityTypesummarize

AWSCloudTrail - Network ACL with all the open ports to a specified CIDR

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

Detects Network ACL entries that allow very broad inbound access ranges, including all protocols or large port ranges. Such changes can significantly increase exposure and may indicate weakening of cloud network controls.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: f8ea7d50-e33b-4b9d-9c3e-a59fcbcee281
name: AWSCloudTrail - Network ACL with all the open ports to a specified CIDR
description: |
  Detects Network ACL entries that allow very broad inbound access ranges, including all protocols or large port
  ranges. Such changes can significantly increase exposure and may indicate weakening of cloud network controls.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562.007
query: |
    AWSCloudTrail
    | where EventName in ('CreateNetworkAclEntry', 'ReplaceNetworkAclEntry') and isempty(ErrorMessage) and isempty(ErrorCode)
    | extend ruleAction = tostring(parse_json(RequestParameters)['ruleAction']),
             egress=parse_json(RequestParameters)['egress'],
             total_ports=(toint(parse_json(parse_json(RequestParameters)['portRange'])['to']) - toint(parse_json(parse_json(RequestParameters)['portRange'])['from'])),
             aclProtocol=parse_json(RequestParameters)['aclProtocol']
    | where isnotempty(total_ports)
    | where ruleAction == 'allow' and egress == false and (aclProtocol == '-1' or (total_ports > 1024))
    | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
    | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
    | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
    | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
      AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  EventName: EventName
  RuleAction: ruleAction
  AclProtocol: aclProtocol
  AWSRegion: AWSRegion
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Network ACL overly permissive rule by {{AccountName}} in {{AWSRegion}}'
  alertDescriptionFormat: 'Detected {{EventName}} from {{SourceIpAddress}} that opened broad Network ACL access in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName in ('CreateNetworkAclEntry', 'ReplaceNetworkAclEntry') and isempty(ErrorMessage) and isempty(ErrorCode)

Stage 3: extend

| extend ruleAction = tostring(parse_json(RequestParameters)['ruleAction']),
         egress=parse_json(RequestParameters)['egress'],
         total_ports=(toint(parse_json(parse_json(RequestParameters)['portRange'])['to']) - toint(parse_json(parse_json(RequestParameters)['portRange'])['from'])),
         aclProtocol=parse_json(RequestParameters)['aclProtocol']

Stage 4: where

| where isnotempty(total_ports)

Stage 5: where

| where ruleAction == 'allow' and egress == false and (aclProtocol == '-1' or (total_ports > 1024))

Stage 6: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNamein
  • CreateNetworkAclEntry corpus 2 (kusto 1, panther 1)
  • ReplaceNetworkAclEntry corpus 2 (kusto 1, panther 1)
field:"aws::eventName" kind:in
aclProtocoleq
  • -1
field:"aclProtocol" kind:eq value:"-1"
egresseq
  • false
field:"egress" kind:eq value:"false"
ruleActioneq
  • allow
field:"ruleAction" kind:eq value:"allow"
total_portsgt
  • 1024 transforms: number
field:"total_ports" kind:gt value:"1024"
total_portsis_not_null
  • (no value, null check)
field:"total_ports" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
aclProtocolextend
egressextend
ruleActionextend
total_portsextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

BloodHound Attack Path Finding - Read GMSA Password Privileges on Tier Zero Objects

#

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

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

BloodHound Attack Path Finding - Read GMSA Password Privileges on Tier Zero Objects

Rule Body

id: d738e45b-472b-4d6b-a1a0-258c4fe9eb26
name: BloodHound Attack Path Finding - Read GMSA Password Privileges on Tier Zero
  Objects
description: BloodHound Attack Path Finding - Read GMSA Password Privileges on Tier
  Zero Objects
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"T0ReadGMSA\""
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: domain_name
customDetails:
  Description: ShortDescription
  Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BHEAttackPathsData_CL

Stage 2: where

| where Finding == "T0ReadGMSA"

Indicators

These rows show field, operator, and value matches.

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

Cisco Umbrella - Empty User Agent Detected

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

'Rule helps to detect empty and unusual user agent indicating web browsing activity by an unusual process other than a web browser.'

MITRE ATT&CK Coverage

TacticTechniques
Command & ControlNo specific technique

Rule Body

id: 2b328487-162d-4034-b472-59f1d53684a1
name: Cisco Umbrella - Empty User Agent Detected
description: |
  'Rule helps to detect empty and unusual user agent indicating web browsing activity by an unusual process other than a web browser.'
severity: Medium
requiredDataConnectors:
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes:
      - Cisco_Umbrella_proxy_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
query: |
  let timeframe = 15m;
  Cisco_Umbrella
  | where EventType == "proxylogs"
  | where TimeGenerated > ago(timeframe)
  | where HttpUserAgentOriginal == ''
  | extend Message = "Empty User Agent"
  | project Message, SrcIpAddr, DstIpAddr, UrlOriginal, TimeGenerated
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlOriginal
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 15m;

Stage 1: source

Cisco_Umbrella

Stage 2: where

| where EventType == "proxylogs"

Stage 3: where

| where TimeGenerated > ago(timeframe)

Stage 4: where

| where HttpUserAgentOriginal == ''

Stage 5: extend

| extend Message = "Empty User Agent"

Stage 6: project

| project Message, SrcIpAddr, DstIpAddr, UrlOriginal, TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
Messageproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

Dev-0228 File Path Hashes November 2021 (ASIM Version)

#
Severity
high
Time window
6h
Group by
DvcId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This hunting query looks for file paths/hashes related to observed activity by Dev-0228. The actor is known to use custom version of popular tool like PsExec, Procdump etc. to carry its activity. The risk score associated with each result is based on a number of factors, hosts with higher risk events should be investigated first. This query uses the Microsoft Sentinel Information Model - https://docs.microsoft.com/azure/sentinel/normalization

MITRE ATT&CK Coverage

TacticTechniques
Execution
Credential Access

Telemetry Coverage

Rule Body

id: 29a29e5d-354e-4f5e-8321-8b39d25047bf
name: Dev-0228 File Path Hashes November 2021 (ASIM Version)
description: |
   'This hunting query looks for file paths/hashes related to observed activity by Dev-0228. The actor is known to use custom version of popular tool like PsExec, Procdump etc. to carry its activity.
    The risk score associated with each result is based on a number of factors, hosts with higher risk events should be investigated first.
    This query uses the Microsoft Sentinel Information Model - https://docs.microsoft.com/azure/sentinel/normalization'
severity: High
requiredDataConnectors: []
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Execution
relevantTechniques:
  - T1569
  - T1003
query: | 
  let files1 = dynamic(["C:\\Windows\\TAPI\\lsa.exe", "C:\\Windows\\TAPI\\pa.exe", "C:\\Windows\\TAPI\\pc.exe", "C:\\Windows\\TAPI\\Rar.exe"]);
  let files2 = dynamic(["svchost.exe","wdmsvc.exe"]);
  let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77",     "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);
  let FileHash2 = dynamic(["2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7", "9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd", "18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b"]);
  imProcessCreate
  | where ((Process has_any (files1)) and (ActingProcessSHA256 has_any (FileHash1))) or ((Process has_any (files2)) and (ActingProcessSHA256 has_any (FileHash2)))
  // Increase risk score if recent alerts for the host
  | join kind=leftouter (
    SecurityAlert
    | where ProviderName =~ "MDATP"
    | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
    | mv-expand todynamic(Entities)
    | extend DvcId = tostring(parse_json(Entities).MdatpDeviceId)
    | where isnotempty(DvcId)
    // Higher risk score are for Defender alerts related to threat actor
    | extend AlertRiskScore = iif(ThreatName has_any ("Backdoor:MSIL/ShellClient.A", "Backdoor:MSIL/ShellClient.A!dll", "Trojan:MSIL/Mimikatz.BA!MTB"), 1.0, 0.5)
    | project DvcId, AlertRiskScore) 
    on DvcId
  | extend AlertRiskScore = iif(isempty(AlertRiskScore), 0.0, AlertRiskScore)
  | extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountNTDomain = tostring(split(ActorUsername, @'\')[0])
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUsername
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: ActingProcessFilename
version: 1.2.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Parameters

let files1 = dynamic(["C:\\Windows\\TAPI\\lsa.exe", "C:\\Windows\\TAPI\\pa.exe", "C:\\Windows\\TAPI\\pc.exe", "C:\\Windows\\TAPI\\Rar.exe"]);
let files2 = dynamic(["svchost.exe","wdmsvc.exe"]);

Let Binding: FileHash1 used in Stage 2

let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77",     "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);

Let Binding: FileHash2 used in Stage 2

let FileHash2 = dynamic(["2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7", "9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd", "18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b"]);

Stage 1: source

imProcessCreate

Stage 2: where

| where ((Process has_any (files1)) and (ActingProcessSHA256 has_any (FileHash1))) or ((Process has_any (files2)) and (ActingProcessSHA256 has_any (FileHash2)))

Stage 3: join

| join kind=leftouter (
  SecurityAlert
  | where ProviderName =~ "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | mv-expand todynamic(Entities)
  | extend DvcId = tostring(parse_json(Entities).MdatpDeviceId)
  | where isnotempty(DvcId)
  | extend AlertRiskScore = iif(ThreatName has_any ("Backdoor:MSIL/ShellClient.A", "Backdoor:MSIL/ShellClient.A!dll", "Trojan:MSIL/Mimikatz.BA!MTB"), 1.0, 0.5)
  | project DvcId, AlertRiskScore) 
  on DvcId

Stage 4: extend (4 consecutive steps)

extend AccountNTDomain, AccountName, AlertRiskScore, DomainIndex, HostName, HostNameDomain

Stage 5: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActingProcessSHA256match
  • 010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77 transforms: term
  • 18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b transforms: term
  • 2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7 transforms: term
  • 43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3 transforms: term
  • 56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7 transforms: term
  • 9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd transforms: term
  • ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb transforms: term
field:"ActingProcessSHA256" kind:match
DvcIdis_not_null
  • (no value, null check)
field:"DvcId" kind:is_not_null
Processmatch
  • C:\Windows\TAPI\Rar.exe transforms: term
  • C:\Windows\TAPI\lsa.exe transforms: term
  • C:\Windows\TAPI\pa.exe transforms: term
  • C:\Windows\TAPI\pc.exe transforms: term
  • svchost.exe transforms: term
  • wdmsvc.exe transforms: term
field:"Process" kind:match
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertRiskScoreextend
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend

GitHub - pull request was created

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

Detect activities when a pull request was created. This query runs every day and its severity is Medium.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 0b85a077-8ba5-4cb5-90f7-1e882afe10c7
name: GitHub - pull request was created
description: |
  'Detect activities when a pull request was created. This query runs every day and its severity is Medium.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
    GitHubAuditData 
    | where Action == "pull_request.create"
    | project Actor, Action
    | extend AccountName = tostring(split(Actor, "@")[0]), AccountUPNSuffix = tostring(split(Actor, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GitHubAuditData

Stage 2: where

| where Action == "pull_request.create"

Stage 3: project

| project Actor, Action

Stage 4: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • pull_request.create
field:"Action" kind:eq value:"pull_request.create"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
Actorproject
AccountNameextend
AccountUPNSuffixextend

Hypervisor Host Deleted

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

Detects when a hypervisor host is deleted from Veeam Backup & Replication. This might indicate unauthorized changes to the virtualization environment.

Rule Body

id: 4e41c85f-d495-4a23-a218-41b938140dce
name: Hypervisor Host Deleted
description: Detects when a hypervisor host is deleted from Veeam Backup & Replication.
  This might indicate unauthorized changes to the virtualization environment.
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 == 24070 \n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    [\"DataSource\"] =\
  \ original_host,\n    EventId = instanceId,\n    UserName = user,\n  [\"MessageDetails\"\
  ] = Description,\n    Severity = SeverityDescription"
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: FullName
    columnName: DataSource
version: 1.0.1
kind: Scheduled
customDetails:
  messageDetails: MessageDetails

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 24070

Stage 3: project

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

Identify instances where a single source is observed using multiple user agents (ASIM Web Session)

#
Status
available
Severity
medium
Time window
5m
Group by
SrcHostname, SrcIpAddr, SrcUsername
Source
github.com/Azure/Azure-Sentinel

'This detection mechanism identifies requests originating from a single source within a brief time period that exhibit multiple user agents. Such behavior could indicate unusual web browsing activities performed by unconventional processes'

MITRE ATT&CK Coverage

Rule Body

id: 813ccf3b-0321-4622-b0bc-63518fd14454
name: Identify instances where a single source is observed using multiple user agents (ASIM Web Session)
description: |
  'This detection mechanism identifies requests originating from a single source within a brief time period that exhibit multiple user agents. Such behavior could indicate unusual web browsing activities performed by unconventional processes'
severity: Medium
status: Available 
tags:
  - Schema: WebSession
    SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1190
  - T1133
  - T1528
query: |
  let threshold = 5; // Please update threshold limit as per your environment
  _Im_WebSession(starttime=ago(1h), eventresult="Success")
  | where isnotempty(HttpUserAgent)
  | summarize
      EventCount=count(),
      UserAgentList=make_set(HttpUserAgent, 100),
      URL_List = make_set(Url, 100),
      DestinationIPList = make_set(DstIpAddr, 100)
      by SrcIpAddr, SrcUsername, SrcHostname, bin(TimeGenerated, 5min)
  | extend UserAgentCount = array_length(UserAgentList)
  | where UserAgentCount > threshold
  | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),""), Threshold=threshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcHostname
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  UserAgentCount: UserAgentCount
  UserAgentArray: UserAgentList
  UserAgentThreshold: Threshold
  URLs: URL_List
  DestinationIPList: DestinationIPList
alertDetailsOverride:
  alertDisplayNameFormat: "User '{{SrcUsername}}' with IP '{{SrcIpAddr}}' has been observed using high number of User Agents within short timeframe"
  alertDescriptionFormat: "The system has detected high User Agent count of '{{UserAgentCount}}' originating from '{{SrcUsername}}'. Further investigation is necessary to determine the reason behind the detection of multiple user agents associated with the SrcIpAddr in this incident. User Agent list include: '{{UserAgentList}}'"
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

_Im_WebSession(starttime=ago(1h), eventresult="Success")

Stage 2: where

| where isnotempty(HttpUserAgent)

Stage 3: summarize

| summarize
    EventCount=count(),
    UserAgentList=make_set(HttpUserAgent, 100),
    URL_List = make_set(Url, 100),
    DestinationIPList = make_set(DstIpAddr, 100)
    by SrcIpAddr, SrcUsername, SrcHostname, bin(TimeGenerated, 5min)

Stage 4: extend

| extend UserAgentCount = array_length(UserAgentList)

Stage 5: where

| where UserAgentCount > threshold

Stage 6: extend

| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),""), Threshold=threshold

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentis_not_null
  • (no value, null check)
field:"HttpUserAgent" kind:is_not_null
UserAgentCountgt
  • 5 transforms: number
field:"UserAgentCount" kind:gt value:"5"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIPListsummarize
EventCountsummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
URL_Listsummarize
UserAgentListsummarize
UserAgentCountextend
Nameextend
Thresholdextend
UPNSuffixextend

Mimecast Audit - Logon Authentication Failed

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

Detects threat when logon authentication failure found in audit

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 9c5dcd76-9f6d-42a3-b984-314b52678f20
name: Mimecast Audit - Logon Authentication Failed
apiVersion: 2021-09-01-preview
description: Detects threat when logon authentication failure found in audit
displayName: Mimecast Audit - Logon Authentication Failed
severity: High
requiredDataConnectors:
  - connectorId: MimecastAuditAPI
    dataTypes:
      - MimecastAudit_CL  
enabled: true
query: MimecastAudit_CL | where src_s !="" and auditType_s == "Logon Authentication Failed"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 3
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Discovery
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1110
alertRuleTemplateName:
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
customDetails:
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: src_s
- entityType: Mailbox
  fieldMappings:
  - identifier: MailboxPrimaryAddress
    columnName: user_s
- entityType: CloudApplication
  fieldMappings:
  - identifier: AppId
    columnName: app_s
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastAudit_CL

Stage 2: where

| where src_s !="" and auditType_s == "Logon Authentication Failed"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
auditType_seq
  • Logon Authentication Failed
field:"auditType_s" kind:eq value:"Logon Authentication Failed"

Pathlock TDnR - System Security Policy Changes

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

Detects changes to the SAP system security policy, forwarded by Pathlock Threat Detection and Response. Modifications to security policies may indicate attempts to weaken SAP's built-in security controls, lower password complexity, or disable audit logging.

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c16
name: Pathlock TDnR - System Security Policy Changes
kind: Scheduled
description: >-
  Detects changes to the SAP system security policy, forwarded by Pathlock Threat Detection
  and Response. Modifications to security policies may indicate attempts to weaken SAP's
  built-in security controls, lower password complexity, or disable audit logging.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1562
  - T1098
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_SECURITY_P"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "CHANGEDOC_SECURITY_P"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

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

Potentially Relayed NTLM Authentication - Microsoft Defender for Endpoint

#
Group by
$left.DeviceIdX, $left.RemoteDeviceName, $left.RemoteIP, $right.DeviceId, $right.DeviceName, $right.DvcIP, AccountName, DeviceId, DeviceName, LogonId, RemoteDeviceName, RemoteIP
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

The below query detects NTLM logons where Network Address in the logon event doesn't match the Workstation Name's IP. This indicates potentially relayed NTLM authentication. It analyzes only the logons with domain accounts having admin privileges.

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique

Telemetry Coverage

Rule Body

// Author       : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-ntlm-relay-attacks-d92e99e68fb9
//
// Description: This query detects NTLM logons where RemoteIP in the logon event doesn't match the RemoteDevice's IP. 
//				      This indicates potentially relayed NTLM authentication. The query analyzes only the logons with domain accounts having admin privileges. 
//
// Query parameters:
//
let lookup_window = 24h;
let baseline_window = 7d;
// Specify domains in NETBIOS name and full domain format
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);
// Exclude authentications coming from  device performing SNAT.
let SNAT_Subnets = datatable (subnet:string)
[
"1.0.0.0/26", "1.1.1.1/32"
];
// Generate list of all known(enrolled) Devices
let all_devices = toscalar (
    DeviceInfo
    | where Timestamp > ago(baseline_window)
    | summarize make_set(DeviceName)
    );
// Create a baseline for known NTLM authentication events.
// This will be used for removing the potential false positives.
let baseline = materialize (
    DeviceLogonEvents
    | where Timestamp > ago(baseline_window) and Timestamp < ago(lookup_window)
    | where ActionType == "LogonSuccess"
    | where LogonType == "Network"
    | where Protocol=="NTLM"
    | where isnotempty(RemoteDeviceName) and isnotempty(RemoteIP)
    | where RemoteIPType <> "Loopback"
    | where AdditionalFields !has '{"IsLocalLogon":true}' // exclude local(interactive) logon
    | where AccountName !has RemoteDeviceName // exclude computer account logon
    | where AccountDomain in~ (domains) // get only the logons with domain accounts
    | distinct DeviceName, RemoteDeviceName, AccountName, RemoteIP
    );
// Generate list of servers (assuming NTLM relay is performed towards servers)
let servers = materialize (
    DeviceInfo
    | where Timestamp > ago(baseline_window)
    | where DeviceType == "Server"
    | summarize make_set(DeviceName)
    );
// Get logons to servers with LocalAdmin rights
DeviceLogonEvents
| where Timestamp > ago(lookup_window)
| where ActionType == "LogonSuccess"
| where DeviceName in (servers)
| where LogonType == "Network"
| where IsLocalAdmin == 1
| project TimestampX=Timestamp, DeviceIdX=DeviceId, DeviceName,AccountName,IsLocalAdmin
// Join LocalAdmin logons with NTLM logons. LocalAdmin logon events don't have logonID, Protocol, etc.,
// use time window join. 
| join kind=inner 
    (
    DeviceLogonEvents
    | where Timestamp > ago(lookup_window)
    | where ActionType == "LogonSuccess"
    | where LogonType == "Network"
    | where Protocol=="NTLM"
    | where isnotempty(RemoteDeviceName) and isnotempty(RemoteIP)
    | where RemoteIPType <> "Loopback"
    | where AdditionalFields !has '{"IsLocalLogon":true}' // exclude local(interactive) logon
    | where AccountName !has RemoteDeviceName // exclude computer account logon
    | where AccountDomain in~ (domains) // get only the logons with domain accounts
    )
    on $left.DeviceIdX==$right.DeviceId, AccountName 
| where abs(datetime_diff('second', Timestamp, TimestampX)) < 15 // time window condition
| summarize arg_max(Timestamp,*) by DeviceId, LogonId // get last event for each logonID
// Filter logons that are not in the baseline(unknown/new logons)
| join kind=leftanti baseline on DeviceName, RemoteDeviceName, AccountName, RemoteIP
// Filter events where there is no corresponding IP address for the RemoteDeviceName
| join kind=leftanti 
    (
    DeviceNetworkInfo
    | where Timestamp > ago(lookup_window)
    | mv-expand todynamic(IPAddresses)
    | extend DvcIP = tostring(IPAddresses.IPAddress)
    | summarize arg_max(Timestamp,*) by DeviceId, DvcIP // get last report event for each IP
    | project DeviceId, DeviceName=replace(@'([A-z0-9-]+)\.?.*',@'\1',DeviceName), ReportTimestamp = Timestamp, DvcIP, IPAddresses
    )
    on $left.RemoteDeviceName==$right.DeviceName, $left.RemoteIP==$right.DvcIP // filter condition
// Get last logon event (remove duplication)
| summarize arg_max(Timestamp,*), count() by DeviceId, AccountName, RemoteDeviceName, RemoteIP
// Get only the logons originated from a known(enrolled) device.
| where all_devices has RemoteDeviceName
// Exclude SNAT subnets
// ipv4 lookup doesn't have notmatch condition. 
| evaluate ipv4_lookup(SNAT_Subnets, RemoteIP, subnet, return_unmatched = true)
| where isempty(subnet) // remove results that matched a SNAT subnet.
| extend Origin = RemoteDeviceName, RelayingDeviceIP = RemoteIP, Target = DeviceName
| project-away TimestampX, DeviceIdX, AccountName1, DeviceName1
| project-reorder Timestamp, Origin, RelayingDeviceIP, Target, AccountName

Stages and Predicates

Parameters

let lookup_window = 24h;
let baseline_window = 7d;
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);

Let Binding: SNAT_Subnets

let SNAT_Subnets = datatable (subnet:string)
[
"1.0.0.0/26", "1.1.1.1/32"
];

Let Binding: all_devices used in Stage 15

let all_devices = toscalar (
    DeviceInfo
    | where Timestamp > ago(baseline_window)
    | summarize make_set(DeviceName)
    );

Let Binding: baseline used in Stage 12

let baseline = materialize (
    DeviceLogonEvents
    | where Timestamp > ago(baseline_window) and Timestamp < ago(lookup_window)
    | where ActionType == "LogonSuccess"
    | where LogonType == "Network"
    | where Protocol=="NTLM"
    | where isnotempty(RemoteDeviceName) and isnotempty(RemoteIP)
    | where RemoteIPType <> "Loopback"
    | where AdditionalFields !has '{"IsLocalLogon":true}'
    | where AccountName !has RemoteDeviceName
    | where AccountDomain in~ (domains)
    | distinct DeviceName, RemoteDeviceName, AccountName, RemoteIP
    );

Let Binding: servers used in Stages 1, 5

let servers = materialize (
    DeviceInfo
    | where Timestamp > ago(baseline_window)
    | where DeviceType == "Server"
    | summarize make_set(DeviceName)
    );

Stage 1: source

let servers

Stage 2: source

DeviceLogonEvents

Stage 3: where

where Timestamp > ago(86400s)

Stage 4: where

where ActionType == "LogonSuccess"

Stage 5: where

where DeviceName == "servers"

Stage 6: where

where LogonType == "Network"

Stage 7: where

where IsLocalAdmin == 1

Stage 8: project

project AccountName, DeviceIdX, DeviceName, IsLocalAdmin, TimestampX

Stage 9: join

join kind=inner (DeviceLogonEvents) on $left.DeviceIdX, $right.DeviceId, AccountName

Stage 10: where

where /* macro: (abs(datetime_diff('second', Timestamp, TimestampX)) < 15) */

Stage 11: summarize

summarize by DeviceId, LogonId

Stage 12: join (negated)

join kind=leftanti (baseline) on DeviceName, RemoteDeviceName, AccountName, RemoteIP

Stage 13: join (negated)

join kind=leftanti (DeviceNetworkInfo) on $left.RemoteDeviceName, $right.DeviceName, $left.RemoteIP, $right.DvcIP

Stage 14: summarize

summarize by DeviceId, AccountName, RemoteDeviceName, RemoteIP

Stage 15: where

where all_devices =~ RemoteDeviceName

Stage 16: evaluate

evaluate

Stage 17: where

where isempty(subnet)

Stage 18: extend

extend Origin, RelayingDeviceIP, Target

Stage 19: project-away

project-away AccountName1, DeviceIdX, DeviceName1, TimestampX

Stage 20: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AdditionalFieldsmatch{"IsLocalLogon":true}excludes:AdditionalFields
AccountDomaininPUT YOUR AD DOMAINS HERE!, contoso, contoso.localexcludes:AccountDomain field:"AccountDomain" value:"PUT YOUR AD DOMAINS HERE!" field:"AccountDomain" value:"contoso" field:"AccountDomain" value:"contoso.local"
AccountNamecross_field_compareRemoteDeviceNameexcludes:AccountName field:"AccountName" value:"RemoteDeviceName"
ActionTypeeqLogonSuccessexcludes:ActionType field:"ActionType" value:"LogonSuccess"
LogonTypeeqNetworkexcludes:LogonType field:"LogonType" value:"Network"
ProtocoleqNTLMexcludes:Protocol field:"Protocol" value:"NTLM"
RemoteDeviceNameis_not_null(no value, null check)excludes:RemoteDeviceName
RemoteIPis_not_null(no value, null check)excludes:RemoteIP
RemoteIPTypeneLoopbackexcludes:RemoteIPType field:"RemoteIPType" value:"Loopback"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
DeviceIdsummarize
RemoteDeviceNamesummarize
RemoteIPsummarize
Originextend
RelayingDeviceIPextend
Targetextend

PRODAFT USTA - Corporate credential compromised

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

'Identifies a new PRODAFT USTA Account Takeover Prevention ticket that exposes a corporate credential (IsCorporate = true). These typically originate from infostealer infections on a victim host and indicate that a valid corporate identity may be available to threat actors. Investigate the affected account, reset credentials, and review the victim host for malware.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Credential Access

Rule Body

id: 9ad6cf22-ffbb-4422-9933-9bbd0104f818
name: PRODAFT USTA - Corporate credential compromised
description: |
  'Identifies a new PRODAFT USTA Account Takeover Prevention ticket that exposes a
  corporate credential (IsCorporate = true). These typically originate from infostealer
  infections on a victim host and indicate that a valid corporate identity may be
  available to threat actors. Investigate the affected account, reset credentials, and
  review the victim host for malware.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: PRODAFTUstaATPCCPDefinition
    dataTypes:
      - PRODAFTUstaCompromisedCredentials_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - InitialAccess
relevantTechniques:
  - T1555
  - T1078
query: |
  PRODAFTUstaCompromisedCredentials
  | where Created > ago(1h)
  | where IsCorporate == true
  | project
      TimeGenerated,
      Created,
      TicketId,
      CompanyName,
      Username,
      Url,
      Source,
      PasswordScore,
      VictimComputerName,
      VictimOs,
      VictimCountry,
      VictimIp,
      VictimMalware,
      InfectionDate,
      Status
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Username
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: VictimComputerName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: VictimIp
customDetails:
  Ticket: TicketId
  Company: CompanyName
  Malware: VictimMalware
  Source: Source
  PasswordScore: PasswordScore
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: Selected
    groupByEntities:
      - Account
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

PRODAFTUstaCompromisedCredentials

Stage 2: where

| where Created > ago(1h)

Stage 3: where

| where IsCorporate == true

Stage 4: project

| project
    TimeGenerated,
    Created,
    TicketId,
    CompanyName,
    Username,
    Url,
    Source,
    PasswordScore,
    VictimComputerName,
    VictimOs,
    VictimCountry,
    VictimIp,
    VictimMalware,
    InfectionDate,
    Status

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CompanyNameproject
Createdproject
InfectionDateproject
PasswordScoreproject
Sourceproject
Statusproject
TicketIdproject
TimeGeneratedproject
Urlproject
Usernameproject
VictimComputerNameproject
VictimCountryproject
VictimIpproject
VictimMalwareproject
VictimOsproject

Recorded Future Identity - Credential Exposure Detected

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

'Creates incidents when Recorded Future Identity detects compromised credentials for users in your organization'

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique

Rule Body

# Keep in sync with ../../Data Connectors/azuredeploy-incident-creation-analytic-rule.json
# (used by the standalone README / Deploy to Azure button path)
id: b1c2d3e4-5678-90ab-cdef-444444444444
name: Recorded Future Identity - Credential Exposure Detected
description: |
  'Creates incidents when Recorded Future Identity detects compromised credentials for users in your organization'
severity: High
status: Available
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques: []
requiredDataConnectors:
  - connectorId: RecordedFutureIdentityAlertImporter
    dataTypes:
      - RFI_PlaybookAlertResults_V2_CL
query: |
  RFI_PlaybookAlertResults_V2_CL
  | where TimeGenerated >= now(-15m)
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'Identity Exposure: {{panel_status_entity_name}} with priority: {{panel_status_priority}}'
  alertDescriptionFormat: |
    _Recorded Future Identity Alert_

    **Rule Name:** {{panel_status_alert_rule_name}}

    **Alert ID:** {{playbook_alert_id}}

    **Evidence Summary:** {{alert_description}}

    Investigate this identity by searching in log analytics workspace for the Alert ID.
  alertDynamicProperties: []
customDetails:
  RFLabel: Type
  AlertId: playbook_alert_id
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: panel_status_entity_name
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT15M
    matchingMethod: AllEntities
    groupByEntities:
      - Account
version: 1.1.0
kind: NRT

Stages and Predicates

Stage 1: source

RFI_PlaybookAlertResults_V2_CL

Stage 2: where

| where TimeGenerated >= now(-15m)

Unauthorized DHCP configuration in the network (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect an unauthorized DHCP configuration indicating a possible unauthorized device configuration.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: c52ec521-9188-4a9e-a4cd-34a3dfbc3d27
name: Unauthorized DHCP configuration in the network (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect an unauthorized DHCP configuration indicating a possible unauthorized device configuration.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T0842
query: |
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName == "Device Failed to Receive a Dynamic IP Address"
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName == "Device Failed to Receive a Dynamic IP Address"

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNameeq
  • Device Failed to Receive a Dynamic IP Address
field:"AlertName" kind:eq value:"Device Failed to Receive a Dynamic IP Address"
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

Unauthorized remote access to the network (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect unauthorized remote access to network devices, if another device on the network is compromised, target devices can be accessed remotely, increasing the attack surface.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Lateral Movement

Rule Body

id: 1ff4fa3d-150b-4c87-b733-26c289af0d49
name: Unauthorized remote access to the network (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect unauthorized remote access to network devices, if another device on the network is compromised, target devices can be accessed remotely, increasing the attack surface.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T0886
query: |
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName == "Unauthorized SSH Access"
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName == "Unauthorized SSH Access"

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNameeq
  • Unauthorized SSH Access
field:"AlertName" kind:eq value:"Unauthorized SSH Access"
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

VMware Cloud Web Security - Policy Publish Event

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

This alert is capturing events when VMware CWS policies were published. During publish, the VMware Edge Cloud Orchestrator deploys the CWS policies in SASE POPs, making them effective. All new rules will be enforced. Depending on the contents of the policy, this might create an impact on the CWS Data Plane traffic.

Rule Body

id: b26a7d97-6b6e-43ab-870e-eb18460ae602
name: VMware Cloud Web Security - Policy Publish Event
version: 1.0.0
kind: Scheduled
description: This alert is capturing events when VMware CWS policies were published. During publish, the VMware Edge Cloud Orchestrator deploys the CWS policies in SASE POPs, making them effective. All new rules will be enforced. Depending on the contents of the policy, this might create an impact on the CWS Data Plane traffic.
severity: Informational
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
  - connectorId: VMwareSDWAN
    dataTypes:
      - CWS
query: |+
  VMware_VECO_EventLogs_CL
  | where event == "CWS_EVENT"
  | extend cwsPolicyAction = todynamic(detail).subEvent
  | where cwsPolicyAction == "CWS_POLICY_PUBLISHED"
  | extend cwsPolicyName = todynamic(detail).policyName

suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDescriptionFormat: |-
    CWS Policy Published: {{cwsPolicyName}}

    During publish, the VMware Edge Cloud Orchestrator deploys the CWS policies in SASE POPs, making them effective. All new rules will be enforced. Depending on the contents of the policy, this might create an impact on the CWS Data Plane traffic.
  alertDynamicProperties: []
suppressionDuration: 5h

Stages and Predicates

Stage 1: source

VMware_VECO_EventLogs_CL

Stage 2: where

| where event == "CWS_EVENT"

Stage 3: extend

| extend cwsPolicyAction = todynamic(detail).subEvent

Stage 4: where

| where cwsPolicyAction == "CWS_POLICY_PUBLISHED"

Stage 5: extend

| extend cwsPolicyName = todynamic(detail).policyName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
cwsPolicyActionextend
cwsPolicyNameextend

Back to all Kusto rules