Detection rules › Kusto
Kusto rules: 06
1Password - New service account integration created
#This will alert when a new integration has been created. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 1Password | sa-create: Create Integration |
Rule Body
id: 26daed54-cea5-469c-9b6e-0d85a40dc463
name: 1Password - New service account integration created
version: 1.0.0
kind: Scheduled
description: |-
This will alert when a new integration has been created. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.
Ref: https://1password.com/
Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
- connectorId: 1Password
dataTypes:
- OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1136
query: |-
OnePasswordEventLogs_CL
| where log_source == "auditevents"
| where action == "create"
| where object_type == "sa"
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
subTechniques:
- T1136.003
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
Stages and Predicates
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where log_source == "auditevents"
Stage 3: where
| where action == "create"
Stage 4: where
| where object_type == "sa"
Stage 5: extend
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"create" |
log_source | eq |
| field:"log_source" kind:eq value:"auditevents" |
object_type | eq |
| field:"object_type" kind:eq value:"sa" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | extend |
SrcIpAddr | extend |
Alsid Indicators of Attack
#'Searches for triggered Indicators of Attack'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 3caa67ef-8ed3-4ab5-baf2-3850d3667f3d
name: Alsid Indicators of Attack
description: |
'Searches for triggered Indicators of Attack'
severity: Low
requiredDataConnectors:
- connectorId: AlsidForAD
dataTypes:
- AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
afad_parser
| where MessageType == 2
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 2
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
AWSCloudTrail - ECR image scan findings high or critical
#Identifies Amazon ECR image scan findings that report high or critical severity vulnerabilities. These findings indicate container images that should be reviewed and remediated before deployment or continued use.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
Rule Body
id: f6928301-56da-4d2c-aabe-e1a552bc8892
name: AWSCloudTrail - ECR image scan findings high or critical
description: |
Identifies Amazon ECR image scan findings that report high or critical severity vulnerabilities. These findings indicate container images that should be reviewed and remediated before deployment or continued use.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1083
query: |
AWSCloudTrail
| where EventName == "DescribeImageScanFindings" and isempty(ErrorCode) and isempty(ErrorMessage)
| extend repoName = tostring(parse_json(ResponseElements).repositoryName)
| extend imageId = tostring(parse_json(ResponseElements).imageId.imageDigest)
| extend Critical = toint(parse_json(ResponseElements).imageScanFindings.findingSeverityCounts.CRITICAL)
| extend High = toint(parse_json(ResponseElements).imageScanFindings.findingSeverityCounts.HIGH)
| where Critical > 0 or High > 0
| 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:
Repository: repoName
ImageDigest: imageId
CriticalFindings: Critical
HighFindings: High
alertDetailsOverride:
alertDisplayNameFormat: 'AWS ECR image scan findings with high or critical Vulnerabilities in {{repoName}}'
alertDescriptionFormat: 'ECR image {{imageId}} in repository {{repoName}} returned critical and high findings.'
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName == "DescribeImageScanFindings" and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend (4 consecutive steps)
| extend repoName = tostring(parse_json(ResponseElements).repositoryName)
| extend imageId = tostring(parse_json(ResponseElements).imageId.imageDigest)
| extend Critical = toint(parse_json(ResponseElements).imageScanFindings.findingSeverityCounts.CRITICAL)
| extend High = toint(parse_json(ResponseElements).imageScanFindings.findingSeverityCounts.HIGH)
Stage 4: where
| where Critical > 0 or High > 0
Stage 5: 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 |
|---|---|---|---|
Critical | gt |
| field:"Critical" kind:gt value:"0" |
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | eq |
| field:"aws::eventName" kind:eq value:"DescribeImageScanFindings" |
High | gt |
| field:"High" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
repoName | extend |
imageId | extend |
Critical | extend |
High | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
AWSCloudTrail - Successful API executed from a Tor exit node
#Identifies successful AWS CloudTrail API activity originating from an IP address identified as a TOR exit node in the external TOR list hosted at https://firewalliplists.gypthecat.com/lists/kusto/kusto-tor-exit.csv.zip. The rule alerts only when CloudTrail indicates the request completed successfully with no ErrorCode and no ErrorMessage, and the source IP is present in the TOR exit node list.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 0adab960-5565-4978-ba6d-044553e4acc4
name: AWSCloudTrail - Successful API executed from a Tor exit node
description: |
Identifies successful AWS CloudTrail API activity originating from an IP address identified as a TOR exit node in
the external TOR list hosted at https://firewalliplists.gypthecat.com/lists/kusto/kusto-tor-exit.csv.zip. The rule alerts
only when CloudTrail indicates the request completed successfully with no ErrorCode and no ErrorMessage, and the source
IP is present in the TOR exit node list.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
let TorNodes = (
externaldata (TorIP:string)
[h@'https://firewalliplists.gypthecat.com/lists/kusto/kusto-tor-exit.csv.zip']
with (ignoreFirstRecord=true));
AWSCloudTrail
| where SourceIpAddress in (TorNodes) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| project TimeGenerated, SourceIpAddress, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName, UserIdentityPrincipalid, EventName, EventSource, AWSRegion, RecipientAccountId
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
UserIdentityArn: UserIdentityArn
EventName: EventName
EventSource: EventSource
AWSRegion: AWSRegion
alertDetailsOverride:
alertDisplayNameFormat: 'Successful AWS API call from TOR exit node: {{AccountName}} from {{SourceIpAddress}}'
alertDescriptionFormat: 'A successful AWS API call was observed from TOR exit node IP {{SourceIpAddress}} for account {{AccountName}}.'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: TorNodes
let TorNodes = (
externaldata (TorIP:string)
[h@'https://firewalliplists.gypthecat.com/lists/kusto/kusto-tor-exit.csv.zip']
with (ignoreFirstRecord=true));
Stage 1: source
AWSCloudTrail
Stage 2: where
| where SourceIpAddress in (TorNodes) and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 4: project
| project TimeGenerated, SourceIpAddress, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName, UserIdentityPrincipalid, EventName, EventSource, AWSRegion, RecipientAccountId
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 | |
SourceIpAddress | in |
| field:"aws::sourceIPAddress" kind:in value:"TorNodes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | project |
AccountName | project |
AccountUPNSuffix | project |
EventName | project |
EventSource | project |
RecipientAccountId | project |
SourceIpAddress | project |
TimeGenerated | project |
UserIdentityArn | project |
UserIdentityPrincipalid | project |
UserIdentityUserName | project |
BloodHound Attack Path Finding - Large Default Groups With Add Self Privileges
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Large Default Groups With Add Self Privileges
Rule Body
id: 6a9a4ef0-e08b-40ad-ad40-c88b6ab915ae
name: BloodHound Attack Path Finding - Large Default Groups With Add Self Privileges
description: BloodHound Attack Path Finding - Large Default Groups With Add Self Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "LargeDefaultGroupsAddSelf"
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 == "LargeDefaultGroupsAddSelf"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"LargeDefaultGroupsAddSelf" |
Cisco Cloud Security - Connection to non-corporate private network
#'IP addresses of broadband links that usually indicates users attempting to access their home network, for example for a remote session to a home computer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: c9b6d281-b96b-4763-b728-9a04b9fe1246
name: Cisco Cloud Security - Connection to non-corporate private network
description: |
'IP addresses of broadband links that usually indicates users attempting to access their home network, for example for a remote session to a home computer.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoUmbrellaDataConnector
dataTypes:
- Cisco_Umbrella_proxy_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1573
- T1041
query: |
Cisco_Umbrella
| where EventType == 'proxylogs'
| where DvcAction =~ 'Allowed'
| where UrlCategory has_any ('Dynamic and Residential', 'Personal VPN')
| project TimeGenerated, SrcIpAddr, DstIpAddr, Identities
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
kind: Scheduled
version: 1.0.3
Stages and Predicates
Stage 1: source
Cisco_Umbrella
Stage 2: where
| where EventType == 'proxylogs'
Stage 3: where
| where DvcAction =~ 'Allowed'
Stage 4: where
| where UrlCategory has_any ('Dynamic and Residential', 'Personal VPN')
Stage 5: project
| project TimeGenerated, SrcIpAddr, DstIpAddr, Identities
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"Allowed" |
EventType | eq |
| field:"EventType" kind:eq value:"proxylogs" |
UrlCategory | match |
| field:"UrlCategory" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
Identities | project |
SrcIpAddr | project |
TimeGenerated | project |
CYFIRMA - Brand Intelligence - Product/Solution High Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This alert is raised when CYFIRMA detects a critical reputation score for an IP address linked to your infrastructure. The IP has been previously associated with hacking activity and web application attacks. Denied outbound traffic to a foreign country from a known Microsoft data center IP suggests potential misuse or compromise of cloud infrastructure."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Initial Access |
Rule Body
id: 3a9a81bc-2f41-4d68-9cd1-7788326c92b1
name: CYFIRMA - Brand Intelligence - Product/Solution High Rule
description: |
"This alert is raised when CYFIRMA detects a critical reputation score for an IP address linked to your infrastructure.
The IP has been previously associated with hacking activity and web application attacks.
Denied outbound traffic to a foreign country from a known Microsoft data center IP suggests potential misuse or compromise of cloud infrastructure."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaBrandIntelligenceAlertsDC
dataTypes:
- CyfirmaBIProductSolutionAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- ResourceDevelopment
- InitialAccess
relevantTechniques:
- T1585.002
- T1583.001
- T1566
- T1583
query: |
// High severity - Product/Solution Impersonation
let timeFrame = 5m;
CyfirmaBIProductSolutionAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
SourceSype=source_type,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
SourceSype,
ProductName,
ProviderName
customDetails:
AssetType: AssetType
AssetValue: AssetValue
Impact: Impact
Recommendation: Recommendation
SourceSype: SourceSype
Description: Description
RiskScore: RiskScore
TimeGenerated: TimeGenerated
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Brand Impersonation Detected for Product/Solution - High Severity - {{AssetType}} : {{AssetValue}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaBIProductSolutionAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
SourceSype=source_type,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
SourceSype,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
SourceSype | project |
TimeGenerated | project |
UID | project |
CYFIRMA - Public Accounts Leaks Detection Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"Detects exposed public-facing account credentials as identified in CYFIRMA's threat intelligence feeds. This rule monitors for credentials leaked through third-party breaches, dark web sources, or public repositories that could impact the organization's users or systems. It captures key details such as email, username, IP address, and associated devices. These accounts may not be directly managed by the enterprise but still pose a risk of lateral access, shadow IT, or third-party exposure."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access | |
| Discovery |
Rule Body
id: 57602938-e95a-4fc3-9352-8d473ed256e1
name: CYFIRMA - Public Accounts Leaks Detection Rule
description: |
"Detects exposed public-facing account credentials as identified in CYFIRMA's threat intelligence feeds.
This rule monitors for credentials leaked through third-party breaches, dark web sources, or public repositories that could impact the organization's users or systems.
It captures key details such as email, username, IP address, and associated devices. These accounts may not be directly managed by the enterprise but still pose a risk of lateral access, shadow IT, or third-party exposure."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaCompromisedAccountsDataConnector
dataTypes:
- CyfirmaCompromisedAccounts_CL
queryFrequency: 5m
queryPeriod: 5m
suppressionDuration: 6h
suppressionEnabled: true
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- InitialAccess
- Discovery
relevantTechniques:
- T1078
- T1087
- T1552
query: |
// Public Accounts Leaks - Latest per UID
let timeFrame = 5m;
CyfirmaCompromisedAccounts_CL
| where TimeGenerated between (ago(timeFrame) .. now())
and Category has "Public Accounts Leaks"
| extend
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| summarize arg_max(TimeGenerated,
email,
user_name,
password,
url,
ip,
computer_name,
operating_system,
breach_date,
first_seen,
last_seen,
impact,
recommendations,
description,
source,
ProductName,
ProviderName
) by uid
| sort by TimeGenerated desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: user_name
- identifier: UPNSuffix
columnName: email
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: computer_name
- identifier: OSVersion
columnName: operating_system
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ip
- entityType: URL
fieldMappings:
- identifier: Url
columnName: url
customDetails:
UID: uid
TimeGenerated: TimeGenerated
BreachDate: breach_date
FirstSeen: first_seen
LastSeen: last_seen
Impact: impact
Recommendations: recommendations
Source: source
Description: description
alertDetailsOverride:
alertDisplayNameFormat: "Public Leak - {{user_name}} - {{email}}"
alertDescriptionFormat: "{{description}}"
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaCompromisedAccounts_CL
Stage 2: where
| where TimeGenerated between (ago(timeFrame) .. now())
and Category has "Public Accounts Leaks"
Stage 3: extend
| extend
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: summarize
| summarize arg_max(TimeGenerated,
email,
user_name,
password,
url,
ip,
computer_name,
operating_system,
breach_date,
first_seen,
last_seen,
impact,
recommendations,
description,
source,
ProductName,
ProviderName
) by uid
Stage 5: sort
| sort by TimeGenerated desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | match |
| field:"Category" kind:match value:"Public Accounts Leaks" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
uid | summarize |
Dataverse - Removal of blocked file extensions
#Identifies modifications to an environment's blocked file extensions and extracts the removed extension.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Evasion |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: 1b1061be-2595-4492-af6d-1c8a5fc9576d
kind: Scheduled
name: Dataverse - Removal of blocked file extensions
description: Identifies modifications to an environment's blocked file extensions
and extracts the removed extension.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1629
query: |
let query_frequency = 1h;
let default_attachments = split('ade;adp;app;asa;ashx;asmx;asp;bas;bat;cdx;cer;chm;class;cmd;com;config;cpl;crt;csh;dll;exe;fxp;hlp;hta;htr;htw;ida;idc;idq;inf;ins;isp;its;jar;js;jse;ksh;lnk;mad;maf;mag;mam;maq;mar;mas;mat;mau;mav;maw;mda;mdb;mde;mdt;mdw;mdz;msc;msh;msh1;msh1xml;msh2;msh2xml;mshxml;msi;msp;mst;ops;pcd;pif;prf;prg;printer;pst;reg;rem;scf;scr;sct;shb;shs;shtm;shtml;soap;stm;tmp;url;vb;vbe;vbs;vsmacros;vss;vst;vsw;ws;wsc;wsf;wsh', ";");
DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == "Update" and EntityName =~ 'organization'
| mv-expand Fields
| where Fields.Name == "blockedattachments"
| extend
UpdatedAttachments = split(tostring(Fields.Value), ";"),
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
| extend RemovedAttachments = set_difference(default_attachments, UpdatedAttachments)
| project
TimeGenerated,
UserId,
ClientIp,
InstanceUrl,
RemovedAttachments,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: 'Dataverse - Blocked file extension removed in {{InstanceUrl}} '
alertDescriptionFormat: '{{UserId}} modified environment blocked extensions list.
{{UserId}} removed the following extensions {{RemovedAttachments}}.'
version: 3.2.0
Stages and Predicates
Parameters
let query_frequency = 1h;
Let Binding: default_attachments
let default_attachments = split('ade;adp;app;asa;ashx;asmx;asp;bas;bat;cdx;cer;chm;class;cmd;com;config;cpl;crt;csh;dll;exe;fxp;hlp;hta;htr;htw;ida;idc;idq;inf;ins;isp;its;jar;js;jse;ksh;lnk;mad;maf;mag;mam;maq;mar;mas;mat;mau;mav;maw;mda;mdb;mde;mdt;mdw;mdz;msc;msh;msh1;msh1xml;msh2;msh2xml;mshxml;msi;msp;mst;ops;pcd;pif;prf;prg;printer;pst;reg;rem;scf;scr;sct;shb;shs;shtm;shtml;soap;stm;tmp;url;vb;vbe;vbs;vsmacros;vss;vst;vsw;ws;wsc;wsf;wsh', ";");
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where Message == "Update" and EntityName =~ 'organization'
Stage 4: mv-expand
| mv-expand Fields
Stage 5: where
| where Fields.Name == "blockedattachments"
Stage 6: extend
| extend
UpdatedAttachments = split(tostring(Fields.Value), ";"),
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
Stage 7: extend
| extend RemovedAttachments = set_difference(default_attachments, UpdatedAttachments)
Stage 8: project
| project
TimeGenerated,
UserId,
ClientIp,
InstanceUrl,
RemovedAttachments,
CloudAppId,
AccountName,
UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EntityName | eq |
| field:"EntityName" kind:eq value:"organization" |
Fields.Name | eq |
| field:"Fields.Name" kind:eq value:"blockedattachments" |
Message | eq |
| field:"Message" kind:eq value:"Update" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ClientIp | project |
CloudAppId | project |
InstanceUrl | project |
RemovedAttachments | project |
TimeGenerated | project |
UPNSuffix | project |
UserId | project |
Digital Guardian - Exfiltration to external domain
#'Detects exfiltration to external domain.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: a19885c8-1e44-47e3-81df-d1d109f5c92d
name: Digital Guardian - Exfiltration to external domain
description: |
'Detects exfiltration to external domain.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
query: |
let corp_domain = dynamic(['example.com']); //add all corporate domains to this list
DigitalGuardianDLPEvent
| where NetworkApplicationProtocol =~ 'SMTP'
| where isnotempty(inspected_document)
| extend s_domain = extract(@'@(.*)', 1, SrcUserName)
| extend d_domain = extract(@'@(.*)', 1, DstUserName)
| where s_domain in~ (corp_domain)
| where d_domain !in (corp_domain)
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let corp_domain = dynamic(['example.com']);
Stage 1: source
DigitalGuardianDLPEvent
Stage 2: where
| where NetworkApplicationProtocol =~ 'SMTP'
Stage 3: where
| where isnotempty(inspected_document)
Stage 4: extend
| extend s_domain = extract(@'@(.*)', 1, SrcUserName)
Stage 5: extend
| extend d_domain = extract(@'@(.*)', 1, DstUserName)
Stage 6: where
| where s_domain in~ (corp_domain)
Stage 7: where
| where d_domain !in (corp_domain)
Stage 8: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
NetworkApplicationProtocol | eq |
| field:"NetworkApplicationProtocol" kind:eq value:"SMTP" |
inspected_document | is_not_null | field:"inspected_document" kind:is_not_null | |
s_domain | in |
| field:"s_domain" kind:in value:"corp_domain" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
d_domain | eq | corp_domain | excludes:d_domain field:"d_domain" value:"corp_domain" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
s_domain | extend |
d_domain | extend |
AccountCustomEntity | extend |
Exchange OAB Virtual Directory Attribute Containing Potential Webshell
#This query uses Windows Event ID 5136 in order to detect potential webshell deployment by exploitation of CVE-2021-27065. This query looks for changes to the InternalHostName or ExternalHostName properties of Exchange OAB Virtual Directory objects in AD Directory Services where the new objects contain potential webshell objects.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: faf1a6ff-53b5-4f92-8c55-4b20e9957594
name: Exchange OAB Virtual Directory Attribute Containing Potential Webshell
description: |
'This query uses Windows Event ID 5136 in order to detect potential webshell deployment by exploitation of CVE-2021-27065.
This query looks for changes to the InternalHostName or ExternalHostName properties of Exchange OAB Virtual Directory objects in AD Directory Services where the new objects contain potential webshell objects.'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
SecurityEvent
// Look for specific Directory Service Changes and parse data
| where EventID == 5136
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion = array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value),TimeGenerated, EventID, Computer, Account, AccountType, EventSourceName, Activity, SubjectAccount)
// Where changes relate to Exchange OAB
| extend ObjectClass = column_ifexists("ObjectClass", "")
| where ObjectClass =~ "msExchOABVirtualDirectory"
// Look for InternalHostName or ExternalHostName properties being changed
| extend AttributeLDAPDisplayName = column_ifexists("AttributeLDAPDisplayName", "")
| where AttributeLDAPDisplayName in~ ("msExchExternalHostName", "msExchInternalHostName")
// Look for suspected webshell activity
| extend AttributeValue = column_ifexists("AttributeValue", "")
| where AttributeValue has "script"
| project-rename LastSeen = TimeGenerated
| extend ObjectDN = column_ifexists("ObjectDN", "")
| project-reorder LastSeen, Computer, Account, ObjectDN, AttributeLDAPDisplayName, AttributeValue
| extend timestamp = LastSeen
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
Stage 3: extend
| extend EventData = parse_xml(EventData).EventData.Data
Stage 4: mv-expand
| mv-expand bagexpansion = array EventData
Stage 5: evaluate
| evaluate bag_unpack(EventData)
Stage 6: extend
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
Stage 7: evaluate
| evaluate pivot(Key, any(Value),TimeGenerated, EventID, Computer, Account, AccountType, EventSourceName, Activity, SubjectAccount)
Stage 8: extend
| extend ObjectClass = column_ifexists("ObjectClass", "")
Stage 9: where
| where ObjectClass =~ "msExchOABVirtualDirectory"
Stage 10: extend
| extend AttributeLDAPDisplayName = column_ifexists("AttributeLDAPDisplayName", "")
Stage 11: where
| where AttributeLDAPDisplayName in~ ("msExchExternalHostName", "msExchInternalHostName")
Stage 12: extend
| extend AttributeValue = column_ifexists("AttributeValue", "")
Stage 13: where
| where AttributeValue has "script"
Stage 14: project-rename
| project-rename LastSeen = TimeGenerated
Stage 15: extend
| extend ObjectDN = column_ifexists("ObjectDN", "")
Stage 16: project-reorder
| project-reorder LastSeen, Computer, Account, ObjectDN, AttributeLDAPDisplayName, AttributeValue
Stage 17: extend (4 consecutive steps)
| extend timestamp = LastSeen
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AttributeLDAPDisplayName | in |
| field:"AttributeLDAPDisplayName" kind:in |
AttributeValue | match |
| field:"AttributeValue" kind:match value:"script" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
ObjectClass | eq |
| field:"ObjectClass" kind:eq value:"msExchOABVirtualDirectory" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventData | extend |
Key | extend |
Value | extend |
ObjectClass | extend |
AttributeLDAPDisplayName | extend |
AttributeValue | extend |
LastSeen | project-rename |
ObjectDN | extend |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
iboss - Malware Detected
#Identifies web traffic where the iboss platform flagged malware. A populated MalwareDetected flag indicates the gateway detected a malicious file or payload associated with the request. Surfaces the destination, user, and other details to support triage.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: cad35734-b97a-4209-9269-b98c916379eb
name: iboss - Malware Detected
description: |
Identifies web traffic where the iboss platform flagged malware. A populated MalwareDetected flag indicates the gateway detected a malicious file or payload associated with the request. Surfaces the destination, user, and other details to support triage.
severity: High
status: Available
requiredDataConnectors:
- connectorId: ibossAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
ibossUrlEvent
| where MalwareDetected == 1
| extend FileHashAlgorithm = "SHA256"
| project EventTime, SrcUsername, Url, Domain, SrcIpAddr, DstIpAddr, FileSHA256, FileHashAlgorithm,DvcAction, UrlCategory, HttpUserAgent
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: FileHashAlgorithm
- identifier: Value
columnName: FileSHA256
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
ibossUrlEvent
Stage 2: where
| where MalwareDetected == 1
Stage 3: extend
| extend FileHashAlgorithm = "SHA256"
Stage 4: project
| project EventTime, SrcUsername, Url, Domain, SrcIpAddr, DstIpAddr, FileSHA256, FileHashAlgorithm,DvcAction, UrlCategory, HttpUserAgent
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MalwareDetected | eq |
| field:"MalwareDetected" kind:eq value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Domain | project |
DstIpAddr | project |
DvcAction | project |
EventTime | project |
FileHashAlgorithm | project |
FileSHA256 | project |
HttpUserAgent | project |
SrcIpAddr | project |
SrcUsername | project |
Url | project |
UrlCategory | project |
Mass Cloud resource deletions Time Series Anomaly
#'This query generates the baseline pattern of cloud resource deletions by an individual and generates an anomaly when any unusual spike is detected. These anomalies from unusual or privileged users could be an indication of a cloud infrastructure takedown by an adversary.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: ed43bdb7-eaab-4ea4-be52-6951fcfa7e3b
name: Mass Cloud resource deletions Time Series Anomaly
description: |
'This query generates the baseline pattern of cloud resource deletions by an individual and generates an anomaly when any unusual spike is detected. These anomalies from unusual or privileged users could be an indication of a cloud infrastructure takedown by an adversary.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
tags:
- DEV-0537
query: |
let starttime = 14d;
let endtime = 1d;
let timeframe = 1d;
let TotalEventsThreshold = 25;
let TimeSeriesData = AzureActivity
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where OperationNameValue endswith "delete"
| project TimeGenerated, Caller
| make-series Total = count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by Caller;
TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(Total, 3, -1, 'linefit')
| mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
| where TimeGenerated >= startofday(ago(endtime))
| where anomalies > 0
| project Caller, TimeGenerated, Total, baseline, anomalies, score
| where Total > TotalEventsThreshold and baseline > 0
| join (AzureActivity
| where TimeGenerated > startofday(ago(endtime))
| where OperationNameValue endswith "delete"
| summarize count(), make_set(OperationNameValue,100), make_set(_ResourceId,100) by bin(TimeGenerated, timeframe), Caller ) on TimeGenerated, Caller
| extend Name = iif(Caller has '@',tostring(split(Caller,'@',0)[0]),"")
| extend UPNSuffix = iif(Caller has '@',tostring(split(Caller,'@',1)[0]),"")
| extend AadUserId = iif(Caller !has '@',Caller,"")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: AadUserId
version: 2.0.4
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
let timeframe = 1d;
let TotalEventsThreshold = 25;
let TimeSeriesData is inlined into the numbered stages below.
Stages 1 to 5 define let TimeSeriesData as the rule's main pipeline source. Stages 6 to 13 use it.
Stage 1: source
AzureActivity
Stage 2: where
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
Stage 3: where
| where OperationNameValue endswith "delete"
Stage 4: project
| project TimeGenerated, Caller
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 5: make-series
| make-series Total = count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by Caller
Stage 6: extend
TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(Total, 3, -1, 'linefit')
Stage 7: mv-expand
| mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
Stage 8: where
| where TimeGenerated >= startofday(ago(endtime))
Stage 9: where
| where anomalies > 0
Stage 10: project
| project Caller, TimeGenerated, Total, baseline, anomalies, score
Stage 11: where
| where Total > TotalEventsThreshold and baseline > 0
Stage 12: join
| join (AzureActivity
| where TimeGenerated > startofday(ago(endtime))
| where OperationNameValue endswith "delete"
| summarize count(), make_set(OperationNameValue,100), make_set(_ResourceId,100) by bin(TimeGenerated, timeframe), Caller ) on TimeGenerated, Caller
Stage 13: extend (3 consecutive steps)
extend AadUserId, Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationNameValue | ends_with |
| field:"azure_ad::operation_name_value" kind:ends_with |
Total | gt |
| field:"Total" kind:gt value:"25" |
anomalies | gt |
| field:"anomalies" kind:gt value:"0" |
baseline | gt |
| field:"baseline" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | project |
TimeGenerated | project |
Total | project |
anomalies | project |
baseline | project |
score | project |
Name | extend |
UPNSuffix | extend |
AadUserId | extend |
Modified domain federation trust settings
#This will alert when a user or application modifies the federation settings on the domain or Update domain authentication from Managed to Federated. For example, this alert will trigger when a new Active Directory Federated Service (ADFS) TrustedRealm object, such as a signing certificate, is added to the domain. Modification to domain federation settings should be rare. Confirm the added or modified target domain/URL is legitimate administrator behavior. To understand why an authorized user may update settings for a federated domain in Office 365, Azure, or Intune, see: https://docs.microsoft.com/office365/troubleshoot/active-directory/update-federated-domain-office-365. For details on security realms that accept security tokens, see the ADFS Proxy Protocol (MS-ADFSPP) specification: https://docs.microsoft.com/openspecs/windows_protocols/ms-adfspp/e7b9ea73-1980-4318-96a6-da559486664b. For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Set domain authentication |
| Azure | Set federation settings on domain |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Domain Federation Trust Settings Modified (Kusto)
- Azure AD New Custom Domain Added (Splunk)
- Azure AD New Federated Domain Added (Splunk)
- Azure Domain Federation Settings Modified (Sigma)
- Entra ID Custom Domain Added or Verified (Elastic)
- Entra ID Domain Federation Configuration Change (Elastic)
- New onmicrosoft domain added to tenant (Kusto)
- New Root Certificate Authority Added (Sigma)
Rule Body
id: 95dc4ae3-e0f2-48bd-b996-cdd22b90f9af
name: Modified domain federation trust settings
description: |
'This will alert when a user or application modifies the federation settings on the domain or Update domain authentication from Managed to Federated.
For example, this alert will trigger when a new Active Directory Federated Service (ADFS) TrustedRealm object, such as a signing certificate, is added to the domain.
Modification to domain federation settings should be rare. Confirm the added or modified target domain/URL is legitimate administrator behavior.
To understand why an authorized user may update settings for a federated domain in Office 365, Azure, or Intune, see: https://docs.microsoft.com/office365/troubleshoot/active-directory/update-federated-domain-office-365.
For details on security realms that accept security tokens, see the ADFS Proxy Protocol (MS-ADFSPP) specification: https://docs.microsoft.com/openspecs/windows_protocols/ms-adfspp/e7b9ea73-1980-4318-96a6-da559486664b.
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1555
- T1098
query: |
(union isfuzzy=true
(
AuditLogs
| where OperationName =~ "Set federation settings on domain"
//| where Result =~ "success" // commenting out, as it may be interesting to capture failed attempts
| mv-expand TargetResources
| extend modifiedProperties = parse_json(TargetResources).modifiedProperties
| mv-expand modifiedProperties
| extend targetDisplayName = tostring(parse_json(modifiedProperties).displayName)
),
(
AuditLogs
| where OperationName =~ "Set domain authentication"
//| where Result =~ "success" // commenting out, as it may be interesting to capture failed attempts
| mv-expand TargetResources
| extend modifiedProperties = parse_json(TargetResources).modifiedProperties
| mv-expand modifiedProperties
| mv-apply Property = modifiedProperties on
(
where Property.displayName =~ "LiveType"
| extend targetDisplayName = tostring(Property.displayName),
NewDomainValue = tostring(Property.newValue)
)
| where NewDomainValue has "Federated"
)
)
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| project-reorder TimeGenerated, OperationName, InitiatingUserOrApp, AADOperationType, targetDisplayName, Result, InitiatingIpAddress, UserAgent, CorrelationId, TenantId, AADTenantId
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.1.2
kind: Scheduled
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: AuditLogs, AuditLogs
Leg 1: AuditLogs
AuditLogs
| where OperationName =~ "Set federation settings on domain"
| mv-expand TargetResources
| extend modifiedProperties = parse_json(TargetResources).modifiedProperties
| mv-expand modifiedProperties
| extend targetDisplayName = tostring(parse_json(modifiedProperties).displayName)
Leg 2: AuditLogs
AuditLogs
| where OperationName =~ "Set domain authentication"
| mv-expand TargetResources
| extend modifiedProperties = parse_json(TargetResources).modifiedProperties
| mv-expand modifiedProperties
| mv-apply Property = modifiedProperties on
(
where Property.displayName =~ "LiveType"
| extend targetDisplayName = tostring(Property.displayName),
NewDomainValue = tostring(Property.newValue)
)
| where NewDomainValue has "Federated"
Applied to the Combined Result
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| project-reorder TimeGenerated, OperationName, InitiatingUserOrApp, AADOperationType, targetDisplayName, Result, InitiatingIpAddress, UserAgent, CorrelationId, TenantId, AADTenantId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalDetail.key | eq |
| field:"AdditionalDetail.key" kind:eq value:"User-Agent" |
NewDomainValue | match |
| field:"NewDomainValue" kind:match value:"Federated" |
OperationName | eq |
| field:"OperationName" kind:eq |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"LiveType" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
modifiedProperties | extend |
targetDisplayName | extend |
InitiatingUserOrApp | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
NGINX - Multiple client errors from single IP address
#'Detects multiple client errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 42771afe-edb3-4330-bc4a-abf6a5714454
name: NGINX - Multiple client errors from single IP address
description: |
'Detects multiple client errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- NGINX_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let threshold = 100;
NGINXHTTPServer
| where tolong(HttpStatusCode) >= 400 and tolong(HttpStatusCode) <= 499
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleClientErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
NGINXHTTPServer
Stage 2: where
| where tolong(HttpStatusCode) >= 400 and tolong(HttpStatusCode) <= 499
Stage 3: summarize
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleClientErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleClientErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
NRT DNS events related to mining pools
#'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: d5b32cd4-2328-43da-ab47-cd289c1f5efc
name: NRT DNS events related to mining pools
description: |
'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: DNS
dataTypes:
- DnsEvents
tactics:
- Impact
relevantTechniques:
- T1496
query: |
DnsEvents
| where Name contains "."
| where Name has_any ("monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com",
"xmrget.com", "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club",
"supportxmr.com", "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com",
"gntl.co.uk", "semipool.com", "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io",
"coinpoolit.webhop.me", "nanopool.org", "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org",
"extrmepool.org", "webcoin.me", "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com",
"dwarfpool.com", "hash-to-coins.com", "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream",
"moneropool.ru", "cryptonotepool.org.uk", "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net",
"backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net", "shscrypto.net")
| extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
| extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.2
kind: NRT
Stages and Predicates
Stage 1: source
DnsEvents
Stage 2: where
| where Name contains "."
Stage 3: where
| where Name has_any ("monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com",
"xmrget.com", "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club",
"supportxmr.com", "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com",
"gntl.co.uk", "semipool.com", "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io",
"coinpoolit.webhop.me", "nanopool.org", "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org",
"extrmepool.org", "webcoin.me", "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com",
"dwarfpool.com", "hash-to-coins.com", "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream",
"moneropool.ru", "cryptonotepool.org.uk", "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net",
"backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net", "shscrypto.net")
Stage 4: extend
| extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
HostName =Computer has "."substring(Computer, 0, indexof(Computer, '.'))ComputerStage 5: extend
| extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
DnsDomain =Computer has "."substring(Computer, (indexof(Computer, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Name | contains |
| field:"Name" kind:contains value:"." |
Name | match |
| field:"Name" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
DnsDomain | extend |
Outgoing connection attempts stateful anomaly on database
#'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to access external sites or resources (e.g. for downloading malicious content).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rules Detecting the Same Action
These rules filter on the same operation.
- Affected rows stateful anomaly on database (Kusto)
- Credential errors stateful anomaly on database (Kusto)
- Drop attempts stateful anomaly on database (Kusto)
- Execution attempts stateful anomaly on database (Kusto)
- Firewall errors stateful anomaly on database (Kusto)
- Firewall rule manipulation attempts stateful anomaly on database (Kusto)
- OLE object manipulation attempts stateful anomaly on database (Kusto)
- Response rows stateful anomaly on database (Kusto)
Rule Body
id: c105513d-e398-4a02-bd91-54b9b2d6fa7d
name: Outgoing connection attempts stateful anomaly on database
description: |
'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to access external sites or resources (e.g. for downloading malicious content).'
severity: Medium
requiredDataConnectors:
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CommandAndControl
relevantTechniques:
- T1105
- T1071
tags:
- SQL
query: |
let monitoredStatementsThreshold = 1; // Minimal number of monitored statements in the slice to trigger an anomaly.
let trainingSlicesThreshold = 5; // The maximal amount of slices with monitored statements in the training window before anomaly detection is throttled.
let timeSliceSize = 1h; // The size of the single timeSlice for individual aggregation.
let detectionWindow = 1h; // The size of the recent detection window for detecting anomalies.
let trainingWindow = detectionWindow + 14d; // The size of the training window before the detection window for learning the normal state.
let hotwords = pack_array('http:', 'https:', 'ftp:', 'onion.pet'); // List of monitored hot words.
let processedData = materialize (
AzureDiagnostics
| where TimeGenerated >= ago(trainingWindow)
| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM") // Keep only SQL affected rows
| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
ResponseRows = response_rows_d, Statement = statement_s,
Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
, 0),
State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
, 0),
AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
| extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
| summarize countEvents = count(), countStatements = dcount(Statement)
, countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
, countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
, countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
, anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
, anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
, anyInfo = anyif(AdditionalInfo, hasHotword == 1)
, hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId
| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
| where WindowType in ('detection', 'training'));
let trainingSet =
processedData
| where WindowType == 'training'
| summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
by Database;
processedData
| where WindowType == 'detection'
| join kind = inner (trainingSet) on Database
| extend IsHotwordAnomalyOnStatement = iff(((countStatementsWithHotwords >= monitoredStatementsThreshold) and (countSlicesWithHotwords <= trainingSlicesThreshold)), true, false)
, anomalyScore = round(countStatementsWithHotwords/monitoredStatementsThreshold, 0)
| where IsHotwordAnomalyOnStatement == 'true'
| project TimeGenerated = timeSlice, Database, ClientIp, ApplicationName, PrincipalName, HostName, ResourceId, countEvents, countStatements, countStatementsWithHotwords, anyMonitoredStatement, anyInfo, anomalyScore, hotWord
| extend Name = tostring(split(PrincipalName,'@',0)[0]), UPNSuffix = tostring(split(PrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ApplicationName
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: ResourceId
customDetails:
HotWords: hotWord
alertDetailsOverride:
alertDisplayNameFormat: 'Outgoing connection attempts stateful anomaly on database {{Database}}'
alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithHotwords}} statements with monitored hot words (http:, https:, ftp:, onion.pet etc.) in the last hour. Investigate the database activity for potential malicious attempts to access external sites or resources.'
version: 1.1.2
kind: Scheduled
Stages and Predicates
Parameters
let monitoredStatementsThreshold = 1;
let trainingSlicesThreshold = 5;
let timeSliceSize = 1h;
let detectionWindow = 1h;
let trainingWindow = detectionWindow + 14d;
let hotwords = pack_array('http:', 'https:', 'ftp:', 'onion.pet');
let processedData is inlined into the numbered stages below.
Let Binding: trainingSet
let trainingSet = processedData
| where WindowType == 'training'
| summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
by Database;
Stages 1 to 8 define let processedData as the rule's main pipeline source. Stages 9 to 14 use it.
Stage 1: source
AzureDiagnostics
Stage 2: where
| where TimeGenerated >= ago(trainingWindow)
Stage 3: where
| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM")
Stage 4: project
| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
ResponseRows = response_rows_d, Statement = statement_s,
Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
, 0),
State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
, 0),
AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
Stage 5: extend
| extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
hasHotword =Statement has "hotwords"10Stage 6: summarize
| summarize countEvents = count(), countStatements = dcount(Statement)
, countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
, countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
, countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
, anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
, anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
, anyInfo = anyif(AdditionalInfo, hasHotword == 1)
, hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId
Stage 7: extend
| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
WindowType =/* macro: (timeSlice >= ago(detectionWindow)) */'detection'/* macro: (ago(trainingWindow) <= timeSlice) */'training''other'Stage 8: where
| where WindowType in ('detection', 'training')
Stage 9: where
processedData
| where WindowType == 'detection'
Stage 10: join
| join kind = inner (trainingSet) on Database
Stage 11: extend
extend IsHotwordAnomalyOnStatement, anomalyScore
IsHotwordAnomalyOnStatement =countStatementsWithHotwords >= monitoredStatementsThreshold and countSlicesWithHotwords <= trainingSlicesThresholdtruefalseStage 12: where
where IsHotwordAnomalyOnStatement == true
Stage 13: project
project ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, TimeGenerated, anomalyScore, anyInfo, anyMonitoredStatement, countEvents, countStatements, countStatementsWithHotwords, hotWord
Stage 14: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"SQLSecurityAuditEvents" |
IsHotwordAnomalyOnStatement | eq |
| field:"IsHotwordAnomalyOnStatement" kind:eq value:"true" |
WindowType | eq |
| field:"WindowType" kind:eq |
WindowType | in |
| field:"WindowType" kind:in |
action_id_s | match |
| field:"action_id_s" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationName | project |
ClientIp | project |
Database | project |
HostName | project |
PrincipalName | project |
ResourceId | project |
TimeGenerated | project |
anomalyScore | project |
anyInfo | project |
anyMonitoredStatement | project |
countEvents | project |
countStatements | project |
countStatementsWithHotwords | project |
hotWord | project |
Name | extend |
UPNSuffix | extend |
Pathlock TDnR - Debitor Change Documents
#Detects changes to debitor (customer) master records in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized modifications may indicate fraudulent manipulation of customer accounts or receivables data.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c08
name: Pathlock TDnR - Debitor Change Documents
kind: Scheduled
description: >-
Detects changes to debitor (customer) master records in SAP, forwarded by Pathlock Threat Detection
and Response. Unauthorized modifications may indicate fraudulent manipulation of customer
accounts or receivables data.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1565
query: |
Pathlock_TDnR_CL
| where DataSource == "CHANGEDOC_DEBI"
| 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_DEBI"
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_DEBI" |
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 |
Pathlock TDnR - SAP HANA Database Audit Trail
#Detects security events from the SAP HANA tenant database audit trail, forwarded by Pathlock Threat Detection and Response. HANA audit anomalies may indicate unauthorized database access, privilege abuse, or attempts to read sensitive data directly from the HANA database.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Discovery |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c27
name: Pathlock TDnR - SAP HANA Database Audit Trail
kind: Scheduled
description: >-
Detects security events from the SAP HANA tenant database audit trail, forwarded by Pathlock Threat
Detection and Response. HANA audit anomalies may indicate unauthorized database access, privilege
abuse, or attempts to read sensitive data directly from the HANA database.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1082
- T1078
query: |
Pathlock_TDnR_CL
| where DataSource == "HANA_AUDIT_TRAIL"
| 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 == "HANA_AUDIT_TRAIL"
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:"HANA_AUDIT_TRAIL" |
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 |
Privileged User Logon from new ASN
#Detects a successful logon by a privileged account from an ASN not logged in from in the last 14 days. Monitor these logons to ensure they are legitimate and identify if there are any similar sign ins.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 55073036-bb86-47d3-a85a-b113ac3d9396
name: Privileged User Logon from new ASN
description: |
'Detects a successful logon by a privileged account from an ASN not logged in from in the last 14 days.
Monitor these logons to ensure they are legitimate and identify if there are any similar sign ins.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: BehaviorAnalytics
dataTypes:
- BehaviorAnalytics
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let admins=(IdentityInfo
| where AssignedRoles contains "admin" or GroupMembership has "Admin"
| summarize by tolower(AccountUPN));
let known_asns = (
SigninLogs
| where TimeGenerated between(ago(14d)..ago(1d))
| where ResultType == 0
| summarize by AutonomousSystemNumber);
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == 0
| where tolower(UserPrincipalName) in (admins)
| where AutonomousSystemNumber !in (known_asns)
| project-reorder TimeGenerated, UserPrincipalName, UserAgent, IPAddress, AutonomousSystemNumber
| extend AccountName = tostring(split(UserPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.6
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Identity", "Security - Others" ]
Stages and Predicates
Let Binding: admins
let admins = (IdentityInfo
| where AssignedRoles contains "admin" or GroupMembership has "Admin"
| summarize by tolower(AccountUPN));
Let Binding: known_asns
let known_asns = (
SigninLogs
| where TimeGenerated between(ago(14d)..ago(1d))
| where ResultType == 0
| summarize by AutonomousSystemNumber);
Stage 1: source
let admins
Stage 2: source
SigninLogs
Stage 3: where
where TimeGenerated > ago(86400s)
Stage 4: where
where ResultType == 0
Stage 5: where
where UserPrincipalName == "admins"
Stage 6: where
where not (AutonomousSystemNumber == "known_asns")
Stage 7: project-reorder
project-reorder
Stage 8: extend
extend AccountName, AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
UserPrincipalName | in |
| field:"UserPrincipalName" kind:in value:"admins" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AutonomousSystemNumber | eq | known_asns | excludes:AutonomousSystemNumber field:"AutonomousSystemNumber" value:"known_asns" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | extend |
AccountUPNSuffix | extend |
Radiflow - Network Scanning Detected
#'Generates an incident when a network scan is detected either by Radiflow's iSID.'
MITRE ATT&CK Coverage
Rule Body
id: cde00cc5-5841-4aa9-96c5-dd836f9e3f26
name: Radiflow - Network Scanning Detected
description: |
'Generates an incident when a network scan is detected either by Radiflow's iSID.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: RadiflowIsid
dataTypes:
- RadiflowEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T0840
- T0846
- T0888
query: |
RadiflowEvent
| where DeviceProduct =~ 'iSID'
| where
(
EventClassID in (52, 105, 150, 151, 152, 153, 158, 166, 175)
or EventMessage has 'Scan'
)
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Network Scanning Detected: {{EventMessage}}'
alertDescriptionFormat: "A suspicious network scan activity has been detected in the network. Please check the the alert details for further information.\n\nMessage: {{EventMessage}}\nSource device: {{SourceIP}}\nDestination device (if any): {{DestinationIP}} "
alertSeverityColumnName: EventSeverity
alertDynamicProperties: []
suppressionEnabled: false
suppressionDuration: 5h
customDetails:
SourceMAC: SourceMACAddress
SourceType: SourceType
SourceVendor: SourceVendor
SourceVLAN: SourceVLAN
DestinationMAC: DestinationMACAddress
DestinationType: DestinationType
DestinationVendor: DestinationVendor
Port: Port
Protocol: Protocol
SourceHostName: SourceHostName
SourceIP: SourceIP
DestinationHostName: DestinationHostName
DestinationIP: DestinationIP
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHostName
- identifier: NetBiosName
columnName: SourceHostName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DestinationHostName
- identifier: NetBiosName
columnName: DestinationHostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
RadiflowEvent
Stage 2: where
| where DeviceProduct =~ 'iSID'
Stage 3: where
| where
(
EventClassID in (52, 105, 150, 151, 152, 153, 158, 166, 175)
or EventMessage has 'Scan'
)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"iSID" |
EventClassID | in |
| field:"EventClassID" kind:in |
EventMessage | match |
| field:"EventMessage" kind:match value:"Scan" |
Snowflake - Possible discovery activity
#'Detects possible discovery activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 09b8dfc7-87b0-4215-b34b-bab363d685cb
name: Snowflake - Possible discovery activity
description: |
'Detects possible discovery activity.'
severity: Medium
status : Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1526
query: |
let threshold = 20;
Snowflake
| where QueryType =~ 'SHOW'
| where QueryExecutionStatus =~ 'SUCCESS'
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
| where count_ > threshold
| extend AccountCustomEntity = TargetUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 20;
Stage 1: source
Snowflake
Stage 2: where
| where QueryType =~ 'SHOW'
Stage 3: where
| where QueryExecutionStatus =~ 'SUCCESS'
Stage 4: summarize
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
Stage 5: where
| where count_ > threshold
Stage 6: extend
| extend AccountCustomEntity = TargetUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryExecutionStatus | eq |
| field:"QueryExecutionStatus" kind:eq value:"SUCCESS" |
QueryType | eq |
| field:"QueryType" kind:eq value:"SHOW" |
count_ | gt |
| field:"count_" kind:gt value:"20" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetUsername | summarize |
AccountCustomEntity | extend |
Tailscale Premium: Network flow beaconing detected
#Identifies when flows between a src-dst pair recur at a regular interval (80%+ of inter-flow gaps cluster on the same delta over 10+ flows). Signature of C2 beaconing or scheduled exfiltration. Requires Tailscale Premium or Enterprise.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: e3f4a5b6-3c4d-5e6f-7a8b-9c0d1e2f3a4b
name: "Tailscale Premium: Network flow beaconing detected"
description: |
Identifies when flows between a src-dst pair recur at a regular interval (80%+ of inter-flow gaps cluster on the same delta over 10+ flows). Signature of C2 beaconing or scheduled exfiltration. Requires Tailscale Premium or Enterprise.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: TailscalePremiumCCF
dataTypes:
- Tailscale_Network_CL
queryFrequency: 1h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1071
- T1095
- T1029
query: |
let lookback = 2d;
let minFlows = 10;
let beaconPercentThreshold = 80.0;
Tailscale_Network_CL
| where TimeGenerated > ago(lookback)
| where HasVirtualTraffic
| mv-expand t = VirtualTraffic
| extend Src = tostring(t.src), Dst = tostring(t.dst), Proto = toint(t.proto)
| project TimeGenerated, Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
| sort by Src asc, Dst asc, Proto asc, TimeGenerated asc
| serialize
| extend NextTime = next(TimeGenerated), NextSrc = next(Src), NextDst = next(Dst), NextProto = next(Proto)
| where Src == NextSrc and Dst == NextDst and Proto == NextProto
| extend DeltaSec = datetime_diff('second', NextTime, TimeGenerated)
| where DeltaSec > 5
| summarize DeltaCount = count() by Src, Dst, Proto, DeltaSec, SrcNodeName, SrcUser, DstNodeName, DstUser
| summarize (MostFrequentDeltaCount, MostFrequentDeltaSec) = arg_max(DeltaCount, DeltaSec), TotalFlows = sum(DeltaCount) by Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
| where TotalFlows >= minFlows
| extend BeaconPercent = round(MostFrequentDeltaCount * 100.0 / TotalFlows, 1)
| where BeaconPercent >= beaconPercentThreshold
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcNodeName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DstNodeName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUser
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Src
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Parameters
let lookback = 2d;
let minFlows = 10;
let beaconPercentThreshold = 80.0;
Stage 1: source
Tailscale_Network_CL
Stage 2: where
| where TimeGenerated > ago(lookback)
Stage 3: where
| where HasVirtualTraffic
Stage 4: mv-expand
| mv-expand t = VirtualTraffic
Stage 5: extend
| extend Src = tostring(t.src), Dst = tostring(t.dst), Proto = toint(t.proto)
Stage 6: project
| project TimeGenerated, Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
Stage 7: sort
| sort by Src asc, Dst asc, Proto asc, TimeGenerated asc
Stage 8: kusto:serialize
| serialize
Stage 9: extend
| extend NextTime = next(TimeGenerated), NextSrc = next(Src), NextDst = next(Dst), NextProto = next(Proto)
Stage 10: where
| where Src == NextSrc and Dst == NextDst and Proto == NextProto
Stage 11: extend
| extend DeltaSec = datetime_diff('second', NextTime, TimeGenerated)
Stage 12: where
| where DeltaSec > 5
Stage 13: summarize
| summarize DeltaCount = count() by Src, Dst, Proto, DeltaSec, SrcNodeName, SrcUser, DstNodeName, DstUser
Stage 14: summarize
| summarize (MostFrequentDeltaCount, MostFrequentDeltaSec) = arg_max(DeltaCount, DeltaSec), TotalFlows = sum(DeltaCount) by Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
Stage 15: where
| where TotalFlows >= minFlows
Stage 16: extend
| extend BeaconPercent = round(MostFrequentDeltaCount * 100.0 / TotalFlows, 1)
Stage 17: where
| where BeaconPercent >= beaconPercentThreshold
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Dst | summarize |
DstNodeName | summarize |
DstUser | summarize |
MostFrequentDeltaCount | summarize |
MostFrequentDeltaSec | summarize |
Proto | summarize |
Src | summarize |
SrcNodeName | summarize |
SrcUser | summarize |
TotalFlows | summarize |
BeaconPercent | extend |
Theom Low Risks
#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.
"Creates Microsoft Sentinel incidents for low risk Theom alerts"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Stealth | |
| Credential Access | |
| Discovery | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: cf7fb616-ac80-40ce-ad18-aa18912811f8
name: Theom Low Risks
description: |
"Creates Microsoft Sentinel incidents for low risk Theom alerts"
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- CommandAndControl
- CredentialAccess
- DefenseEvasion
- Discovery
- Exfiltration
- Impact
- Reconnaissance
relevantTechniques:
- T1592
- T1589
- T1070
- T1552
- T1619
- T1119
- T1560
- T1530
- T1213
- T1001
- T1041
- T1537
- T1485
- T1486
- T1565
query: |
TheomAlerts_CL
| where priority_s == "P4"
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
alertDescriptionFormat: "\nSummary: {{summary_s}} \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: customProps_AssetName_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: deepLink_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
TheomAlerts_CL
Stage 2: where
| where priority_s == "P4"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
priority_s | eq |
| field:"priority_s" kind:eq value:"P4" |
TI map IP entity to GitHub_CL
#'Identifies a match in GitHub_CL table from any IP IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: aac495a9-feb1-446d-b08e-a1164a539452
name: TI map IP entity to GitHub_CL
description: |
'Identifies a match in GitHub_CL table from any IP IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Look back 1 hour for VMConnection events
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
ThreatIntelligenceIndicator
// Picking up only IOC's that contain the entities we want
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
| where Action == true
| where TimeGenerated >= ago(ioc_lookBack)
// Taking the first non-empty value based on potential IOC match availability
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| join (
GitHubAudit
| where TimeGenerated >= ago(dt_lookBack)
| extend GitHubAudit_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.IPaddress
| project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, GitHubAudit_TimeGenerated, TI_ipEntity, IPaddress, Actor, Action, Country, OperationType, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Actor
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPaddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
Stage 3: where
| where Action == true
Stage 4: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 5: extend (3 consecutive steps)
| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =isnotempty(NetworkIP)NetworkIPNetworkDestinationIPStage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: join
| join (
GitHubAudit
| where TimeGenerated >= ago(dt_lookBack)
| extend GitHubAudit_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.IPaddress
Stage 9: project
project Action, ActivityGroupNames, Actor, ConfidenceScore, Country, Description, EmailSourceIpAddress, ExpirationDateTime, GitHubAudit_TimeGenerated, IPaddress, IndicatorId, LatestIndicatorTime, NetworkDestinationIP, NetworkIP, NetworkSourceIP, OperationType, TI_ipEntity, ThreatType, Url
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActivityGroupNames | project |
Actor | project |
ConfidenceScore | project |
Country | project |
Description | project |
EmailSourceIpAddress | project |
ExpirationDateTime | project |
GitHubAudit_TimeGenerated | project |
IPaddress | project |
IndicatorId | project |
LatestIndicatorTime | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
OperationType | project |
TI_ipEntity | project |
ThreatType | project |
Url | project |
Tomcat - Known malicious user agent
#'Detects known malicious user agents'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 5e77a818-5825-4ff6-a901-80891c4774d1
name: Tomcat - Known malicious user agent
description: |
'Detects known malicious user agents'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let malicious_ua = dynamic(['Nikto', 'hydra', '.nasl', 'absinthe', 'advanced email extractor', 'arachni', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project', 'crimscanner', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'vega', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', 'BOT for JCE']);
TomcatEvent
| where HttpUserAgentOriginal has_any (malicious_ua)
| extend MalwareCustomEntity = HttpUserAgentOriginal
entityMappings:
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: MalwareCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: malicious_ua
let malicious_ua = dynamic(['Nikto', 'hydra', '.nasl', 'absinthe', 'advanced email extractor', 'arachni', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project', 'crimscanner', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'vega', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', 'BOT for JCE']);
Stage 1: source
TomcatEvent
Stage 2: where
| where HttpUserAgentOriginal has_any (malicious_ua)
Stage 3: extend
| extend MalwareCustomEntity = HttpUserAgentOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | match |
| field:"HttpUserAgentOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MalwareCustomEntity | extend |
Veeam ONE Suspicious Incremental Backup Size
#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.
Detects suspiciously large incremental backup sizes.
Rule Body
id: 8bb38c66-ca2a-4ad0-9b49-1e60368f8a19
name: Veeam ONE Suspicious Incremental Backup Size
description: Detects suspiciously large incremental backup sizes.
severity: High
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
dataTypes:
- VeeamOneTriggeredAlarms_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 364
version: 1.0.1
kind: Scheduled
customDetails:
PredefinedAlarmId: PredefinedAlarmId
TriggeredAlarmId: TriggeredAlarmId
Description: Description
Name: Name
VoneHostName: VoneHostName
TriggeredTime: TriggeredTime
Status: Status
Comment: Comment
ObjectId: ObjectId
ObjectName: ObjectName
ObjectType: ObjectType
Stages and Predicates
Stage 1: source
VeeamOneTriggeredAlarms_CL
Stage 2: where
| where PredefinedAlarmId == 364
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PredefinedAlarmId | eq |
| field:"PredefinedAlarmId" kind:eq value:"364" |
VMware SD-WAN Edge - IDS/IPS Signature Update Succeeded
#The VMware SD-WAN Edge Management Plane reported a successful IDS/IPS signature update. New signatures might impact Data Plane traffic, therefore an audit event is generated.
Rule Body
id: 6364be84-9f13-4fd8-8b4a-8ccb43a89376
name: VMware SD-WAN Edge - IDS/IPS Signature Update Succeeded
version: 1.0.0
kind: Scheduled
description: The VMware SD-WAN Edge Management Plane reported a successful IDS/IPS signature update. New signatures might impact Data Plane traffic, therefore an audit event is generated.
severity: Informational
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
- connectorId: VMwareSDWAN
dataTypes:
- SDWAN
query: |+
VMware_VECO_EventLogs_CL
| where event == "MGD_ATPUP_APPLY_IDPS_SIGNATURE_SUCCEEDED"
| extend idpsSignatureVersion = extract("\"version\":\"([0-9]+)\"", 1, tostring(todynamic(detail).data))
| extend todynamic(detail).edgeSerialNumber
| extend todynamic(detail).data
| project-rename idpsSignatureData = detail_data
| project-rename edgeSerialNumber = detail_edgeSerialNumber
| project-away detail
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDescriptionFormat: '{{message}} '
alertDynamicProperties: []
customDetails:
idpsSignatureVersion: idpsSignatureVersion
edgeSerialNumber: edgeSerialNumber
suppressionDuration: 5h
Stages and Predicates
Stage 1: source
VMware_VECO_EventLogs_CL
Stage 2: where
| where event == "MGD_ATPUP_APPLY_IDPS_SIGNATURE_SUCCEEDED"
Stage 3: extend (3 consecutive steps)
| extend idpsSignatureVersion = extract("\"version\":\"([0-9]+)\"", 1, tostring(todynamic(detail).data))
| extend todynamic(detail).edgeSerialNumber
| extend todynamic(detail).data
Stage 4: project-rename
| project-rename idpsSignatureData = detail_data
Stage 5: project-rename
| project-rename edgeSerialNumber = detail_edgeSerialNumber
Stage 6: project-away
| project-away detail
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"MGD_ATPUP_APPLY_IDPS_SIGNATURE_SUCCEEDED" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
idpsSignatureVersion | extend |
idpsSignatureData | project-rename |
edgeSerialNumber | project-rename |