Detection rules › Kusto
Kusto rules: 38
Azure DevOps New Extension Added
#'Extensions add additional features to Azure DevOps. An attacker could use a malicious extension to conduct malicious activity. This query looks for new extensions that are not from a configurable list of approved publishers.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: bf07ca9c-e408-443a-8939-6860a45a929e
name: Azure DevOps New Extension Added
description: |
'Extensions add additional features to Azure DevOps. An attacker could use a malicious extension to conduct malicious activity.
This query looks for new extensions that are not from a configurable list of approved publishers.'
severity: Low
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1505
query: |
let allowed_publishers = dynamic([]);
ADOAuditLogs
| where OperationName =~ "Extension.Installed"
| extend ExtensionName = tostring(Data.ExtensionName)
| extend PublisherName = tostring(Data.PublisherName)
| where PublisherName !in (allowed_publishers)
| project-reorder TimeGenerated, OperationName, ExtensionName, PublisherName, ActorUPN, IpAddress, UserAgent, ScopeDisplayName, Data
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let allowed_publishers = dynamic([]);
Stage 1: source
ADOAuditLogs
Stage 2: where
| where OperationName =~ "Extension.Installed"
Stage 3: extend
| extend ExtensionName = tostring(Data.ExtensionName)
Stage 4: extend
| extend PublisherName = tostring(Data.PublisherName)
Stage 5: where
| where PublisherName !in (allowed_publishers)
Stage 6: project-reorder
| project-reorder TimeGenerated, OperationName, ExtensionName, PublisherName, ActorUPN, IpAddress, UserAgent, ScopeDisplayName, Data
Stage 7: extend
| extend timestamp = TimeGenerated
Stage 8: extend
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"Extension.Installed" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
PublisherName | eq | [] | excludes:PublisherName field:"PublisherName" value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ExtensionName | extend |
PublisherName | extend |
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Backup Repository Settings Updated
#Detects when backup repository settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.
Rule Body
id: f78e0bb9-2878-4e36-b9c9-6141bff8d3dd
name: Backup Repository Settings Updated
description: Detects when backup repository settings are updated in Veeam Backup &
Replication. This might indicate configuration changes that require review.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28100\n| extend RepositoryName\
\ = extract(\"RepositoryName=\\\"(?<RepositoryName>[^\\\"]*)\\\"\", 1, SyslogMessage)\n\
| project\n Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource\
\ = original_host,\n EventId = instanceId,\n [\"User Name\"] = user,\n \
\ [\"Object Name\"] = RepositoryName,\n MessageDetails = Description,\n Severity\
\ = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 28100
Stage 3: extend
| extend RepositoryName = extract("RepositoryName=\"(?<RepositoryName>[^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Object Name"] = RepositoryName,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"28100" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Object Name | project |
Severity | project |
User Name | project |
BloodHound Attack Path Finding - Website Contributor Role on Tier Zero Resource
#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 - Website Contributor Role on Tier Zero Resource
Rule Body
id: a1052a3e-d955-4a0b-9f96-179393e7741a
name: BloodHound Attack Path Finding - Website Contributor Role on Tier Zero Resource
description: BloodHound Attack Path Finding - Website Contributor Role on Tier Zero
Resource
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 == "AzureT0WebsiteContributor"
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 == "AzureT0WebsiteContributor"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0WebsiteContributor" |
Cognni Incidents for Highly Sensitive Legal Information
#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.
'Display incidents in which highly sensitive legal information was placed at risk by user sharing.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 4f45f43b-3a4b-491b-9cbe-d649603384aa
name: Cognni Incidents for Highly Sensitive Legal Information
description: |
'Display incidents in which highly sensitive legal information was placed at risk by user sharing.'
severity: High
requiredDataConnectors:
- connectorId: CognniSentinelDataConnector
dataTypes:
- CognniIncidents_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Collection
relevantTechniques:
- T1530
query: |
let highRisk = 3;
let legal = 'Legal Information';
CognniIncidents_CL
| where Severity == highRisk
| where informationType_s == legal
| where TimeGenerated >= ago(5h)
| extend AccountCustomEntity = userId_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let highRisk = 3;
let legal = 'Legal Information';
Stage 1: source
CognniIncidents_CL
Stage 2: where
| where Severity == highRisk
Stage 3: where
| where informationType_s == legal
Stage 4: where
| where TimeGenerated >= ago(5h)
Stage 5: extend
| extend AccountCustomEntity = userId_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | eq |
| field:"Severity" kind:eq value:"3" |
informationType_s | eq |
| field:"informationType_s" kind:eq value:"Legal Information" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
CTE - License Expiring Critically (7 Days)
#A Cybereinforce license - paid or trial - is expiring within 7 days. Urgent renewal action is needed to avoid a lapse in protection. Emitted once per license term by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); re-arms automatically on renewal.
Rule Body
id: c32e5873-00b7-45fc-9a7e-43e9d0018978
name: CTE - License Expiring Critically (7 Days)
description: |
A Cybereinforce license - paid or trial - is expiring within 7 days. Urgent renewal action is needed to avoid a lapse in protection. Emitted once per license term by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); re-arms automatically on renewal.
severity: High
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 24h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
[]
relevantTechniques: []
query: |
CybereinforceCTE_CL
| where EventType == "license.expiring_critical"
| extend d = parse_json(Details)
| extend DaysRemaining = toint(d.daysRemaining), EndAt = tostring(d.endAt), Plan = tostring(d.plan), AppName = "Cybereinforce"
| project TimeGenerated, TenantId, DaysRemaining, EndAt, Plan, AppName, Severity, Category, Source
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: AppName
- identifier: InstanceName
columnName: TenantId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "license.expiring_critical"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend DaysRemaining = toint(d.daysRemaining), EndAt = tostring(d.endAt), Plan = tostring(d.plan), AppName = "Cybereinforce"
Stage 5: project
| project TimeGenerated, TenantId, DaysRemaining, EndAt, Plan, AppName, Severity, Category, Source
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"license.expiring_critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppName | project |
Category | project |
DaysRemaining | project |
EndAt | project |
Plan | project |
Severity | project |
Source | project |
TenantId | project |
TimeGenerated | project |
CyberArkEPM - Attack attempt not blocked
#'This rule triggers on attack attempt which was not blocked by CyberArkEPM.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 8e8978a2-9188-4187-8909-5ea00507bf16
name: CyberArkEPM - Attack attempt not blocked
description: |
'This rule triggers on attack attempt which was not blocked by CyberArkEPM.'
severity: High
requiredDataConnectors:
- connectorId: CyberArkEPM
dataTypes:
- CyberArkEPM
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
CyberArkEPM
| where EventSubType =~ 'AttackAttempt'
| where ThreatProtectionAction =~ 'Detect'
| project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName, Evidences
| extend AccountCustomEntity = ActorUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CyberArkEPM
Stage 2: where
| where EventSubType =~ 'AttackAttempt'
Stage 3: where
| where ThreatProtectionAction =~ 'Detect'
Stage 4: project
| project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName, Evidences
Stage 5: extend
| extend AccountCustomEntity = ActorUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSubType | eq |
| field:"EventSubType" kind:eq value:"AttackAttempt" |
ThreatProtectionAction | eq |
| field:"ThreatProtectionAction" kind:eq value:"Detect" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessFileInternalName | project |
ActorUsername | project |
EventEndTime | project |
EventMessage | project |
Evidences | project |
AccountCustomEntity | extend |
CyberArkEPM - Multiple attack types
#'This rule triggers on multiple attack attemts triggered by same user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: c02f96b4-057b-4e63-87af-6376ef7a081b
name: CyberArkEPM - Multiple attack types
description: |
'This rule triggers on multiple attack attemts triggered by same user.'
severity: High
requiredDataConnectors:
- connectorId: CyberArkEPM
dataTypes:
- CyberArkEPM
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
CyberArkEPM
| where EventSubType =~ 'AttackAttempt'
| summarize LatestAttackTime=max(EventEndTime), att=makeset(EventMessage) by ActorUsername
| where array_length(att) > 1
| extend AccountCustomEntity = ActorUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CyberArkEPM
Stage 2: where
| where EventSubType =~ 'AttackAttempt'
Stage 3: summarize
| summarize LatestAttackTime=max(EventEndTime), att=makeset(EventMessage) by ActorUsername
Stage 4: where
| where array_length(att) > 1
Stage 5: extend
| extend AccountCustomEntity = ActorUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSubType | eq |
| field:"EventSubType" kind:eq value:"AttackAttempt" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | summarize |
LatestAttackTime | summarize |
att | summarize |
AccountCustomEntity | extend |
Disks Alerts From Prancer
#'High severity disk alerts found by Prancer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 8c484ef9-d758-4827-9920-f4f77158f03e
name: Disks Alerts From Prancer
description: |
'High severity disk alerts found by Prancer.'
severity: High
requiredDataConnectors:
- connectorId: PrancerLogData
dataTypes:
- prancer_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: SingleAlert
status: Available
tactics:
- Reconnaissance
relevantTechniques:
- T1595
query: |
union prancer_CL
| where deviceProduct_s == 'azure'
| where parse_json(data_data_snapshots_s)[0].type == 'Microsoft.Compute/disks'
| where data_data_severity_s == 'High' and data_data_result_s == 'failed'
| extend snapshot = parse_json(data_data_snapshots_s)
| mv-expand snapshot
| extend
id = tostring(snapshot.id),
structure = tostring(snapshot.structure),
reference = tostring(snapshot.reference),
source = tostring(snapshot.source),
collection = tostring(snapshot.collection),
type = tostring(snapshot.type),
region = tostring(snapshot.region),
resourceTypes = tostring(snapshot.resourceTypes),
path = tostring(snapshot.path)
customDetails:
entityMappings:
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: path
alertDetailsOverride:
alertDisplayNameFormat: "{{data_data_message_s}}"
alertDescriptionFormat: "{{data_data_description_s}}"
alertSeverityColumnName: "{{data_data_severity_s}}"
alertDynamicProperties:
- alertProperty: RemediationSteps
value: data_data_remediation_description_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: union
union of 1 branches
Stage 2: source
prancer_CL
Stage 3: where
where deviceProduct_s == "azure"
Stage 4: where
where parse_json(data_data_snapshots_s)[0].type == "Microsoft.Compute/disks"
Stage 5: where
where data_data_result_s == "failed" and data_data_severity_s == "High"
Stage 6: extend
extend snapshot
Stage 7: mv-expand
mv-expand snapshot
Stage 8: extend
extend collection, id, path, reference, region, resourceTypes, source, structure, type
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
snapshot | extend |
collection | extend |
id | extend |
path | extend |
reference | extend |
region | extend |
resourceTypes | extend |
source | extend |
structure | extend |
type | extend |
DomainTools Newly Observed Hostnames Feeds Ingested
#Creates an incident whenever DomainTools Newly Observed Hostnames new Feeds (domains) are ingested in the last 1 hour.
Rule Body
id: f8c223c1-1a28-4b28-8e9f-5e36b610916b
name: DomainTools Newly Observed Hostnames Feeds Ingested
description: Creates an incident whenever DomainTools Newly Observed Hostnames new Feeds (domains) are ingested in the last 1 hour.
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: DomainTools
dataTypes:
- DomainToolsThreatIntelDomains_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "DomainToolsThreatIntelDomains(1h, \"noh\")"
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DomainCustomEntity
eventGroupingSettings:
aggregationKind: SingleAlert
version: 1.0.0
Stages and Predicates
Stage 1: source
DomainToolsThreatIntelDomains(1h, "noh")
Highly Sensitive Password Accessed
#'This rule will monitor access to highly sensitive passwords. Within the Watchlist called 'LastPass' define passwords which are deemed highly sensitive (such as password to a high privileged application). When an activity is observed against such password, an incident is created.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Discovery |
Rule Body
id: b39e6482-ab7e-4817-813d-ec910b64b26e
name: Highly Sensitive Password Accessed
description: |
'This rule will monitor access to highly sensitive passwords.
Within the Watchlist called 'LastPass' define passwords which are deemed highly sensitive (such as password to a high privileged application).
When an activity is observed against such password, an incident is created.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: LastPass
dataTypes:
- LastPassNativePoller_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Discovery
relevantTechniques:
- T1555
- T1087
query: |
let watchlist = (_GetWatchlist("LastPass") | project name);
LastPassNativePoller_CL
| where Data_s in (watchlist)
| extend AccountCustomEntity = Username_s, IPCustomEntity = IP_Address_s, URLCustomEntity = Data_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: watchlist
let watchlist = (_GetWatchlist("LastPass") | project name);
Stage 1: source
LastPassNativePoller_CL
Stage 2: where
| where Data_s in (watchlist)
Stage 3: extend
| extend AccountCustomEntity = Username_s, IPCustomEntity = IP_Address_s, URLCustomEntity = Data_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Data_s | in |
| field:"Data_s" kind:in value:"watchlist" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
URLCustomEntity | extend |
Hijack Execution Flow - DLL Side-Loading
#This detection tries to identify all DLLs loaded by "high integrity" processes and cross-checks the DLL paths against FileCreate/FileModify events of the same DLL by a medium integrity process. Of course, we need to do some magic to filter out false positives as much as possible. So any FileCreate/FileModify done by "NT Authoriy\System" and the "RID 500" users aren't interesting. Also, we only want to see the FileCreate/FileModify actions which are performed with a default or limited token elevation. If done with a full elevated token, the user is apparently admin already.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
Rule Body
id: 3084b487-fad6-4000-9544-6085b9657290
name: Hijack Execution Flow - DLL Side-Loading
description: |
This detection tries to identify all DLLs loaded by "high integrity" processes and cross-checks the DLL paths against FileCreate/FileModify events of the same DLL by a medium integrity process.
Of course, we need to do some magic to filter out false positives as much as possible. So any FileCreate/FileModify done by "NT Authoriy\System" and the "RID 500" users aren't interesting.
Also, we only want to see the FileCreate/FileModify actions which are performed with a default or limited token elevation. If done with a full elevated token, the user is apparently admin already.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
- DeviceImageLoadEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
- DefenseEvasion
relevantTechniques:
- T1574.002
query: |
let imls = materialize(
DeviceImageLoadEvents
| where InitiatingProcessIntegrityLevel in ("High", "System") and FileName !endswith ".exe"
| project FolderPath=tolower(FolderPath), InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName
| distinct FolderPath, InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName
);
imls
| join (
DeviceFileEvents
| where FolderPath in~ ((imls | project FolderPath)) and ActionType in ("FileCreated", "FileModified") and
InitiatingProcessIntegrityLevel !in ("High", "System", "") and InitiatingProcessAccountSid != "S-1-5-18" and
InitiatingProcessTokenElevation in ("TokenElevationTypeDefault", "TokenElevationTypeLimited") and InitiatingProcessAccountSid !endswith "-500"
| extend FolderPath=tolower(FolderPath)
) on FolderPath, DeviceId, DeviceName
| project-away FolderPath1
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: InitiatingProcessAccountSid
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: NTDomain
columnName: InitiatingProcessAccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: InitiatingProcessCommandLine
version: 1.0.1
kind: Scheduled
Stages and Predicates
let imls is inlined into the numbered stages below.
Stage 1: source
let imls
Stage 2: source
DeviceImageLoadEvents
Stage 3: where
where not (FileName endswith ".exe") and InitiatingProcessIntegrityLevel in ("High", "System")
Stage 4: project
project DeviceId, DeviceName, FolderPath, InitiatingProcessFileName, InitiatingProcessIntegrityLevel
Stage 5: distinct
distinct DeviceId, DeviceName, FolderPath, InitiatingProcessFileName, InitiatingProcessIntegrityLevel
Stage 6: join
join (DeviceFileEvents) on FolderPath, DeviceId, DeviceName
Stage 7: project-away
project-away FolderPath1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
InitiatingProcessAccountSid | ne |
| field:"InitiatingProcessAccountSid" kind:ne value:"S-1-5-18" |
InitiatingProcessIntegrityLevel | in |
| field:"InitiatingProcessIntegrityLevel" kind:in |
InitiatingProcessTokenElevation | in |
| field:"InitiatingProcessTokenElevation" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FileName | ends_with | .exe | excludes:FileName field:"FileName" value:".exe" |
InitiatingProcessAccountSid | ends_with | -500 | excludes:InitiatingProcessAccountSid field:"InitiatingProcessAccountSid" value:"-500" |
InitiatingProcessIntegrityLevel | in | High, System | excludes:InitiatingProcessIntegrityLevel field:"InitiatingProcessIntegrityLevel" value:"High" field:"InitiatingProcessIntegrityLevel" value:"System" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | project |
DeviceName | project |
FolderPath | project |
InitiatingProcessFileName | project |
InitiatingProcessIntegrityLevel | project |
Hunt for public facing devices via DeviceNetworkEvents
#Find public facing devices via the DeviceNetworkEvents table.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
References
Telemetry Coverage
Rule Body
DeviceNetworkEvents
| where ActionType contains "InboundConnection"
| where RemoteIPType == "Public"
| distinct DeviceName
Stages and Predicates
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where ActionType contains "InboundConnection"
Stage 3: where
| where RemoteIPType == "Public"
Stage 4: distinct
| distinct DeviceName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | contains |
| field:"ActionType" kind:contains value:"InboundConnection" |
RemoteIPType | eq |
| field:"RemoteIPType" kind:eq value:"Public" |
Illumio VEN Suspend Detection Rule
#'Create Microsoft Sentinel Incident When Ven Goes Into Suspended state'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 7379f752-18a2-43ca-8b74-70747dd792f8
name: Illumio VEN Suspend Detection Rule
description: |
'Create Microsoft Sentinel Incident When Ven Goes Into Suspended state'
severity: High
status: Available
requiredDataConnectors:
- connectorId: IllumioSaaSDataConnector
dataTypes:
- Illumio_Auditable_Events_CL
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 60m
queryPeriod: 60m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
Illumio_Auditable_Events_CL
| union IllumioSyslogAuditEvents
| where event_type has 'agent.suspend'
| extend ipaddress = action.src_ip,
hostname = created_by.agent.hostname
| project-away resource_changes, action, version
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ipaddress
alertDetailsOverride:
alertDisplayNameFormat: |
Illumio VEN Suspended Incident for {{hostname}}
alertDescriptionFormat: |
Illumio VEN Suspended Incident for {{hostname}} generated at {{TimeGenerated}}
version: 1.0.6
kind: Scheduled
Stages and Predicates
Stage 1: source
Illumio_Auditable_Events_CL
Stage 2: union
union of 1 branches
Stage 3: source
IllumioSyslogAuditEvents
Stage 4: where
where event_type has "agent.suspend"
Stage 5: extend
extend hostname, ipaddress
Stage 6: project-away
project-away action, resource_changes, version
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type | match |
| field:"event_type" kind:match value:"agent.suspend" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
hostname | extend |
ipaddress | extend |
Imperva - Request to unexpected destination port
#'Detects request attempts to unexpected destination ports.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 0ba78922-033c-468c-82de-2974d7b1797d
name: Imperva - Request to unexpected destination port
description: |
'Detects request attempts to unexpected destination ports.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: ImpervaWAFCloudAPI
dataTypes:
- ImpervaWAFCloud
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let bl_ports = dynamic(['22', '3389']);
ImpervaWAFCloud
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
| where DstPortNumber in (bl_ports)
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let bl_ports = dynamic(['22', '3389']);
Stage 1: source
ImpervaWAFCloud
Stage 2: where
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
Stage 3: where
| where DstPortNumber in (bl_ports)
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstPortNumber | in |
| field:"DstPortNumber" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Object Storage Settings Updated
#Detects when object storage settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.
Rule Body
id: 1f6897bf-a05a-47b2-a5f9-4ba6fd34f715
name: Object Storage Settings Updated
description: Detects when object storage settings are updated in Veeam Backup & Replication.
This might indicate configuration changes that require review.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28970\n| extend Name = extract(\"\
Name=\\\"(?<Name>[^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"User Name\"] = user,\n [\"Data SourceName\"] = Name,\n MessageDetails\
\ = Description,\n Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 28970
Stage 3: extend
| extend Name = extract("Name=\"(?<Name>[^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Data SourceName"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"28970" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Data SourceName | project |
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
Remote File Creation with PsExec
#This query was originally published in the threat analytics report, Ryuk ransomware. There is also a related blog. Ryuk is human-operated ransomware. Much like DoppelPaymer ransomware, Ryuk is spread manually, often on networks that are already infected with Trickbot. Ryuk operators use PsExec to manually spread the ransomware to other devices. The following query detects remote file creation events that might indicate an active attack. The See also section below lists links to other queries associated with Ryuk ransomware. References: https://www.microsoft.com/security/blog/2020/03/05/human-operated-ransomware-attacks-a-preventable-disaster/ https://www.microsoft.com/en-us/wdsi/threats/malware-encyclopedia-description?Name=Ransom:Win32/Ryuk.AA https://www.microsoft.com/security/blog/2020/03/05/human-operated-ransomware-attacks-a-preventable-disaster/ https://docs.microsoft.com/sysinternals/downloads/psexec
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceFileEvents action any: File activity |
Rule Body
id: 35ab0d58-baab-4154-87ed-fa2f69797e9e
name: Remote File Creation with PsExec
description: |
This query was originally published in the threat analytics report, Ryuk ransomware. There is also a related blog.
Ryuk is human-operated ransomware. Much like DoppelPaymer ransomware, Ryuk is spread manually, often on networks that are already infected with Trickbot.
Ryuk operators use PsExec to manually spread the ransomware to other devices.
The following query detects remote file creation events that might indicate an active attack.
The See also section below lists links to other queries associated with Ryuk ransomware.
References:
https://www.microsoft.com/security/blog/2020/03/05/human-operated-ransomware-attacks-a-preventable-disaster/
https://www.microsoft.com/en-us/wdsi/threats/malware-encyclopedia-description?Name=Ransom:Win32/Ryuk.AA
https://www.microsoft.com/security/blog/2020/03/05/human-operated-ransomware-attacks-a-preventable-disaster/
https://docs.microsoft.com/sysinternals/downloads/psexec
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1570
tags:
- Ryuk
- Ransomware
- PsExec
query: |
// Find PsExec creating multiple files on remote machines in a 10-minute window
DeviceFileEvents
// Looking for PsExec by accepteula command flag
| where InitiatingProcessCommandLine has "accepteula"
// Remote machines and file is exe
| where FolderPath has "\\\\" and FileName endswith ".exe"
| extend Exe = countof(InitiatingProcessCommandLine, ".exe")
// Checking to see if command line has 2 .exe or .bat
| where InitiatingProcessCommandLine !has ".ps1" and Exe > 1 or
InitiatingProcessCommandLine has ".bat"
// Exclusions: Remove the following line to widen scope of AHQ
| where not(InitiatingProcessCommandLine has_any("batch", "auditpol",
"script", "scripts", "illusive", "rebootrequired"))
| summarize FileCount = dcount(FolderPath), make_set(SHA1, 100000), make_set(FolderPath, 100000),
make_set(FileName, 100000), make_set(InitiatingProcessCommandLine, 100000) by DeviceId, DeviceName,
TimeWindow=bin(TimeGenerated, 10m), InitiatingProcessFileName
| where FileCount > 4
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceFileEvents
Stage 2: where
| where InitiatingProcessCommandLine has "accepteula"
Stage 3: where
| where FolderPath has "\\\\" and FileName endswith ".exe"
Stage 4: extend
| extend Exe = countof(InitiatingProcessCommandLine, ".exe")
Stage 5: where
| where InitiatingProcessCommandLine !has ".ps1" and Exe > 1 or
InitiatingProcessCommandLine has ".bat"
Stage 6: where
| where not(InitiatingProcessCommandLine has_any("batch", "auditpol",
"script", "scripts", "illusive", "rebootrequired"))
Stage 7: summarize
| summarize FileCount = dcount(FolderPath), make_set(SHA1, 100000), make_set(FolderPath, 100000),
make_set(FileName, 100000), make_set(InitiatingProcessCommandLine, 100000) by DeviceId, DeviceName,
TimeWindow=bin(TimeGenerated, 10m), InitiatingProcessFileName
Stage 8: where
| where FileCount > 4
Stage 9: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 10: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Exe | gt |
| field:"Exe" kind:gt value:"1" |
FileCount | gt |
| field:"FileCount" kind:gt value:"4" |
FileName | ends_with |
| field:"file_name" kind:ends_with value:".exe" |
FolderPath | match |
| field:"TargetFilename" kind:match value:"\\" |
InitiatingProcessCommandLine | match |
| field:"CommandLine" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessCommandLine | match | batch, auditpol, script, scripts, illusive, rebootrequired | excludes:InitiatingProcessCommandLine |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
DeviceName | summarize |
FileCount | summarize |
InitiatingProcessFileName | summarize |
TimeWindow | summarize |
HostName | extend |
DnsDomain | extend |
Response rows stateful anomaly on database
#'Goal: To detect anomalous data exfiltration. This query detects SQL queries that accessed a large number of rows, which is significantly higher than normal for this database. The calculation is made inside recent time window (defined by 'detectionWindow' parameter), and the anomaly is calculated based on previous training window (defined by 'trainingWindow' parameter). The user can set the minimal threshold for anomaly by changing the threshold parameters volThresholdZ and volThresholdQ (higher thresholds will detect only more severe anomalies).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
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)
- Outgoing connection attempts stateful anomaly on database (Kusto)
Rule Body
id: 9851c360-5fd5-4bae-a117-b66d8476bf5e
name: Response rows stateful anomaly on database
description: |
'Goal: To detect anomalous data exfiltration. This query detects SQL queries that accessed a large number of rows, which is significantly higher than normal for this database.
The calculation is made inside recent time window (defined by 'detectionWindow' parameter), and the anomaly is calculated based on previous training window (defined by 'trainingWindow' parameter). The user can set the minimal threshold for anomaly by changing the threshold parameters volThresholdZ and volThresholdQ (higher thresholds will detect only more severe anomalies).'
severity: Medium
requiredDataConnectors:
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Collection
relevantTechniques:
- T1213.006
tags:
- SQL
query: |
let volumeThresholdZ = 3.0; // Minimal threshold for the Zscore to trigger anomaly (number of standard deviations above mean). If set higher, only very significant alerts will fire.
let volumeThresholdQ = volumeThresholdZ; // Minimal threshold for the Qscore to trigger anomaly (number of Inter-Percentile Ranges above high percentile). If set higher, only very significant alerts will fire.
let volumeThresholdHardcoded = 500; // Minimal value for the volume metric to trigger anomaly.
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 monitoredColumn = 'ResponseRows'; // The name of the column for volumetric anomalies.
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
| extend QuantityColumn = column_ifexists(monitoredColumn, 0)
| extend WindowType = case( TimeGenerated >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= TimeGenerated and TimeGenerated < ago(detectionWindow)), 'training', 'other')
| where WindowType in ('detection', 'training'));
let trainingSet =
processedData
| where WindowType == 'training'
| summarize AvgVal = round(avg(QuantityColumn), 2), StdVal = round(stdev(QuantityColumn), 2), N = count(),
P99Val = round(percentile(QuantityColumn, 99), 2), P50Val = round(percentile(QuantityColumn, 50), 2)
by Database;
processedData
| where WindowType == 'detection'
| join kind = inner (trainingSet) on Database
| extend ZScoreVal = iff(N >= 20, round(todouble(QuantityColumn - AvgVal) / todouble(StdVal + 1), 2), 0.00),
QScoreVal = iff(N >= 20, round(todouble(QuantityColumn - P99Val) / todouble(P99Val - P50Val + 1), 2), 0.00)
| extend IsVolumeAnomalyOnVal = iff((ZScoreVal > volumeThresholdZ and QScoreVal > volumeThresholdQ and QuantityColumn > volumeThresholdHardcoded), true, false), AnomalyScore = round((ZScoreVal + QScoreVal)/2, 0)
| where IsVolumeAnomalyOnVal == 'true'
| project TimeGenerated, Database, PrincipalName, ClientIp, HostName, ApplicationName, ActionName, Statement,
IsSuccess, ResponseRows, AffectedRows, IsVolumeAnomalyOnVal, AnomalyScore, ResourceId
| 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
alertDetailsOverride:
alertDisplayNameFormat: 'Response rows anomaly on database {{Database}}'
alertDescriptionFormat: |
'An anomaly was detected on database {{Database}} with {{ResponseRows}} response rows in a statement, which is significantly higher than normal for this database. Investigate the database activity for potential malicious data changes or deletions.'
version: 1.1.3
kind: Scheduled
Stages and Predicates
Parameters
let volumeThresholdZ = 3.0;
let volumeThresholdQ = volumeThresholdZ;
let volumeThresholdHardcoded = 500;
let detectionWindow = 1h;
let trainingWindow = detectionWindow + 14d;
let monitoredColumn = 'ResponseRows';
let processedData is inlined into the numbered stages below.
Let Binding: trainingSet
let trainingSet = processedData
| where WindowType == 'training'
| summarize AvgVal = round(avg(QuantityColumn), 2), StdVal = round(stdev(QuantityColumn), 2), N = count(),
P99Val = round(percentile(QuantityColumn, 99), 2), P50Val = round(percentile(QuantityColumn, 50), 2)
by Database;
Stages 1 to 7 define let processedData as the rule's main pipeline source. Stages 8 to 15 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
Stage 5: extend
| extend QuantityColumn = column_ifexists(monitoredColumn, 0)
Stage 6: extend
| extend WindowType = case( TimeGenerated >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= TimeGenerated and TimeGenerated < ago(detectionWindow)), 'training', 'other')
WindowType =TimeGenerated >= ago(3600s)'detection'/* macro: (ago(trainingWindow) <= TimeGenerated) */'training''other'Stage 7: where
| where WindowType in ('detection', 'training')
Stage 8: where
processedData
| where WindowType == 'detection'
Stage 9: join
| join kind = inner (trainingSet) on Database
Stage 10: extend
extend QScoreVal, ZScoreVal
QScoreVal =N >= 20round((todouble((QuantityColumn - P99Val)) / todouble(((P99Val - P50Val) + 1))), 2)0.0ZScoreVal =N >= 20round((todouble((QuantityColumn - AvgVal)) / todouble((StdVal + 1))), 2)0.0Stage 11: extend
extend AnomalyScore, IsVolumeAnomalyOnVal
IsVolumeAnomalyOnVal =(ZScoreVal > volumeThresholdZ and QScoreVal > volumeThresholdQ) and QuantityColumn > volumeThresholdHardcodedtruefalseStage 12: where
where IsVolumeAnomalyOnVal == true
Stage 13: project
project ActionName, AffectedRows, AnomalyScore, ApplicationName, ClientIp, Database, HostName, IsSuccess, IsVolumeAnomalyOnVal, PrincipalName, ResourceId, ResponseRows, Statement, TimeGenerated
Stage 14: extend
extend Name, UPNSuffix
Stage 15: summarize aggregation inside the join branch
summarize by Database
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"SQLSecurityAuditEvents" |
IsVolumeAnomalyOnVal | eq |
| field:"IsVolumeAnomalyOnVal" 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 |
|---|---|
Database | summarize |
Tailscale: Auth key created
#Identifies when a new Tailscale auth key is generated. Auth keys allow unattended device enrollment into the tailnet - confirm it was expected and revoke if not.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 6b052c8d-5de8-eab0-1956-69a297765a32
name: "Tailscale: Auth key created"
description: |
Identifies when a new Tailscale auth key is generated. Auth keys allow unattended device enrollment into the tailnet - confirm it was expected and revoke if not.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
Tailscale_Audit_CL
| where Action == "CREATE"
| where tostring(Target.type) == "AUTH_KEY"
| extend ActorLogin = tostring(Actor.loginName)
| extend KeyDescription = tostring(New.description)
| extend Reusable = tostring(New.reusable)
| extend Ephemeral = tostring(New.ephemeral)
| project TimeGenerated, ActorLogin, KeyDescription, Reusable, Ephemeral, Origin, New
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorLogin
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Tailscale_Audit_CL
Stage 2: where
| where Action == "CREATE"
Stage 3: where
| where tostring(Target.type) == "AUTH_KEY"
Stage 4: extend (4 consecutive steps)
| extend ActorLogin = tostring(Actor.loginName)
| extend KeyDescription = tostring(New.description)
| extend Reusable = tostring(New.reusable)
| extend Ephemeral = tostring(New.ephemeral)
Stage 5: project
| project TimeGenerated, ActorLogin, KeyDescription, Reusable, Ephemeral, Origin, New
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"CREATE" |
Target.type | eq |
| field:"Target.type" kind:eq value:"AUTH_KEY" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
Ephemeral | project |
KeyDescription | project |
New | project |
Origin | project |
Reusable | project |
TimeGenerated | project |
TI Map IP Entity to Azure SQL Security Audit Events
#This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SQL Security Audit Events.
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)
- Outgoing connection attempts stateful anomaly on database (Kusto)
Rule Body
id: d0aa8969-1bbe-4da3-9e76-09e5f67c9d85
name: TI Map IP Entity to Azure SQL Security Audit Events
description: |
This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SQL Security Audit Events.
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
- 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 AzureDiagnostics logs
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
// Fetch threat intelligence indicators related to IP addresses
let IP_Indicators = ThreatIntelligenceIndicator
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
| where TimeGenerated >= ago(ioc_lookBack)
| 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)
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now();
// Perform a join between IP indicators and AzureDiagnostics logs for SQL Security Audit events
IP_Indicators
// Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
| join kind=innerunique (
AzureDiagnostics
| where TimeGenerated >= ago(dt_lookBack)
| where ResourceProvider == 'MICROSOFT.SQL'
| where Category == 'SQLSecurityAuditEvents'
| extend SQLSecurityAuditEvents_TimeGenerated = TimeGenerated
| extend ClientIP = column_ifexists("client_ip_s", "Not Available")
| extend Action = column_ifexists("action_name_s", "Not Available")
| extend Application = column_ifexists("application_name_s", "Not Available")
| extend HostName = column_ifexists("host_name_s", "Not Available")
)
on $left.TI_ipEntity == $right.ClientIP
// Filter out logs that occurred after the expiration of the corresponding indicator
| where SQLSecurityAuditEvents_TimeGenerated < ExpirationDateTime
// Group the results by IndicatorId and ClientIP, and keep the log entry with the latest timestamp
| summarize SQLSecurityAuditEvents_TimeGenerated = arg_max(SQLSecurityAuditEvents_TimeGenerated, *) by IndicatorId, ClientIP
// Select the desired output fields
| project SQLSecurityAuditEvents_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, ResourceId, ClientIP, Action, Application, HostName, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
// Rename the timestamp field
| extend timestamp = SQLSecurityAuditEvents_TimeGenerated
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.3.2
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Stages 1 to 7 define let IP_Indicators as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: 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 5: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: join
IP_Indicators
| join kind=innerunique (
AzureDiagnostics
| where TimeGenerated >= ago(dt_lookBack)
| where ResourceProvider == 'MICROSOFT.SQL'
| where Category == 'SQLSecurityAuditEvents'
| extend SQLSecurityAuditEvents_TimeGenerated = TimeGenerated
| extend ClientIP = column_ifexists("client_ip_s", "Not Available")
| extend Action = column_ifexists("action_name_s", "Not Available")
| extend Application = column_ifexists("application_name_s", "Not Available")
| extend HostName = column_ifexists("host_name_s", "Not Available")
)
on $left.TI_ipEntity == $right.ClientIP
Stage 9: where
where SQLSecurityAuditEvents_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize SQLSecurityAuditEvents_TimeGenerated by IndicatorId, ClientIP
Stage 11: project
project Action, ActivityGroupNames, Application, ClientIP, ConfidenceScore, Description, EmailSourceIpAddress, ExpirationDateTime, HostName, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, ResourceId, SQLSecurityAuditEvents_TimeGenerated, TI_ipEntity, ThreatType, Type, Url
Stage 12: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Category | eq |
| field:"Category" kind:eq value:"SQLSecurityAuditEvents" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null | |
ResourceProvider | eq |
| field:"ResourceProvider" kind:eq value:"MICROSOFT.SQL" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActivityGroupNames | project |
Application | project |
ClientIP | project |
ConfidenceScore | project |
Description | project |
EmailSourceIpAddress | project |
ExpirationDateTime | project |
HostName | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
ResourceId | project |
SQLSecurityAuditEvents_TimeGenerated | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
timestamp | extend |
TIE Password issues
#'Searches for triggered Indicators of Exposures related to password issues.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 87af910a-e9c0-4c96-8045-f778ba405251
name: TIE Password issues
description: |
'Searches for triggered Indicators of Exposures related to password issues.'
severity: Low
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| 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
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
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 |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Tomcat - Put file and get file from same IP address
#'Detects put or get files from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 103d5ada-4874-11ec-81d3-0242ac130003
name: Tomcat - Put file and get file from same IP address
description: |
'Detects put or get files from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let p = TomcatEvent
| where HttpRequestMethod in~ ('POST', 'PUT')
| sort by EventStartTime asc
| summarize post_time=min(EventStartTime) by SrcIpAddr, tostring(UrlOriginal);
TomcatEvent
| where HttpRequestMethod =~ 'GET'
| sort by EventStartTime asc
| summarize get_time=min(EventStartTime) by SrcIpAddr, tostring(UrlOriginal)
| join kind=innerunique (p) on UrlOriginal, SrcIpAddr
| extend second = datetime_diff('second',get_time,post_time)
| where second between (1 .. 300)
| project second, post_time, get_time, SrcIpAddr, UrlOriginal
| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = tostring(UrlOriginal)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: p
let p = TomcatEvent
| where HttpRequestMethod in~ ('POST', 'PUT')
| sort by EventStartTime asc
| summarize post_time=min(EventStartTime) by SrcIpAddr, tostring(UrlOriginal);
Stage 1: source
TomcatEvent
Stage 2: where
| where HttpRequestMethod =~ 'GET'
Stage 3: sort
| sort by EventStartTime asc
Stage 4: summarize
| summarize get_time=min(EventStartTime) by SrcIpAddr, tostring(UrlOriginal)
Stage 5: join
| join kind=innerunique (p) on UrlOriginal, SrcIpAddr
Stage 6: extend
extend second
Stage 7: where
where second >= 1 and second <= 300
Stage 8: project
project SrcIpAddr, UrlOriginal, get_time, post_time, second
Stage 9: extend
extend IPCustomEntity, UrlCustomEntity
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | project |
UrlOriginal | project |
get_time | project |
post_time | project |
second | project |
IPCustomEntity | extend |
UrlCustomEntity | extend |