Detection rules › Kusto
Kusto rules: 0x
AWS Security Hub - Detect root user lacking MFA
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Credential Access |
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.
| Field | Source |
|---|---|
AwsAccountId | summarize |
AwsRegion | summarize |
AwsSecurityFindingDescription | summarize |
AwsSecurityFindingId | summarize |
AwsSecurityFindingTitle | summarize |
ComplianceSecurityControlId | summarize |
RootUserARN | summarize |
TimeGenerated | summarize |
AWSCloudTrail - Changes to AWS Elastic Load Balancer security groups
#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
| Tactic | Techniques |
|---|---|
| Defense Impairment |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | in |
| field:"aws::eventName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | summarize |
AccountName | summarize |
AccountUPNSuffix | summarize |
AdditionalEventData | summarize |
EndTimeUtc | summarize |
EventCount | summarize |
EventName | summarize |
EventSource | summarize |
RecipientAccountId | summarize |
ResponseElements | summarize |
SessionMfaAuthenticated | summarize |
SourceIpAddress | summarize |
StartTimeUtc | summarize |
UserAgent | summarize |
UserIdentityAccountId | summarize |
UserIdentityPrincipalid | summarize |
UserIdentityType | summarize |
AWSCloudTrail - Network ACL with all the open ports to a specified CIDR
#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
| Tactic | Techniques |
|---|---|
| Defense Impairment |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
aclProtocol | eq |
| field:"aclProtocol" kind:eq value:"-1" |
egress | eq |
| field:"egress" kind:eq value:"false" |
ruleAction | eq |
| field:"ruleAction" kind:eq value:"allow" |
total_ports | gt |
| field:"total_ports" kind:gt value:"1024" |
total_ports | is_not_null | field:"total_ports" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
aclProtocol | extend |
egress | extend |
ruleAction | extend |
total_ports | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0ReadGMSA" |
Cisco Umbrella - Empty User Agent Detected
#'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
| Tactic | Techniques |
|---|---|
| Command & Control | No 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"proxylogs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
Message | project |
SrcIpAddr | project |
TimeGenerated | project |
UrlOriginal | project |
Dev-0228 File Path Hashes November 2021 (ASIM Version)
#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
| Tactic | Techniques |
|---|---|
| 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
let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77", "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);
Let Binding: FileHash2
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActingProcessSHA256 | match |
| field:"ActingProcessSHA256" kind:match |
DvcId | is_not_null | field:"DvcId" kind:is_not_null | |
Process | match |
| field:"Process" kind:match |
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertRiskScore | extend |
AccountNTDomain | extend |
AccountName | extend |
HostName | extend |
HostNameDomain | extend |
GitHub - pull request was created
#Detect activities when a pull request was created. This query runs every day and its severity is Medium.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GitHub | Audit log action pull_request.create: Pull request created |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"pull_request.create" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
Actor | project |
AccountName | extend |
AccountUPNSuffix | extend |
Hypervisor Host Deleted
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"24070" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Identify instances where a single source is observed using multiple user agents (ASIM Web Session)
#'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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgent | is_not_null | field:"HttpUserAgent" kind:is_not_null | |
UserAgentCount | gt |
| field:"UserAgentCount" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIPList | summarize |
EventCount | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
URL_List | summarize |
UserAgentList | summarize |
UserAgentCount | extend |
Name | extend |
Threshold | extend |
UPNSuffix | extend |
Mimecast Audit - Logon Authentication Failed
#Detects threat when logon authentication failure found in audit
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
auditType_s | eq |
| field:"auditType_s" kind:eq value:"Logon Authentication Failed" |
Pathlock TDnR - System Security Policy Changes
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"CHANGEDOC_SECURITY_P" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Potentially Relayed NTLM Authentication - Microsoft Defender for Endpoint
#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
| Tactic | Techniques |
|---|---|
| Credential Access | No 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
let all_devices = toscalar (
DeviceInfo
| where Timestamp > ago(baseline_window)
| summarize make_set(DeviceName)
);
Let Binding: baseline
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountDomain | in |
| field:"AccountDomain" kind:in |
ActionType | eq |
| field:"ActionType" kind:eq value:"LogonSuccess" |
AdditionalFields | match |
| field:"AdditionalFields" kind:match |
DeviceName | in |
| field:"DeviceName" kind:in value:"servers" |
IsLocalAdmin | eq |
| field:"IsLocalAdmin" kind:eq value:"1" |
LogonType | eq |
| field:"LogonType" kind:eq value:"Network" |
Protocol | eq |
| field:"Protocol" kind:eq value:"NTLM" |
RemoteDeviceName | is_not_null | field:"RemoteDeviceName" kind:is_not_null | |
RemoteIP | is_not_null | field:"RemoteIP" kind:is_not_null | |
RemoteIPType | ne |
| field:"RemoteIPType" kind:ne value:"Loopback" |
subnet | is_null | field:"subnet" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AdditionalFields | match | {"IsLocalLogon":true} | excludes:AdditionalFields |
AccountDomain | in | PUT YOUR AD DOMAINS HERE!, contoso, contoso.local | excludes:AccountDomain field:"AccountDomain" value:"PUT YOUR AD DOMAINS HERE!" field:"AccountDomain" value:"contoso" field:"AccountDomain" value:"contoso.local" |
AccountName | cross_field_compare | RemoteDeviceName | excludes:AccountName field:"AccountName" value:"RemoteDeviceName" |
ActionType | eq | LogonSuccess | excludes:ActionType field:"ActionType" value:"LogonSuccess" |
LogonType | eq | Network | excludes:LogonType field:"LogonType" value:"Network" |
Protocol | eq | NTLM | excludes:Protocol field:"Protocol" value:"NTLM" |
RemoteDeviceName | is_not_null | excludes:RemoteDeviceName | |
RemoteIP | is_not_null | excludes:RemoteIP | |
RemoteIPType | ne | Loopback | excludes:RemoteIPType field:"RemoteIPType" value:"Loopback" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
DeviceId | summarize |
RemoteDeviceName | summarize |
RemoteIP | summarize |
Origin | extend |
RelayingDeviceIP | extend |
Target | extend |
PRODAFT USTA - Corporate credential compromised
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
IsCorporate | eq |
| field:"IsCorporate" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CompanyName | project |
Created | project |
InfectionDate | project |
PasswordScore | project |
Source | project |
Status | project |
TicketId | project |
TimeGenerated | project |
Url | project |
Username | project |
VictimComputerName | project |
VictimCountry | project |
VictimIp | project |
VictimMalware | project |
VictimOs | project |
Recorded Future Identity - Credential Exposure Detected
#'Creates incidents when Recorded Future Identity detects compromised credentials for users in your organization'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No 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)
VMware Cloud Web Security - Policy Publish Event
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
cwsPolicyAction | eq |
| field:"cwsPolicyAction" kind:eq value:"CWS_POLICY_PUBLISHED" |
event | eq |
| field:"event" kind:eq value:"CWS_EVENT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
cwsPolicyAction | extend |
cwsPolicyName | extend |