Detection rules › Kusto
Kusto rules: 0v
BloodHound Attack Path Finding - Large Default Groups With RDP Access
#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 RDP Access
Rule Body
id: f4776f0d-86b7-470a-993a-f3152bbb8917
name: BloodHound Attack Path Finding - Large Default Groups With RDP Access
description: BloodHound Attack Path Finding - Large Default Groups With RDP Access
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 == "LargeDefaultGroupsRDP"
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 == "LargeDefaultGroupsRDP"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"LargeDefaultGroupsRDP" |
Cisco SEG - Potential phishing link
#'Detects mails with suspicious links.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 2e5158e1-9fc2-40ff-a909-c701a13a0405
name: Cisco SEG - Potential phishing link
description: |
'Detects mails with suspicious links.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let dl_cat = dynamic(['Illegal Downloads']);
CiscoSEGEvent
| where NetworkDirection =~ 'Incoming'
| where tostring(AdditionalFields) has 'ESAURLDetails'
| extend link_cat = extract(@"'Category': '(.*?)'", 1, tostring(AdditionalFields))
| where link_cat in~ (dl_cat)
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let dl_cat = dynamic(['Illegal Downloads']);
Stage 1: source
CiscoSEGEvent
Stage 2: where
| where NetworkDirection =~ 'Incoming'
Stage 3: where
| where tostring(AdditionalFields) has 'ESAURLDetails'
Stage 4: extend
| extend link_cat = extract(@"'Category': '(.*?)'", 1, tostring(AdditionalFields))
Stage 5: where
| where link_cat in~ (dl_cat)
Stage 6: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalFields | match |
| field:"AdditionalFields" kind:match value:"ESAURLDetails" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"Incoming" |
link_cat | in |
| field:"link_cat" kind:in value:"dl_cat" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
link_cat | extend |
AccountCustomEntity | extend |
Contrast ADR - Security Incident Alert
#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.
'Monitors Contrast ADR security incidents across all applications and environments. This rule creates alerts for all security incidents detected by Contrast Security ADR, providing comprehensive visibility into application security events requiring investigation.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Stealth | |
| Discovery | |
| Command & Control |
Rule Body
id: 7ce5956f-48f2-42f5-8e2e-c254e7643c11
name: Contrast ADR - Security Incident Alert
description: |
'Monitors Contrast ADR security incidents across all applications and environments. This rule creates alerts for all security incidents detected by Contrast Security ADR, providing comprehensive visibility into application security events requiring investigation.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ContrastADRCCF
dataTypes:
- ContrastADRIncidents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- DefenseEvasion
- Discovery
- CommandAndControl
relevantTechniques:
- T1190
- T1055
- T1018
- T1008
query: |
ContrastADRIncidents_CL
entityMappings:
- entityType: SecurityGroup
fieldMappings:
- identifier: ObjectGuid
columnName: incidentId
alertDetailsOverride:
alertDisplayNameFormat: '{{incidentName}}'
alertDescriptionFormat: '{{summary}}'
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT1H
matchingMethod: Selected
groupByEntities:
- SecurityGroup
eventGroupingSettings:
aggregationKind: AlertPerResult
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
ContrastADRIncidents_CL
Darktrace Model Breach (Legacy)
#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 rule creates Microsoft Sentinel Alerts based on Darktrace Model Breaches, fetched every 5 minutes.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Lateral Movement | |
| Command & Control |
Rule Body
id: a3c7b8ed-56a9-47b7-98e5-2555c16e17c9
name: Darktrace Model Breach (Legacy)
description: |
'This rule creates Microsoft Sentinel Alerts based on Darktrace Model Breaches, fetched every 5 minutes.'
severity: Medium
requiredDataConnectors:
- connectorId: DarktraceRESTConnector
dataTypes:
- darktrace_model_alerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- LateralMovement
- CommandAndControl
relevantTechniques:
- T1190
- T1059
- T1021
- T1071
query: |
darktrace_model_alerts_CL
| where dtProduct_s =="Policy Breach"
| project-rename EventSeverity=score_d, EventStartTime=breachTime_s, NetworkRuleName=modelName_s, NetworkRuleNumber=pid_d, ThreatIdentifier=threatID_d, ThreatCategory=dtProduct_s, SrcIpAddr=SourceIP, SrcHostname=hostname_s, SrcMacAddr=sourceMac_s, SrcPortNumber=sourcePort_s, DstIpAddr=destIP_s, DstPortNumber=destPort_s, DstHostname=destHost_s, DstMacAddr=destMac_s, DtCompliance=compliance_b, DtClientSensor=cSensor_b, DtDescription=description_s, DtAntigena=antigena_b, DtDeviceIdentifier=deviceId_d, DtSubnetID=sid_d, DtTags=tags_s, DtMitreTechniques=mitreTechniques_s, DtMessage=Message, DtUUID=uuid_g, DtBreachURL=breachUrl_s, DtSrcTypeLabel=typeLabel_s, DtTriggeredComponents=triggeredComponents_s, DtDetails=details_s, DtLongitude=longitude_d, DtLatitude=latitude_d, DtCategory=Category
| extend EventCount=1, EventType="NetworkSession", EventSchema="NetworkSession", EventSchemaVersion="0.2.2", EventResult="Success", DvcAction = "Allow", EventVendor = "Darktrace", EventProduct = "Darktrace DETECT", EventEndTime=EventStartTime, ThreatName=NetworkRuleName, ThreatRiskLevel=EventSeverity
| extend DtSentinelCategory = case(DtCategory == "Suspicious", "Medium",
DtCategory == "Critical", "High",
"Informational")
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcHostname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DstHostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DstIpAddr
customDetails:
SrcMacAddr: SrcMacAddr
EventSeverity: EventSeverity
EventStartTime: EventStartTime
NetworkRuleName: NetworkRuleName
NetworkRuleNumber: NetworkRuleNumber
ThreatIdentifier: ThreatIdentifier
DtSentinelCategory: DtSentinelCategory
SrcPortNumber: SrcPortNumber
DstPortNumber: DstPortNumber
DstMacAddr: DstMacAddr
DtCompliance: DtCompliance
DtDescription: DtDescription
DtCategory: DtCategory
DtDeviceIdentifier: DtDeviceIdentifier
# These are described here - this is why we're leaving tactics and techniques above empty
alertDetailsOverride:
# model breach name here
alertDisplayNameFormat: 'Darktrace: {{ThreatRiskLevel}} - {{NetworkRuleName}}' # Up to 256 chars and 3 placeholders
alertDescriptionFormat: '{{DtMessage}}' # Up to 5000 chars and 3 placeholders
# MITRE tactic
alertTacticsColumnName: # leave empty
alertSeverityColumnName: # leave empty
alertDynamicProperties:
- alertProperty: AlertLink
value: DtBreachURL
- alertProperty: ProviderName
value: EventVendor
- alertProperty: ProductName
value: EventProduct
- alertProperty: ProductComponentName
value: ThreatCategory
- alertProperty: Severity
value: DtSentinelCategory
version: 1.1.1
kind: NRT
Stages and Predicates
Stage 1: source
darktrace_model_alerts_CL
Stage 2: where
| where dtProduct_s =="Policy Breach"
Stage 3: project-rename
| project-rename EventSeverity=score_d, EventStartTime=breachTime_s, NetworkRuleName=modelName_s, NetworkRuleNumber=pid_d, ThreatIdentifier=threatID_d, ThreatCategory=dtProduct_s, SrcIpAddr=SourceIP, SrcHostname=hostname_s, SrcMacAddr=sourceMac_s, SrcPortNumber=sourcePort_s, DstIpAddr=destIP_s, DstPortNumber=destPort_s, DstHostname=destHost_s, DstMacAddr=destMac_s, DtCompliance=compliance_b, DtClientSensor=cSensor_b, DtDescription=description_s, DtAntigena=antigena_b, DtDeviceIdentifier=deviceId_d, DtSubnetID=sid_d, DtTags=tags_s, DtMitreTechniques=mitreTechniques_s, DtMessage=Message, DtUUID=uuid_g, DtBreachURL=breachUrl_s, DtSrcTypeLabel=typeLabel_s, DtTriggeredComponents=triggeredComponents_s, DtDetails=details_s, DtLongitude=longitude_d, DtLatitude=latitude_d, DtCategory=Category
Stage 4: extend
| extend EventCount=1, EventType="NetworkSession", EventSchema="NetworkSession", EventSchemaVersion="0.2.2", EventResult="Success", DvcAction = "Allow", EventVendor = "Darktrace", EventProduct = "Darktrace DETECT", EventEndTime=EventStartTime, ThreatName=NetworkRuleName, ThreatRiskLevel=EventSeverity
Stage 5: extend
| extend DtSentinelCategory = case(DtCategory == "Suspicious", "Medium",
DtCategory == "Critical", "High",
"Informational")
DtSentinelCategory =DtCategory == "Suspicious""Medium"DtCategory == "Critical""High""Informational"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
dtProduct_s | eq |
| field:"dtProduct_s" kind:eq value:"Policy Breach" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstHostname | project-rename |
DstIpAddr | project-rename |
DstMacAddr | project-rename |
DstPortNumber | project-rename |
DtAntigena | project-rename |
DtBreachURL | project-rename |
DtCategory | project-rename |
DtClientSensor | project-rename |
DtCompliance | project-rename |
DtDescription | project-rename |
DtDetails | project-rename |
DtDeviceIdentifier | project-rename |
DtLatitude | project-rename |
DtLongitude | project-rename |
DtMessage | project-rename |
DtMitreTechniques | project-rename |
DtSrcTypeLabel | project-rename |
DtSubnetID | project-rename |
DtTags | project-rename |
DtTriggeredComponents | project-rename |
DtUUID | project-rename |
EventSeverity | project-rename |
EventStartTime | project-rename |
NetworkRuleName | project-rename |
NetworkRuleNumber | project-rename |
SrcHostname | project-rename |
SrcIpAddr | project-rename |
SrcMacAddr | project-rename |
SrcPortNumber | project-rename |
ThreatCategory | project-rename |
ThreatIdentifier | project-rename |
DvcAction | extend |
EventCount | extend |
EventEndTime | extend |
EventProduct | extend |
EventResult | extend |
EventSchema | extend |
EventSchemaVersion | extend |
EventType | extend |
EventVendor | extend |
ThreatName | extend |
ThreatRiskLevel | extend |
DtSentinelCategory | extend |
Detect Multiple Hello for Business PRT tokens being used simultaneously for one device.
#This detection rule tries to find multiple PRT tokens being used simultaneously for one device. This might indicate that an attacker was able to request a new PRT on a second device using exxported Windows Hello for Business keys. More information about the attack scenario can be found in the references.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Sign-in naming a target application |
Rule Body
// Get the Sign-in logs we want to query
let base = materialize(
SigninLogs
| where Timestamp > ago(1d)
);
// Get all the WHfB signins by looking at the authentication method and incomming token
let whfb = (
base
// Get WHfB signins
| mv-expand todynamic(AuthenticationDetails)
| where AuthenticationDetails.authenticationMethod == "Windows Hello for Business"
| where IncomingTokenType == "primaryRefreshToken"
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
// Remove empty Session and Device IDs
| where SessionId != "" and DeviceID != ""
);
// Save the time frame for each WHfB PRT token
// We use the SessionID to identify a specific PRT token since the SessionID changes when a new refresh token is being used
let prt_timeframes = (
whfb
// Summarize the first and last PRT usage per device, by using the Session ID
| summarize TimeMin = arg_min(AuthenticationDateTime,*), TimeMax=arg_max(AuthenticationDateTime,*) by DeviceID, SessionId
| project DeviceID, SessionId, TimeMin, TimeMax
);
// Save all the Session IDs for the logins that came from a WHfB authentication method
let whfb_sessions = toscalar(
whfb
| summarize make_set(SessionId)
);
base
| mv-expand todynamic(AuthenticationDetails)
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
// Get all signins related to a WHfB Session
| where SessionId in (whfb_sessions)
// Join the access token requests comming from a WHfB session with all the PRT tokens used in the past for each device
| join kind=inner prt_timeframes on DeviceID
| extend CurrentSessionID = SessionId, OtherSessionID = SessionId1, OtherSessionTimeMin = TimeMin, OtherSessionTimeMax = TimeMax, DeviceName = tostring(DeviceDetail.displayName)
// Get logins where the current SessionID is not the same as another one
| where CurrentSessionID != OtherSessionID
// Check if the new Session ID is seen while other Session IDs are still active (only check first login of the current Session ID)
| summarize arg_min(AuthenticationDateTime, *) by DeviceID, CurrentSessionID
| where AuthenticationDateTime between (OtherSessionTimeMin .. OtherSessionTimeMax)
// Exclude Windows Sign In as application login since attackers will use the PRT to request access tokens for other applications (they do not need to signin into Windows anymore)
| where AppDisplayName != "Windows Sign In"
| project AuthenticationDateTime, UserPrincipalName, DeviceID, DeviceName, CurrentSessionID, OtherSessionID, OtherSessionTimeMin, OtherSessionTimeMax, AppDisplayName, ResourceDisplayName
// Get the Sign-in logs we want to query
let base = materialize(
SigninLogs
| where TimeGenerated > ago(1d)
);
// Get all the WHfB signins by looking at the authentication method and incomming token
let whfb = (
base
// Get WHfB signins
| mv-expand todynamic(AuthenticationDetails)
| where AuthenticationDetails.authenticationMethod == "Windows Hello for Business"
| where IncomingTokenType == "primaryRefreshToken"
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
// Remove empty Session and Device IDs
| where SessionId != "" and DeviceID != ""
);
// Save the time frame for each WHfB PRT token
// We use the SessionID to identify a specific PRT token since the SessionID changes when a new refresh token is being used
let prt_timeframes = (
whfb
// Summarize the first and last PRT usage per device, by using the Session ID
| summarize TimeMin = arg_min(AuthenticationDateTime,*), TimeMax=arg_max(AuthenticationDateTime,*) by DeviceID, SessionId
| project DeviceID, SessionId, TimeMin, TimeMax
);
// Save all the Session IDs for the logins that came from a WHfB authentication method
let whfb_sessions = toscalar(
whfb
| summarize make_set(SessionId)
);
base
| mv-expand todynamic(AuthenticationDetails)
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
// Get all signins related to a WHfB Session
| where SessionId in (whfb_sessions)
// Join the access token requests comming from a WHfB session with all the PRT tokens used in the past for each device
| join kind=inner prt_timeframes on DeviceID
| extend CurrentSessionID = SessionId, OtherSessionID = SessionId1, OtherSessionTimeMin = TimeMin, OtherSessionTimeMax = TimeMax, DeviceName = tostring(DeviceDetail.displayName)
// Get logins where the current SessionID is not the same as another one
| where CurrentSessionID != OtherSessionID
// Check if the new Session ID is seen while other Session IDs are still active (only check first login of the current Session ID)
| summarize arg_min(AuthenticationDateTime, *) by DeviceID, CurrentSessionID
| where AuthenticationDateTime between (OtherSessionTimeMin .. OtherSessionTimeMax)
// Exclude Windows Sign In as application login since attackers will use the PRT to request access tokens for other applications (they do not need to signin into Windows anymore)
| where AppDisplayName != "Windows Sign In"
| project AuthenticationDateTime, UserPrincipalName, DeviceID, DeviceName, CurrentSessionID, OtherSessionID, OtherSessionTimeMin, OtherSessionTimeMax, AppDisplayName, ResourceDisplayName
Stages and Predicates
let base is inlined into the numbered stages below.
Let Binding: whfb
let whfb = (
base
| mv-expand todynamic(AuthenticationDetails)
| where AuthenticationDetails.authenticationMethod == "Windows Hello for Business"
| where IncomingTokenType == "primaryRefreshToken"
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
| where SessionId != "" and DeviceID != ""
);
Let Binding: prt_timeframes
let prt_timeframes = (
whfb
| summarize TimeMin = arg_min(AuthenticationDateTime,*), TimeMax=arg_max(AuthenticationDateTime,*) by DeviceID, SessionId
| project DeviceID, SessionId, TimeMin, TimeMax
);
Let Binding: whfb_sessions
let whfb_sessions = toscalar(
whfb
| summarize make_set(SessionId)
);
Stages 1 to 2 define let base as the rule's main pipeline source. Stages 3 to 12 use it.
Stage 1: source
SigninLogs
Stage 2: where
| where Timestamp > ago(1d)
Stage 3: mv-expand
base
| mv-expand todynamic(AuthenticationDetails)
Stage 4: extend
| extend DeviceID = tostring(DeviceDetail.deviceId), AuthenticationDateTime = todatetime(AuthenticationDetails.authenticationStepDateTime)
Stage 5: where
| where SessionId in (whfb_sessions)
Stage 6: join
| join kind=inner prt_timeframes on DeviceID
Stage 7: extend
extend CurrentSessionID, DeviceName, OtherSessionID, OtherSessionTimeMax, OtherSessionTimeMin
Stage 8: where
where CurrentSessionID != OtherSessionID
Stage 9: summarize
summarize by DeviceID, CurrentSessionID
Stage 10: where
where AuthenticationDateTime >= "OtherSessionTimeMin" and AuthenticationDateTime <= "OtherSessionTimeMax"
Stage 11: where
where AppDisplayName != "Windows Sign In"
Stage 12: project
project AppDisplayName, AuthenticationDateTime, CurrentSessionID, DeviceID, DeviceName, OtherSessionID, OtherSessionTimeMax, OtherSessionTimeMin, ResourceDisplayName, UserPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppDisplayName | ne |
| field:"Application" kind:ne value:"Windows Sign In" |
AuthenticationDateTime | ge |
| field:"AuthenticationDateTime" kind:ge value:"OtherSessionTimeMin" |
AuthenticationDateTime | le |
| field:"AuthenticationDateTime" kind:le value:"OtherSessionTimeMax" |
AuthenticationDetails.authenticationMethod | eq |
| field:"AuthenticationDetails.authenticationMethod" kind:eq value:"Windows Hello for Business" |
IncomingTokenType | eq |
| field:"IncomingTokenType" kind:eq value:"primaryRefreshToken" |
SessionId | in |
| field:"SessionId" kind:in value:"whfb_sessions" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
AuthenticationDateTime | project |
CurrentSessionID | project |
DeviceID | project |
DeviceName | project |
OtherSessionID | project |
OtherSessionTimeMax | project |
OtherSessionTimeMin | project |
ResourceDisplayName | project |
UserPrincipalName | project |
DomainTools Newly Added Domains Feeds Ingested
#Creates an incident whenever DomainTools Newly Added Domains new Feeds (domains) are ingested in the last 1 hour.
Rule Body
id: f8c223c1-1a28-4b28-8e9f-5e34b610916b
name: DomainTools Newly Added Domains Feeds Ingested
description: Creates an incident whenever DomainTools Newly Added Domains 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,\"nad\")"
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DomainCustomEntity
eventGroupingSettings:
aggregationKind: SingleAlert
version: 1.0.0
Stages and Predicates
Stage 1: source
DomainToolsThreatIntelDomains(1h,"nad")
Failed logon attempts in authpriv
#'Identifies failed logon attempts from unknown users in Syslog authpriv logs. The unknown user means the account that tried to log in isn't provisioned on the machine. A few hits could indicate someone attempting to access a machine they aren't authorized to access. If there are many of hits, especially from outside your network, it could indicate a brute force attack. Default threshold for logon attempts is 15.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: e7ec9fa6-e7f7-41ed-a34b-b956837a3ee6
name: Failed logon attempts in authpriv
description: |
'Identifies failed logon attempts from unknown users in Syslog authpriv logs. The unknown user means the account that tried to log in isn't provisioned on the machine. A few hits could indicate someone attempting to access a machine they aren't authorized to access.
If there are many of hits, especially from outside your network, it could indicate a brute force attack.
Default threshold for logon attempts is 15.'
severity: Medium
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 15;
// Below pulls messages from syslog-authpriv logs where there was an authentication failure with an unknown user.
// IP address of system attempting logon is also extracted from the SyslogMessage field. Some of these messages
// are aggregated.
Syslog
| where Facility =~ "authpriv"
| where SyslogMessage has "authentication failure" and SyslogMessage has " uid=0"
| extend RemoteIP = extract(@".*?rhost=([\d.]+).*?", 1,SyslogMessage)
| project TimeGenerated, Computer, ProcessName, HostIP, RemoteIP, ProcessID
| join kind=innerunique (
// Below pulls messages from syslog-authpriv logs that show each instance an unknown user tried to logon.
Syslog
| where Facility =~ "authpriv"
| where SyslogMessage has "user unknown"
| project Computer, HostIP, ProcessID
) on Computer, HostIP, ProcessID
// Count the number of failed logon attempts by External IP and internal machine
| summarize FirstLogonAttempt = min(TimeGenerated), LatestLogonAttempt = max(TimeGenerated), TotalLogonAttempts = count() by Computer, HostIP, RemoteIP
// Calculate the time between first and last logon attempt (AttemptPeriodLength)
| extend TimeBetweenLogonAttempts = LatestLogonAttempt - FirstLogonAttempt
| where TotalLogonAttempts >= threshold
| project FirstLogonAttempt, LatestLogonAttempt, TimeBetweenLogonAttempts, TotalLogonAttempts, SourceAddress = RemoteIP, Computer, HostIP
| sort by Computer asc nulls last
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 15;
Stage 1: source
Syslog
Stage 2: where
| where Facility =~ "authpriv"
Stage 3: where
| where SyslogMessage has "authentication failure" and SyslogMessage has " uid=0"
Stage 4: extend
| extend RemoteIP = extract(@".*?rhost=([\d.]+).*?", 1,SyslogMessage)
Stage 5: project
| project TimeGenerated, Computer, ProcessName, HostIP, RemoteIP, ProcessID
Stage 6: join
| join kind=innerunique (
Syslog
| where Facility =~ "authpriv"
| where SyslogMessage has "user unknown"
| project Computer, HostIP, ProcessID
) on Computer, HostIP, ProcessID
Stage 7: summarize
summarize FirstLogonAttempt, LatestLogonAttempt, TotalLogonAttempts by Computer, HostIP, RemoteIP
Stage 8: extend
extend TimeBetweenLogonAttempts
Stage 9: where
where TotalLogonAttempts >= 15
Stage 10: project
project Computer, FirstLogonAttempt, HostIP, LatestLogonAttempt, SourceAddress, TimeBetweenLogonAttempts, TotalLogonAttempts
Stage 11: sort
sort by Computer
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Facility | eq |
| field:"Facility" kind:eq value:"authpriv" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
TotalLogonAttempts | ge |
| field:"TotalLogonAttempts" kind:ge value:"15" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
FirstLogonAttempt | project |
HostIP | project |
LatestLogonAttempt | project |
SourceAddress | project |
TimeBetweenLogonAttempts | project |
TotalLogonAttempts | project |
Guardian- Ban Topic Policy Violation Detection
#'This alert creates an incident when Ban Topic Policy Violation detected from the Guardian.'
Rule Body
id: 97a76b2d-02ea-4ae5-b0c4-79e834036481
name: Guardian- Ban Topic Policy Violation Detection
description: |
'This alert creates an incident when Ban Topic Policy Violation detected from the Guardian.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Ban Topic'
| where Severity =~ 'Medium'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Ban Topic Policy Violation detection
alertDescriptionFormat: |
This query detects Ban Topic Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Ban Topic'
Stage 3: where
| where Severity =~ 'Medium'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Ban Topic" |
Severity | eq |
| field:"Severity" kind:eq value:"Medium" |
Hunt for critical credentials on devices with non-critical accounts
#In most organizations normal user accounts or accounts with low risk permissions have less security controls enabled. This because there are less security controls needed in order to minize the risk vectors that come with these accounts. If these accounts are used on devices where critical account credentials are also present, the critical user account can be compromised more easily when the device is accessed by an adversary via the non-critical user account. Because of this, a Privileged Access Workstation should be used which serves as a dedicated workstation for the critical accounts. By doing this, the critical user account cannot be compromised via a unhardened device.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | ExposureGraphEdges action any: Exposure graph edges |
Rule Body
// Search for all users and save their criticality level
let xspm_users = materialize(
ExposureGraphNodes
| where NodeLabel == "user"
| extend CriticalityLevel = todynamic(NodeProperties).rawData.criticalityLevel.criticalityLevel
| extend RuleNames = todynamic(NodeProperties).rawData.criticalityLevel.ruleNames
| distinct NodeName, NodeId, tostring(CriticalityLevel), tostring(RuleNames)
);
// Make a list of all critical users
let critical_users = toscalar(
xspm_users
| where CriticalityLevel == 0
| summarize make_set(NodeName)
);
// Make a list of all non critical users
let non_critical_users = toscalar(
xspm_users
| where CriticalityLevel != 0
| summarize make_set(NodeName)
);
// Make graph for max of 3 edges, where we start from a device and end with an user
ExposureGraphEdges
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
where SourceNode.NodeLabel in ("device", "microsoft.compute/virtualmachines") and TargetNode.NodeLabel == "user"
project SourceNodeName = SourceNode.NodeName,
Edges = anyEdge.EdgeLabel,
TargetNodeName = TargetNode.NodeName,
TargetNodeLabel = TargetNode.NodeLabel
// Make a list of all users a device has credentials for
| summarize UserList = make_set(TargetNodeName) by SourceNodeName
// Only return devices with more than one credential
| where array_length(UserList) > 1
// Make new lists saving the critical users and non critical users per device
| extend CriticalUserList = set_intersect(UserList, critical_users),
NonCriticalUserList = set_intersect(UserList, non_critical_users)
// Flag when a device has both critical and non critical users
| where array_length(CriticalUserList) > 0 and array_length(NonCriticalUserList) > 0
Stages and Predicates
Let Binding: xspm_users
let xspm_users = materialize(
ExposureGraphNodes
| where NodeLabel == "user"
| extend CriticalityLevel = todynamic(NodeProperties).rawData.criticalityLevel.criticalityLevel
| extend RuleNames = todynamic(NodeProperties).rawData.criticalityLevel.ruleNames
| distinct NodeName, NodeId, tostring(CriticalityLevel), tostring(RuleNames)
);
Let Binding: critical_users
let critical_users = toscalar(
xspm_users
| where CriticalityLevel == 0
| summarize make_set(NodeName)
);
Let Binding: non_critical_users
let non_critical_users = toscalar(
xspm_users
| where CriticalityLevel != 0
| summarize make_set(NodeName)
);
Stage 1: source
ExposureGraphEdges
Stage 2: macro
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
Stage 3: macro
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
where SourceNode.NodeLabel in ("device", "microsoft.compute/virtualmachines") and TargetNode.NodeLabel == "user"
project SourceNodeName = SourceNode.NodeName,
Edges = anyEdge.EdgeLabel,
TargetNodeName = TargetNode.NodeName,
TargetNodeLabel = TargetNode.NodeLabel
Stage 4: summarize
| summarize UserList = make_set(TargetNodeName) by SourceNodeName
Stage 5: where
| where array_length(UserList) > 1
Stage 6: extend
| extend CriticalUserList = set_intersect(UserList, critical_users),
NonCriticalUserList = set_intersect(UserList, non_critical_users)
Stage 7: where
| where array_length(CriticalUserList) > 0 and array_length(NonCriticalUserList) > 0
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceNodeName | summarize |
UserList | summarize |
CriticalUserList | extend |
NonCriticalUserList | extend |
Identify SysAid Server web shell creation
#This query looks for potential webshell creation by the threat actor Mercury after the sucessful exploitation of SysAid server. Reference: https://www.microsoft.com/security/blog/2022/08/25/mercury-leveraging-log4j-2-vulnerabilities-in-unpatched-systems-to-target-israeli-organizations/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: 50eb4cbd-188f-44f4-b964-bab84dcdec10
name: Identify SysAid Server web shell creation
description: |
'This query looks for potential webshell creation by the threat actor Mercury after the sucessful exploitation of SysAid server.
Reference: https://www.microsoft.com/security/blog/2022/08/25/mercury-leveraging-log4j-2-vulnerabilities-in-unpatched-systems-to-target-israeli-organizations/'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
tags:
- Mercury
- Schema: ASIMFileEvent
SchemaVersion: 0.1.0
query: |
let timeframe = 1d;
let time_window = 5m;
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688
| where Process has_any ("java.exe", "javaw.exe") and CommandLine has "SysAidServer"
| summarize by ParentProcessName,Process, Account, Computer, CommandLine, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663
| where Process has_any ("java.exe", "javaw.exe")
| where AccessMask in ('0x2','0x100', '0x10', '0x4')
| where ObjectName endswith ".jsp"
| summarize by ParentProcessName, Account, Computer, ObjectName, ProcessName, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId)
on timekey, Computer, SubjectLogonId
),
(DeviceFileEvents
| where InitiatingProcessFileName has_any ("java.exe", "javaw.exe")
| where InitiatingProcessCommandLine has "SysAidServer"
| where FileName endswith ".jsp"
| extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName), Computer = DeviceName
),
(imFileEvent
| where TimeGenerated > ago(timeframe)
| where EventType == "FileCreated"
| where ActingProcessName has_any ("java.exe", "javaw.exe")
| where ActingProcessCommandLine has "SysAidServer"
| where FilePath endswith ".jsp"
| extend Account = ActorUsername, Computer = DvcHostname
)
)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
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.3
kind: Scheduled
metadata:
source:
kind: Scheduled
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let timeframe = 1d;
let time_window = 5m;
Union isfuzzy=true (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, DeviceFileEvents, imFileEvent
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688
| where Process has_any ("java.exe", "javaw.exe") and CommandLine has "SysAidServer"
| summarize by ParentProcessName,Process, Account, Computer, CommandLine, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663
| where Process has_any ("java.exe", "javaw.exe")
| where AccessMask in ('0x2','0x100', '0x10', '0x4')
| where ObjectName endswith ".jsp"
| summarize by ParentProcessName, Account, Computer, ObjectName, ProcessName, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId)
on timekey, Computer, SubjectLogonId
Leg 2: DeviceFileEvents
DeviceFileEvents
| where InitiatingProcessFileName has_any ("java.exe", "javaw.exe")
| where InitiatingProcessCommandLine has "SysAidServer"
| where FileName endswith ".jsp"
| extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName), Computer = DeviceName
Leg 3: imFileEvent
imFileEvent
| where TimeGenerated > ago(timeframe)
| where EventType == "FileCreated"
| where ActingProcessName has_any ("java.exe", "javaw.exe")
| where ActingProcessCommandLine has "SysAidServer"
| where FilePath endswith ".jsp"
| extend Account = ActorUsername, Computer = DvcHostname
Applied to the Combined Result
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccessMask | in |
| field:"AccessMask" kind:in |
ActingProcessCommandLine | match |
| field:"ParentCommandLine" kind:match value:"SysAidServer" |
ActingProcessName | match |
| field:"ParentImage" kind:match |
CommandLine | match |
| field:"CommandLine" kind:match value:"SysAidServer" |
EventID | eq |
| field:"EventID" kind:eq |
EventType | eq |
| field:"OperationName" kind:eq value:"FileCreated" |
FileName | ends_with |
| field:"file_name" kind:ends_with value:".jsp" |
FilePath | ends_with |
| field:"FilePath" kind:ends_with value:".jsp" |
InitiatingProcessCommandLine | match |
| field:"CommandLine" kind:match value:"SysAidServer" |
InitiatingProcessFileName | match |
| field:"process_basename" kind:match |
ObjectName | ends_with |
| field:"ObjectName" kind:ends_with value:".jsp" |
Process | match |
| field:"Process" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
Computer | summarize |
ObjectName | summarize |
ParentProcessName | summarize |
ProcessName | summarize |
SubjectLogonId | summarize |
TimeGenerated | summarize |
timekey | summarize |
Infoblox - IQ for TD Detected Insights - API Source
#'Infoblox IQ for TD Insight detected in logs sourced via REST API. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxInsight.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: a5e2df87-f0c9-4540-8715-96e71b608986
name: Infoblox - IQ for TD Detected Insights - API Source
description: |
'Infoblox IQ for TD Insight detected in logs sourced via REST API. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxInsight**](https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Infoblox/Parsers/InfobloxInsight.yaml).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: InfobloxSOCInsightsDataConnector_API
dataTypes:
- InfobloxInsight
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1498
- T1565
query: |
InfobloxInsight
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
| where DataObservedVia != "CDC" and InsightDetail != true
| extend ThreatProperties = strcat_array(ThreatProperties, ", ")
entityMappings:
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: Name
- identifier: Category
columnName: ThreatProperties
customDetails:
InfobloxInsightID: InfobloxInsightID
Severity: Priority
Status: Status
FirstSeen: FirstSeen
EvaluationStartDate: EvaluationStartDate
EvaluationEndDate: EvaluationEndDate
TotalHits: EventsCount
TotalAssets: TotalAssets
TotalIndicators: TotalIndicators
ThreatActor: ThreatActor
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Infoblox - IQ for TD Insight - {{Name}}'
alertDescriptionFormat: 'Observed via API. {{Description}} Observation: {{FirstSeen}}'
alertSeverityColumnName: IncidentSeverity
incidentConfiguration:
createIncident: true
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
InfobloxInsight
Stage 2: summarize
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
Stage 3: where
| where DataObservedVia != "CDC" and InsightDetail != true
Stage 4: extend
| extend ThreatProperties = strcat_array(ThreatProperties, ", ")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataObservedVia | ne |
| field:"DataObservedVia" kind:ne value:"CDC" |
InsightDetail | ne |
| field:"InsightDetail" kind:ne value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InfobloxInsightID | summarize |
ThreatProperties | extend |
Jira - User removed from project
#'Detects when a user was removed from project.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 5d3af0aa-833e-48ed-a29a-8cfd2705c953
name: Jira - User removed from project
description: |
'Detects when a user was removed from project.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: JiraAuditAPI
dataTypes:
- JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1531
query: |
JiraAudit
| where EventMessage =~ 'User removed from project'
| extend user = todynamic(AssociatedItems)[0]['name']
| project EventCreationTime, ObjectItemName, user
| extend AccountCustomEntity = user
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
JiraAudit
Stage 2: where
| where EventMessage =~ 'User removed from project'
Stage 3: extend
| extend user = todynamic(AssociatedItems)[0]['name']
Stage 4: project
| project EventCreationTime, ObjectItemName, user
Stage 5: extend
| extend AccountCustomEntity = user
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"User removed from project" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCreationTime | project |
ObjectItemName | project |
user | project |
AccountCustomEntity | extend |
M2131_EventLogManagementPostureChanged_EL0
#'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL0 policy compliance falls below 70% within a 1 week timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 1f8fcca5-47ed-409d-a8fa-d49ef821feaf
name: M2131_EventLogManagementPostureChanged_EL0
description: |
'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL0 policy compliance falls below 70% within a 1 week timeframe.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
SecurityRecommendation
| where RecommendationDisplayName <> ""
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
| extend PassedControls = (Passed/todouble(Total))*100
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
| where MaturityLevel <> ''
| where MaturityLevel == "Event Logging (EL0)"
| where PassedControls < 70
//Adjust Either FailedRatePercentage or PasedRatePercentage Thresholds within Organizational Needs
| sort by PassedControls desc
| extend URLCustomEntity = RemediationLink
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityRecommendation
Stage 2: where
| where RecommendationDisplayName <> ""
Stage 3: extend
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
MaturityLevel =(RecommendationDisplayName has "agent" or RecommendationDisplayName has "extension" or RecommendationDisplayName has "retention" or RecommendationDisplayName has "logs encryption" or RecommendationDisplayName has "collect" or RecommendationDisplayName has "retained" or RecommendationDisplayName has "log profile" or RecommendationDisplayName has "CloudTrail" or RecommendationDisplayName has "metric" or RecommendationDisplayName has "AWS" or RecommendationDisplayName has "GCP" or RecommendationDisplayName has "DNS" or RecommendationDisplayName has "Auditing" or RecommendationDisplayName has "Flow" or RecommendationDisplayName has "logging" or RecommendationDisplayName has "usage")"Event Logging (EL0)"iff((RecommendationDisplayName has_any ("container registries", "logic apps", "Enhanced monitoring", "IoT Hub", "Event Hub", "App Service", "Kubernetes", "updates", "email", "automation", "adaptive")), "Advanced Event Logging (EL3)", iff((RecommendationDisplayName has_any ("signatures", "CMK", "CMEK", "double encryption", "managed key", "KMS", "container", "Watcher")), "Intermediate Event Logging (EL2)", iff((RecommendationDisplayName has_any ("Exploit Guard", "endpoint protection", "Antimalware", "health", "VPC", "Defender", "Vulnerabilities", "vulnerability", "diagnostic", "Key", "activity log alert", "")), "Basic Event Logging (EL1)", "Other")))Stage 4: summarize
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
Stage 5: summarize
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
Stage 6: extend
| extend PassedControls = (Passed/todouble(Total))*100
Stage 7: extend
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
Stage 8: project
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
Stage 9: where
| where MaturityLevel <> ''
Stage 10: where
| where MaturityLevel == "Event Logging (EL0)"
Stage 11: where
| where PassedControls < 70
Stage 12: sort
| sort by PassedControls desc
Stage 13: extend
| extend URLCustomEntity = RemediationLink
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MaturityLevel | eq |
| field:"MaturityLevel" kind:eq value:"Event Logging (EL0)" |
PassedControls | lt |
| field:"PassedControls" kind:lt value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Failed | project |
LastObserved | project |
MaturityLevel | project |
Passed | project |
PassedControls | project |
RemediationLink | project |
Total | project |
URLCustomEntity | extend |
Mass Download & copy to USB device by single user
#This query looks for any mass download by a single user with possible file copy activity to a new USB drive. Malicious insiders may perform such activities that may cause harm to the organization. This query could also reveal unintentional insider that had no intention of malicious activity but their actions may impact an organizations security posture. Reference:https://docs.microsoft.com/defender-cloud-apps/policy-template-reference
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
Rule Body
id: 6267ce44-1e9d-471b-9f1e-ae76a6b7aa84
name: Mass Download & copy to USB device by single user
description: |
'This query looks for any mass download by a single user with possible file copy activity to a new USB drive. Malicious insiders may perform such activities that may cause harm to the organization.
This query could also reveal unintentional insider that had no intention of malicious activity but their actions may impact an organizations security posture.
Reference:https://docs.microsoft.com/defender-cloud-apps/policy-template-reference'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftCloudAppSecurity
dataTypes:
- SecurityAlert
- connectorId: MicrosoftThreatProtection
dataTypes:
- CloudAppEvents
- DeviceEvents
- DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1052
query: |
let Alerts = SecurityAlert
| where AlertName =~ "mass download by a single user"
| where Status != 'Resolved'
| extend ipEnt = parse_json(Entities), accountEnt = parse_json(Entities)
| mv-apply tempParams = ipEnt on (
mv-expand ipEnt
| where ipEnt.Type == "ip"
| extend IpAddress = tostring(ipEnt.Address)
)
| mv-apply tempParams = accountEnt on (
mv-expand accountEnt
| where accountEnt.Type == "account"
| extend AADUserId = tostring(accountEnt.AadUserId)
)
| extend Alert_TimeGenerated = TimeGenerated
| distinct Alert_TimeGenerated, IpAddress, AADUserId, DisplayName, Description, ProductName, ExtendedProperties, Entities, Status, CompromisedEntity
;
let CA_Events = CloudAppEvents
| where ActionType == "FileDownloaded"
| extend parsed = parse_json(RawEventData)
| extend UserId = tostring(parsed.UserId)
| extend FileName = tostring(parsed.SourceFileName)
| extend FileExtension = tostring(parsed.SourceFileExtension)
| summarize CloudAppEvent_StartTime = min(TimeGenerated), CloudAppEvent_EndTime = max(TimeGenerated), CloudAppEvent_Files = make_set(FileName), FileCount = dcount(FileName) by Application, AccountObjectId, UserId, IPAddress, City, CountryCode
| extend CloudAppEvents_Details = pack_all();
let CA_Alerts_Events = Alerts | join kind=inner (CA_Events)
on $left.AADUserId == $right.AccountObjectId and $left.IpAddress == $right.IPAddress
// Cloud app event comes before Alert
| where CloudAppEvent_EndTime <= Alert_TimeGenerated
| project Alert_TimeGenerated, UserId, AADUserId, IPAddress, CloudAppEvents_Details, CloudAppEvent_Files
;
// setup list to filter DeviceFileEvents for only files downloaded as indicated by CloudAppEvents
let CA_FileList = CA_Alerts_Events | project CloudAppEvent_Files;
CA_Alerts_Events
| join kind=inner ( DeviceFileEvents
| where ActionType in ("FileCreated", "FileRenamed")
| where FileName in~ (CA_FileList)
| summarize DeviceFileEvent_StartTime = min(TimeGenerated), DeviceFileEvent_EndTime = max(TimeGenerated), DeviceFileEvent_Files = make_set(FolderPath), DeviceFileEvent_FileCount = dcount(FolderPath) by InitiatingProcessAccountUpn, DeviceId, DeviceName, InitiatingProcessFolderPath, InitiatingProcessParentFileName//, InitiatingProcessCommandLine
| extend DeviceFileEvents_Details = pack_all()
) on $left.UserId == $right.InitiatingProcessAccountUpn
| where DeviceFileEvent_StartTime >= Alert_TimeGenerated
| join kind=inner (
// get device events where a USB drive was mounted
DeviceEvents
| where ActionType == "UsbDriveMounted"
| extend parsed = parse_json(AdditionalFields)
| extend USB_DriveLetter = tostring(AdditionalFields.DriveLetter), USB_ProductName = tostring(AdditionalFields.ProductName), USB_Volume = tostring(AdditionalFields.Volume)
| where isnotempty(USB_DriveLetter)
| project USB_TimeGenerated = TimeGenerated, DeviceId, USB_DriveLetter, USB_ProductName, USB_Volume
| extend USB_Details = pack_all()
)
on DeviceId
// USB event occurs after the Alert
| where USB_TimeGenerated >= Alert_TimeGenerated
| mv-expand DeviceFileEvent_Files
| extend DeviceFileEvent_Files = tostring(DeviceFileEvent_Files)
// make sure that we only pickup the files that have the USB drive letter
| where DeviceFileEvent_Files startswith USB_DriveLetter
| summarize USB_Drive_MatchedFiles = make_set_if(DeviceFileEvent_Files, DeviceFileEvent_Files startswith USB_DriveLetter) by Alert_TimeGenerated, USB_TimeGenerated, UserId, AADUserId, DeviceId, DeviceName, IPAddress, CloudAppEvents_Details = tostring(CloudAppEvents_Details), DeviceFileEvents_Details = tostring(DeviceFileEvents_Details), USB_Details = tostring(USB_Details)
| extend InitiatingProcessFileName = tostring(split(todynamic(DeviceFileEvents_Details).InitiatingProcessFolderPath, "\\")[-1]), InitiatingProcessFolderPath = tostring(todynamic(DeviceFileEvents_Details).InitiatingProcessFolderPath)
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DeviceName != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserId
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: AADUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: File
fieldMappings:
- identifier: Name
columnName: InitiatingProcessFileName
- identifier: Directory
columnName: InitiatingProcessFolderPath
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
let Alerts and let CA_Alerts_Events are inlined into the numbered stages below.
Let Binding: CA_Events
let CA_Events = CloudAppEvents
| where ActionType == "FileDownloaded"
| extend parsed = parse_json(RawEventData)
| extend UserId = tostring(parsed.UserId)
| extend FileName = tostring(parsed.SourceFileName)
| extend FileExtension = tostring(parsed.SourceFileExtension)
| summarize CloudAppEvent_StartTime = min(TimeGenerated), CloudAppEvent_EndTime = max(TimeGenerated), CloudAppEvent_Files = make_set(FileName), FileCount = dcount(FileName) by Application, AccountObjectId, UserId, IPAddress, City, CountryCode
| extend CloudAppEvents_Details = pack_all();
Let Binding: CA_FileList
let CA_FileList = CA_Alerts_Events | project CloudAppEvent_Files;
Stage 1: source
let CA_Events
Stage 2: source
let CA_Alerts_Events
Stage 3: source
let CA_FileList
Stage 4: source
SecurityAlert
Stage 5: where
where AlertName =~ "mass download by a single user"
Stage 6: where
where Status != "Resolved"
Stage 7: extend
extend accountEnt, ipEnt
Stage 8: kusto:mv-apply
kusto:mv-apply ipEnt.Type == "ip"
Stage 9: kusto:mv-apply
kusto:mv-apply accountEnt.Type == "account"
Stage 10: extend
extend Alert_TimeGenerated
Stage 11: distinct
distinct AADUserId, Alert_TimeGenerated, CompromisedEntity, Description, DisplayName, Entities, ExtendedProperties, IpAddress, ProductName, Status
Stage 12: join
join kind=inner (CA_Events)
Stage 13: where
where CloudAppEvent_EndTime <= Alert_TimeGenerated
Stage 14: project
project AADUserId, Alert_TimeGenerated, CloudAppEvent_Files, CloudAppEvents_Details, IPAddress, UserId
Stage 15: join
join kind=inner (DeviceFileEvents) on $left.UserId, $right.InitiatingProcessAccountUpn
Stage 16: where
where DeviceFileEvent_StartTime >= Alert_TimeGenerated
Stage 17: join
join kind=inner (DeviceEvents) on DeviceId
Stage 18: where
where USB_TimeGenerated >= Alert_TimeGenerated
Stage 19: mv-expand
mv-expand DeviceFileEvent_Files
Stage 20: extend
extend DeviceFileEvent_Files
Stage 21: where
where DeviceFileEvent_Files startswith "USB_DriveLetter"
Stage 22: summarize
summarize CloudAppEvents_Details, DeviceFileEvents_Details, USB_Details, USB_Drive_MatchedFiles by Alert_TimeGenerated, USB_TimeGenerated, UserId, AADUserId, DeviceId, DeviceName, IPAddress, CloudAppEvents_Details, DeviceFileEvents_Details, USB_Details
Stage 23: extend (4 consecutive steps)
extend AccountName, AccountUPNSuffix, DomainIndex, HostName, HostNameDomain, InitiatingProcessFileName, InitiatingProcessFolderPath
Stage 24: project-away
project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq |
ActionType | in |
| field:"ActionType" kind:in |
AlertName | eq |
| field:"AlertName" kind:eq value:"mass download by a single user" |
DeviceFileEvent_Files | starts_with |
| field:"DeviceFileEvent_Files" kind:starts_with value:"USB_DriveLetter" |
FileName | in |
| field:"FileName" kind:in value:"CA_FileList" |
Status | ne |
| field:"Status" kind:ne value:"Resolved" |
USB_DriveLetter | is_not_null | field:"USB_DriveLetter" kind:is_not_null | |
accountEnt.Type | eq |
| field:"accountEnt.Type" kind:eq value:"account" |
ipEnt.Type | eq |
| field:"ipEnt.Type" kind:eq value:"ip" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AADUserId | summarize |
Alert_TimeGenerated | summarize |
CloudAppEvents_Details | summarize |
DeviceFileEvents_Details | summarize |
DeviceId | summarize |
DeviceName | summarize |
IPAddress | summarize |
USB_Details | summarize |
USB_Drive_MatchedFiles | summarize |
USB_TimeGenerated | summarize |
UserId | summarize |
InitiatingProcessFileName | extend |
InitiatingProcessFolderPath | extend |
HostName | extend |
HostNameDomain | extend |
AccountName | extend |
AccountUPNSuffix | extend |
McAfee ePO - Logging error occurred
#Detects when logging errors on agent.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Telemetry Coverage
Rule Body
id: 0c9243d6-d2ec-48e1-8593-e713859c8f3c
name: McAfee ePO - Logging error occurred
description: |
'Detects when logging errors on agent.'
severity: Medium
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
- T1070
query: |
McAfeeEPOEvent
| where EventId in ('1040', '1076', '3032', '3033', '3034', '3036', '3038')
| extend EventMessage = case(EventId == '1040', 'Activity Log error',
EventId == '1076', 'Error logging information',
EventId == '3032', 'Error while trying to open/create activity log file',
EventId == '3033', 'Activity log file maximum size reached',
EventId == '3034', 'Unable to write the activity log file',
EventId == '3036', 'Error during initialization of the activity log file',
'Error writing to log')
| project DvcIpAddr, EventId, EventMessage
| extend IPCustomEntity = DvcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
McAfeeEPOEvent
Stage 2: where
| where EventId in ('1040', '1076', '3032', '3033', '3034', '3036', '3038')
Stage 3: extend
| extend EventMessage = case(EventId == '1040', 'Activity Log error',
EventId == '1076', 'Error logging information',
EventId == '3032', 'Error while trying to open/create activity log file',
EventId == '3033', 'Activity log file maximum size reached',
EventId == '3034', 'Unable to write the activity log file',
EventId == '3036', 'Error during initialization of the activity log file',
'Error writing to log')
EventMessage =EventId == 1040'Activity Log error'EventId == 1076'Error logging information'EventId == 3032'Error while trying to open/create activity log file'EventId == 3033'Activity log file maximum size reached'EventId == 3034'Unable to write the activity log file'EventId == 3036'Error during initialization of the activity log file''Error writing to log'Stage 4: project
| project DvcIpAddr, EventId, EventMessage
Stage 5: extend
| extend IPCustomEntity = DvcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventId | in |
| field:"EventId" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DvcIpAddr | project |
EventId | project |
EventMessage | project |
IPCustomEntity | extend |
Mimecast Secure Email Gateway - Spam Event Thread
#'Detects threat from spam event thread protection logs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 0cda82c8-e8f0-4117-896e-a10f1b43e64a
name: Mimecast Secure Email Gateway - Spam Event Thread
description: |
'Detects threat from spam event thread protection logs.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: MimecastSEGAPI
dataTypes:
- MimecastCG
enabled: true
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1083
query: |
MimecastCG
| where Type == "email_spam"
| extend SenderEnvelope = ['Sender Envelope']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: SenderEnvelope
- identifier: Recipient
columnName: Recipients
- identifier: Subject
columnName: Subject
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastCG
Stage 2: where
| where Type == "email_spam"
Stage 3: extend
| extend SenderEnvelope = ['Sender Envelope']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Type | eq |
| field:"Type" kind:eq value:"email_spam" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SenderEnvelope | extend |
Mimecast Secure Email Gateway - Spam Event Thread
#Detects threat from spam event thread protection logs
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: df1b9377-5c29-4928-872f-9934a6b4f611
name: Mimecast Secure Email Gateway - Spam Event Thread
description: Detects threat from spam event thread protection logs
severity: Low
requiredDataConnectors:
- connectorId: MimecastSIEMAPI
dataTypes:
- MimecastSIEM_CL
enabled: true
query: MimecastSIEM_CL| where mimecastEventId_s=="mail_spameventthread"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Discovery
relevantTechniques:
- T1083
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
customDetails:
MsgId_s: MsgId_s
headerFrom: headerFrom_s
Route: Route_s
SourceIP: SourceIP
SenderDomain: SenderDomain_s
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: Sender_s
- identifier: Recipient
columnName: Recipient_s
- identifier: Subject
columnName: Subject_s
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastSIEM_CL
Stage 2: where
| where mimecastEventId_s=="mail_spameventthread"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
mimecastEventId_s | eq |
| field:"mimecastEventId_s" kind:eq value:"mail_spameventthread" |
Mimecast Targeted Threat Protection - Attachment Protect
#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 a threat for an unsafe attachment in an email
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: aa75944c-a663-4901-969e-7b55bfa49a73
name: Mimecast Targeted Threat Protection - Attachment Protect
description: Detects a threat for an unsafe attachment in an email
severity: High
requiredDataConnectors:
- connectorId: MimecastTTPAPI
dataTypes:
- MimecastTTPAttachment_CL
enabled: true
query: MimecastTTPAttachment_CL| where result_s <> "safe";
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- InitialAccess
- Discovery
relevantTechniques:
- T0865
alertRuleTemplateName:
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
fileHash: fileHash_s
fileName: fileName_s
fileType: fileType_s
details: details_s
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: senderAddress_s
- identifier: Recipient
columnName: recipientAddress_s
- identifier: Subject
columnName: subject_s
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastTTPAttachment_CL
Stage 2: where
| where result_s <> "safe";
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
result_s | ne |
| field:"result_s" kind:ne value:"safe" |
NTLM Relay Attack
#This query searches for successful NTLM network logins where the device name contained in the NTLM authentication message contains a device that is known to MDE, but the source IP address is different from the known source IP address for that specific device. This could indicate an attacker is relaying the NTLM authentication information. To remove false positives, this query also searches for an outgoing network connection from the initiator to the attacker.
Known False Positives
- None expected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
References
Telemetry Coverage
Rule Body
let timeframe = 2*1d;
// Extract a list of known local IPs per device. Note that the DeviceNetworkEvents table is used for this, since this is faster than the
// DeviceNetworkInfo table where IP addresses are stored inside a JSON structure that requires additional parsing.
let DeviceIPs=(
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ConnectionAttempt" or ActionType == "ConnectionSuccess"
| distinct DeviceName, LocalIP
| extend DeviceName=tolower(split(DeviceName,".")[0])
);
// Find potential NTLM relay attack by looking for NTLM logins from devices that are known in MDE, but are from a source IP that does not match any known IP addresses for the device.
let PotentialNTLMRelayLogins=materialize (
DeviceLogonEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "LogonSuccess"
| where LogonType == "Network"
| where Protocol=="NTLM"
| where isnotempty(RemoteDeviceName) and isnotempty(RemoteIP)
| where RemoteIPType <> "Loopback"
| extend RemoteDeviceName=tolower(RemoteDeviceName)
| where RemoteDeviceName in ((DeviceIPs | project DeviceName)) // The remote device is known in MDE.
| join kind=leftanti DeviceIPs on $left.RemoteIP == $right.LocalIP, $left.RemoteDeviceName == $right.DeviceName // The Remote IP does not match any known IP for the device.
| project-reorder Timestamp, RemoteIP, RemoteDeviceName, AccountDomain, AccountName
);
// Filter the potential NTLM relay events by checking there was an outgoing SMB connection from the source device to the relay IP address.
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where RemotePort in (445, 80, 9389)
| where RemoteIP in ((PotentialNTLMRelayLogins | project RemoteIP))
| extend ShortDeviceName=tolower(split(DeviceName,".")[0])
| where ShortDeviceName in ((PotentialNTLMRelayLogins | project RemoteDeviceName))
| lookup kind=inner PotentialNTLMRelayLogins on $left.ShortDeviceName == $right.RemoteDeviceName, $left.RemoteIP == $right.RemoteIP
| extend HostName=tostring(split(DeviceName,".")[0]),DnsDomain=iif(DeviceName contains ".", substring(DeviceName, indexof(DeviceName, ".") + 1, strlen(DeviceName)),"")
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1d;
Let Binding: DeviceIPs
let DeviceIPs = (
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ConnectionAttempt" or ActionType == "ConnectionSuccess"
| distinct DeviceName, LocalIP
| extend DeviceName=tolower(split(DeviceName,".")[0])
);
Let Binding: PotentialNTLMRelayLogins
let PotentialNTLMRelayLogins = materialize (
DeviceLogonEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "LogonSuccess"
| where LogonType == "Network"
| where Protocol=="NTLM"
| where isnotempty(RemoteDeviceName) and isnotempty(RemoteIP)
| where RemoteIPType <> "Loopback"
| extend RemoteDeviceName=tolower(RemoteDeviceName)
| where RemoteDeviceName in ((DeviceIPs | project DeviceName))
| join kind=leftanti DeviceIPs on $left.RemoteIP == $right.LocalIP, $left.RemoteDeviceName == $right.DeviceName
| project-reorder Timestamp, RemoteIP, RemoteDeviceName, AccountDomain, AccountName
);
Stage 1: source
let DeviceIPs
Stage 2: source
let PotentialNTLMRelayLogins
Stage 3: source
DeviceNetworkEvents
Stage 4: where
where ...
Stage 5: where
where RemotePort in (445, 80, 9389)
Stage 6: where
where /* macro: (RemoteIP in (PotentialNTLMRelayLogins | project)) */
Stage 7: extend
extend ShortDeviceName
Stage 8: where
where /* macro: (ShortDeviceName in (PotentialNTLMRelayLogins | project)) */
Stage 9: kusto:lookup
lookup kind=inner (PotentialNTLMRelayLogins) on $left.ShortDeviceName, $right.RemoteDeviceName, $left.RemoteIP, $right.RemoteIP
Stage 10: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq |
LogonType | eq |
| field:"LogonType" kind:eq value:"Network" |
Protocol | eq |
| field:"Protocol" kind:eq value:"NTLM" |
RemoteDeviceName | is_not_null | field:"RemoteDeviceName" kind:is_not_null | |
RemoteIP | is_not_null | field:"RemoteIP" kind:is_not_null | |
RemoteIPType | ne |
| field:"RemoteIPType" kind:ne value:"Loopback" |
RemotePort | in |
| field:"RemotePort" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ShortDeviceName | extend |
DnsDomain | extend |
HostName | extend |
PaloAlto - Possible flooding
#'Detects possible flooding.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: feb185cc-51f4-11ec-bf63-0242ac130002
name: PaloAlto - Possible flooding
description: |
'Detects possible flooding.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
PaloAltoCDLEvent
| where EventResourceId =~ 'TRAFFIC'
| where isnotempty(NetworkSessionId)
| where DstBytes == 0 and tolong(NetworkPackets) > 0
| extend AccountCustomEntity = DstUsername, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
PaloAltoCDLEvent
Stage 2: where
| where EventResourceId =~ 'TRAFFIC'
Stage 3: where
| where isnotempty(NetworkSessionId)
Stage 4: where
| where DstBytes == 0 and tolong(NetworkPackets) > 0
Stage 5: extend
| extend AccountCustomEntity = DstUsername, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
PROD (TM005.1) - GPO - Linked, Unlinked, or Enforced at Tier Level OU
#Linking, Unlinking, or Enforcing of a GPO within the Tier Level OU structure
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Defense Impairment |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: 02cf2172-0eb7-4a4a-bce7-7251cdd3e233
name: 'PROD (TM005.1) - GPO - Linked, Unlinked, or Enforced at Tier Level OU'
description: |
Linking, Unlinking, or Enforcing of a GPO within the Tier Level OU structure
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1484.001
query: |
SecurityEvent
| where EventID == 5136
and (EventData matches regex @"(?i)OU=Domain Controllers"
or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
or EventData matches regex @"(?i)OU=Tier 2 End-User Accounts"
or EventData matches regex @"(?i)OU=Tier 2 End-User Devices"
or EventData matches regex @"(?i)OU=Tier 2 End-User Groups"
or EventData matches regex @"(?i)OU=Tier Model Administration"
or EventData matches regex @"(?i)OU=Tier Model Computer Quarantine")
and Activity has "modified"
and EventData has '"ObjectClass">organizationalUnit'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
| extend ObjectName = extract("<Data Name=\"ObjectDN\">(((?i)OU=[^,]+,?)+),(?i)DC=", 1, EventData)
| extend ObjectTier = case(
ObjectName has "Tier 0", "Tier 0",
ObjectName has "Tier 1", "Tier 1",
ObjectName has "Tier 2", "Tier 2",
ObjectName has "Tier Model Administration", "Tier 0",
ObjectName has "Tier Model Computer Quarantine", "Tier 0",
ObjectName has "Domain Controllers", "Tier 0",
"Unknown")
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ObjectName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
customDetails:
ObjectName: ObjectName
ObjectTier: ObjectTier
ObjectClass: ObjectClass
ActivityType: ActivityType
AttributeLDAP: AttributeLDAP
Account: Account
Domain: Domain
Computer: Computer
Channel: Channel
EventID: EventID
EventData: EventData
eventGroupingSettings:
aggregationKind: AlertPerResult
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: '(TM005.1) A GPO was {{ActivityType}} at the following {{ObjectTier}} {{ObjectName}} OU'
alertDescriptionFormat: 'This will alert when a GPO is linked, unlinked, or enforced at any of the Tier Model OUs.'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
and (EventData matches regex @"(?i)OU=Domain Controllers"
or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
or EventData matches regex @"(?i)OU=Tier 2 End-User Accounts"
or EventData matches regex @"(?i)OU=Tier 2 End-User Devices"
or EventData matches regex @"(?i)OU=Tier 2 End-User Groups"
or EventData matches regex @"(?i)OU=Tier Model Administration"
or EventData matches regex @"(?i)OU=Tier Model Computer Quarantine")
and Activity has "modified"
and EventData has '"ObjectClass">organizationalUnit'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
Stage 3: extend (8 consecutive steps)
| extend ObjectName = extract("<Data Name=\"ObjectDN\">(((?i)OU=[^,]+,?)+),(?i)DC=", 1, EventData)
| extend ObjectTier = case(
ObjectName has "Tier 0", "Tier 0",
ObjectName has "Tier 1", "Tier 1",
ObjectName has "Tier 2", "Tier 2",
ObjectName has "Tier Model Administration", "Tier 0",
ObjectName has "Tier Model Computer Quarantine", "Tier 0",
ObjectName has "Domain Controllers", "Tier 0",
"Unknown")
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
Stage 4: project
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Activity | match |
| field:"Activity" kind:match value:"modified" |
EventData | match |
| field:"EventData" kind:match |
EventData | regex_match |
| field:"EventData" kind:regex_match |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
AttributeLDAP | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
ObjectClass | project |
ObjectName | project |
ObjectTier | project |
TimeGenerated | project |
Remote Desktop Network Brute force (ASIM Network Session schema)
#This detection identifies RDP application network traffic and filters any source/destination pair generating more than 25 events hard threshold.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
Rule Body
id: b7dc801e-1e79-48bb-91e8-2229a8e6d40b
name: Remote Desktop Network Brute force (ASIM Network Session schema)
description: |
'This detection identifies RDP application network traffic and filters any source/destination pair generating more than 25 events hard threshold.'
severity: Medium
status: Available
tags:
- Schema: ASimNetworkSessions
SchemaVersion: 0.2.4
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// Start of the query and Filter events that resulted in failure
_Im_NetworkSession(eventresult="Failure")
// Filter out private source IP addresses and focus on specific destination port (3389)
// Also, ensure that the source and destination IP addresses are not the same
| where not(ipv4_is_private(SrcIpAddr)) and tostring(DstPortNumber) has_any ("3389") and SrcIpAddr != DstIpAddr
// Summarize the data by source and destination IP addresses, destination port number, network protocol, and event result
// Also, bin the time generated in 5-minute intervals
// Calculate the minimum and maximum time generated, the sum of event counts, and a set of up to 10 event vendors
| summarize Starttime= min(TimeGenerated),EndTime= max(TimeGenerated),TargettedIPs=dcount(DstIpAddr),Eventscount=sum(EventCount),EventVendors=make_set(EventVendor,10) by SrcIpAddr,DstPortNumber,EventResult, bin(TimeGenerated, 5m)
// Filter the summarized data to include only those with an event count of 25 or more
| where Eventscount >= 25
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
_Im_NetworkSession(eventresult="Failure")
Stage 2: where
| where not(ipv4_is_private(SrcIpAddr)) and tostring(DstPortNumber) has_any ("3389") and SrcIpAddr != DstIpAddr
Stage 3: summarize
| summarize Starttime= min(TimeGenerated),EndTime= max(TimeGenerated),TargettedIPs=dcount(DstIpAddr),Eventscount=sum(EventCount),EventVendors=make_set(EventVendor,10) by SrcIpAddr,DstPortNumber,EventResult, bin(TimeGenerated, 5m)
Stage 4: where
| where Eventscount >= 25
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstPortNumber | match |
| field:"DstPortNumber" kind:match value:"3389" |
Eventscount | ge |
| field:"Eventscount" kind:ge value:"25" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SrcIpAddr | 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:SrcIpAddr |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstPortNumber | summarize |
EndTime | summarize |
EventResult | summarize |
EventVendors | summarize |
Eventscount | summarize |
SrcIpAddr | summarize |
Starttime | summarize |
TargettedIPs | summarize |
SonicWall - Capture ATP Malicious File Detection
#'This rule identifies malicious file verdicts from the SonicWall Capture ATP service. This analytic rule leverages the SonicWall Firewall ASIM Network Session parser (ASimNetworkSessionSonicWallFirewall). Ref: https://www.sonicwall.com/products/capture-advanced-threat-protection/ Ref: https://www.sonicwall.com/support/knowledge-base/how-to-view-threat-reports-capture-atp/170505384715913/'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 3db9f99e-a459-41e0-8e02-8b332f5fcb2c
name: SonicWall - Capture ATP Malicious File Detection
description: |
'This rule identifies malicious file verdicts from the SonicWall Capture ATP service. This analytic rule leverages the SonicWall Firewall ASIM Network Session parser (ASimNetworkSessionSonicWallFirewall).
Ref: https://www.sonicwall.com/products/capture-advanced-threat-protection/
Ref: https://www.sonicwall.com/support/knowledge-base/how-to-view-threat-reports-capture-atp/170505384715913/'
severity: Medium
status: Experimental
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
CommonSecurityLog
| where DeviceEventClassID == 1631
| extend CaptureATPVerdict = extract(@'Gateway Anti-Virus Status: (.*)\. ', 1, Message)
| where CaptureATPVerdict == "BAD"
| parse-kv AdditionalExtensions as (['susr']:string, ['fileid']:string) with (pair_delimiter=";", kv_delimiter="=")
| extend
NetworkProtocol = toupper(iff(Protocol contains "-" and Protocol !contains "/", toupper(trim_start(@".*-", Protocol)), toupper(trim_end(@"/.*", Protocol)))),
NetworkApplicationProtocol = tostring(toupper(trim_start(@".*/", Protocol)))
| project
TimeGenerated,
DeviceModel = DeviceProduct,
SerialNumber = Computer,
SrcIpAddr = SourceIP,
SrcUsername = coalesce(susr, SourceUserName),
NetworkProtocol,
NetworkApplicationProtocol,
RequestURL,
File = url_decode(tostring(split(RequestURL, '/')[-1])),
Hash = fileid,
Verdict = CaptureATPVerdict
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: File
- entityType: Url
fieldMappings:
- identifier: Url
columnName: RequestURL
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceEventClassID == 1631
Stage 3: extend
| extend CaptureATPVerdict = extract(@'Gateway Anti-Virus Status: (.*)\. ', 1, Message)
Stage 4: where
| where CaptureATPVerdict == "BAD"
Stage 5: parse
| parse-kv AdditionalExtensions as (['susr']:string, ['fileid']:string) with (pair_delimiter=";", kv_delimiter="=")
Stage 6: extend
| extend
NetworkProtocol = toupper(iff(Protocol contains "-" and Protocol !contains "/", toupper(trim_start(@".*-", Protocol)), toupper(trim_end(@"/.*", Protocol)))),
NetworkApplicationProtocol = tostring(toupper(trim_start(@".*/", Protocol)))
Stage 7: project
| project
TimeGenerated,
DeviceModel = DeviceProduct,
SerialNumber = Computer,
SrcIpAddr = SourceIP,
SrcUsername = coalesce(susr, SourceUserName),
NetworkProtocol,
NetworkApplicationProtocol,
RequestURL,
File = url_decode(tostring(split(RequestURL, '/')[-1])),
Hash = fileid,
Verdict = CaptureATPVerdict
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CaptureATPVerdict | eq |
| field:"CaptureATPVerdict" kind:eq value:"BAD" |
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"1631" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceModel | project |
File | project |
Hash | project |
NetworkApplicationProtocol | project |
NetworkProtocol | project |
RequestURL | project |
SerialNumber | project |
SrcIpAddr | project |
SrcUsername | project |
TimeGenerated | project |
Verdict | project |
Suspicious TGT Request with a DC Account
#Below query detects TGT requests from a DC account with an IP that doesn't belong to a DC. It detect PetitPotam and any other attacks that uses a stolen DC certificate/account to perform operations.
If you make it a detection rule, take ingestion delay into account.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4768: A Kerberos authentication ticket (TGT) was requested. |
Rule Body
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-petitpotam-and-other-domain-controller-account-takeovers-d3364bd9ee0a
//
// Description : This query detects if a computer account of a Domain Controller is stolen and used from a Non-DC device.
// computer account of a DC can bu used to obtain a TGT.
//
// Query parameters:
//
// list of DCs
let DCs = dynamic(["yourdc1.yourdomain.local"]);
// list of DC IPs
let DC_IPs = dynamic(["IP of the DCs including IPv6 addresses"]);
//
SecurityEvent
| where EventID == 4768
| where Computer in~ (DCs)
| where TargetUserName endswith "$"
| where DCs has replace_string(TargetUserName,"$","")
| where IpAddress <> "::1"
| extend IpAddress = replace_string(IpAddress, "::ffff:", "")
| where IpAddress !in (DC_IPs)
| project-reorder Computer, TargetUserName, IpAddress
Stages and Predicates
Parameters
let DCs = dynamic(["yourdc1.yourdomain.local"]);
let DC_IPs = dynamic(["IP of the DCs including IPv6 addresses"]);
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 4768
Stage 3: where
| where Computer in~ (DCs)
Stage 4: where
| where TargetUserName endswith "$"
Stage 5: where
| where DCs has replace_string(TargetUserName,"$","")
Stage 6: where
| where IpAddress <> "::1"
Stage 7: extend
| extend IpAddress = replace_string(IpAddress, "::ffff:", "")
Stage 8: where
| where IpAddress !in (DC_IPs)
Stage 9: project-reorder
| project-reorder Computer, TargetUserName, IpAddress
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Computer | in |
| field:"Computer" kind:in value:"DCs" |
EventID | eq |
| field:"EventID" kind:eq value:"4768" |
IpAddress | ne |
| field:"SourceIp" kind:ne value:"::1" |
TargetUserName | ends_with |
| field:"TargetUserName" kind:ends_with value:"$" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
IpAddress | eq | DC_IPs | excludes:IpAddress field:"IpAddress" value:"DC_IPs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IpAddress | extend |
Theom - Financial data exposed
#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 Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0026 (Theom has observed financial data in a data store that is publicly exposed. As per this requirement, use this information to apply data access control lists or access permissions to secure your data)"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 0cead100-f6ca-4cbb-989d-424d20705f30
name: Theom - Financial data exposed
description: |
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0026 (Theom has observed financial data in a data store that is publicly exposed. As per this requirement, use this information to apply data access control lists or access permissions to secure your data)"
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1213
- T1530
query: |
TheomAlerts_CL
| where customProps_RuleId_s == "TRIS0026" and (priority_s == "P1" or priority_s == "P2")
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 customProps_RuleId_s == "TRIS0026" and (priority_s == "P1" or priority_s == "P2")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
customProps_RuleId_s | eq |
| field:"customProps_RuleId_s" kind:eq value:"TRIS0026" |
priority_s | eq |
| field:"priority_s" kind:eq |
ThreatConnect TI map Email entity to SigninLogs
#Identifies a match in SigninLogs table from any Email IOC from ThreatConnect TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: ecb68ce7-c309-59a7-a8de-07ccf2a0ea4f
name: ThreatConnect TI map Email entity to SigninLogs
version: 1.2.5
kind: Scheduled
description: |
'Identifies a match in SigninLogs table from any Email IOC from ThreatConnect TI'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
let aadFunc = (tableName:string){
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where ExpirationDateTime > now() and Active == true
// Filter out non ThreatConnect TI Sources
| where SourceSystem startswith "ThreatConnect-"
//Filtering the table for Email related IOCs
| where isnotempty(EmailSenderAddress)
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (
table(tableName) | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserPrincipalName)
//Normalizing the column to lower case for exact match with EmailSenderAddress column
| extend UserPrincipalName = tolower(UserPrincipalName)
| where UserPrincipalName matches regex emailregex
| extend Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
// renaming timestamp column so it is clear the log this came from SigninLogs table
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
)
on $left.EmailSenderAddress == $right.UserPrincipalName
| where SigninLogs_TimeGenerated < ExpirationDateTime
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, UserPrincipalName
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, IPAddress, UserPrincipalName, AppDisplayName,
StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, Type
| extend Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
| extend timestamp = SigninLogs_TimeGenerated
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
Let Binding: aadFunc
let aadFunc = (tableName:string){
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where ExpirationDateTime > now() and Active == true
| where SourceSystem startswith "ThreatConnect-"
| where isnotempty(EmailSenderAddress)
| join kind=innerunique (
table(tableName) | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserPrincipalName)
| extend UserPrincipalName = tolower(UserPrincipalName)
| where UserPrincipalName matches regex emailregex
| extend Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
)
on $left.EmailSenderAddress == $right.UserPrincipalName
| where SigninLogs_TimeGenerated < ExpirationDateTime
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, UserPrincipalName
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, IPAddress, UserPrincipalName, AppDisplayName,
StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, Type
| extend Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
| extend timestamp = SigninLogs_TimeGenerated
};
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: aadSignin, aadNonInt
Leg 1: aadSignin
Leg 2: aadNonInt
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
SourceSystem | starts_with |
| field:"SourceSystem" kind:starts_with value:"ThreatConnect-" |
UserPrincipalName | is_not_null | field:"UserPrincipalName" kind:is_not_null | |
UserPrincipalName | regex_match |
| field:"UserPrincipalName" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AppDisplayName | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IPAddress | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
SigninLogs_TimeGenerated | project |
StatusCode | project |
StatusDetails | project |
ThreatType | project |
Type | project |
Url | project |
UserPrincipalName | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
TI Map IP entity to AzureFirewall
#Identifies a match in AzureFirewall (NetworkRule & ApplicationRule Logs) from any IP IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 4992d2f3-d6c0-4271-adac-b23532ba4492
name: TI Map IP entity to AzureFirewall
description: |
Identifies a match in AzureFirewall (NetworkRule & ApplicationRule Logs) from any IP IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: AzureFirewall
dataTypes:
- AzureDiagnostics
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
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 = ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
| extend NetworkSourceIP = toupper(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
// Filter out indicators without relevant IP address fields
| where TimeGenerated >= ago(ioc_lookBack)
// Select the IP entity based on availability of different IP fields
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
// Exclude local addresses using the ipv4_is_private operator and filtering out specific address prefixes
| 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 Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// Perform a join between IP indicators and AzureDiagnostics logs to identify potential malicious activity
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
// 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 (
union AZFWApplicationRule, AZFWNetworkRule
| where TimeGenerated >= ago(dt_lookBack)
//| where OperationName in ("AzureFirewallApplicationRuleLog", "AzureFirewallNetworkRuleLog")
| parse kind=regex flags=U ActionReason with Protocol 'request from ' SourceHost 'to ' DestinationHost @'\.? Action: ' Firewall_Action @'\.' Rest_msg
| extend SourceAddress = SourceIp
| extend DestinationAddress = DestinationIp
| extend RemoteIP = case(not(ipv4_is_private(DestinationAddress)), DestinationAddress, not(ipv4_is_private(SourceAddress)), SourceAddress, "")
| where isnotempty(RemoteIP) // Filter out traffic involving public addresses only
| extend Firewall_Action = Action
| project-rename AzureFirewall_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.RemoteIP
// Filter out logs that occurred after the expiration of the corresponding indicator
| where AzureFirewall_TimeGenerated < ValidUntil
// Group the results by IndicatorId and RemoteIP, and keep the log entry with the latest timestamp
| summarize AzureFirewall_TimeGenerated = arg_max(AzureFirewall_TimeGenerated, *) by Id, RemoteIP
// Select the desired output fields
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project LatestIndicatorTime, Description, ActivityGroupNames, Id, ValidUntil, Confidence,
AzureFirewall_TimeGenerated, TI_ipEntity, ActionReason, SourceAddress, DestinationAddress, Firewall_Action, Protocol,
NetworkSourceIP, Type, TargetUrl
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: TI_ipEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: TargetUrl
version: 1.3.8
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 11 define let IP_Indicators as the rule's main pipeline source. Stages 12 to 18 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
Stage 4: extend
| extend NetworkSourceIP = toupper(ObservableValue)
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: extend
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
TI_ipEntity =isnotempty(NetworkSourceIP)NetworkSourceIPNetworkSourceIPStage 8: extend
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
TI_ipEntity =isempty(TI_ipEntity) and isnotempty(NetworkSourceIP)NetworkSourceIPTI_ipEntityStage 9: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 10: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 11: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 12: project-reorder
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
Stage 13: join
join kind=innerunique (...) on $left.TI_ipEntity, $right.RemoteIP
Stage 14: where
where AzureFirewall_TimeGenerated < ValidUntil
Stage 15: summarize
summarize AzureFirewall_TimeGenerated by Id, RemoteIP
Stage 16: extend
extend Description
Stage 17: extend
extend ActivityGroupNames
Stage 18: project
project ActionReason, ActivityGroupNames, AzureFirewall_TimeGenerated, Confidence, Description, DestinationAddress, Firewall_Action, Id, LatestIndicatorTime, NetworkSourceIP, Protocol, SourceAddress, TI_ipEntity, TargetUrl, Type, ValidUntil
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | in |
| field:"IndicatorType" kind:in |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
RemoteIP | is_not_null | field:"RemoteIP" kind:is_not_null | |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
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 |
|---|---|
ActionReason | project |
ActivityGroupNames | project |
AzureFirewall_TimeGenerated | project |
Confidence | project |
Description | project |
DestinationAddress | project |
Firewall_Action | project |
Id | project |
LatestIndicatorTime | project |
NetworkSourceIP | project |
Protocol | project |
SourceAddress | project |
TI_ipEntity | project |
TargetUrl | project |
Type | project |
ValidUntil | project |
TI Map IP Entity to VMConnection
#This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in VMConnection.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 9713e3c0-1410-468d-b79e-383448434b2d
name: TI Map IP Entity to VMConnection
description: |
This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in VMConnection.
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureMonitor(VMInsights)
dataTypes:
- VMConnection
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
// 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 VMConnection 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 (
VMConnection
| where TimeGenerated >= ago(dt_lookBack)
| extend VMConnection_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.RemoteIp
// Filter out VMConnection events that occurred after the expiration of the corresponding indicator
| where VMConnection_TimeGenerated < ExpirationDateTime
// Group the results by IndicatorId and keep the VMConnection event with the latest timestamp
| summarize VMConnection_TimeGenerated = arg_max(VMConnection_TimeGenerated, *) by IndicatorId, RemoteIp
// Select the desired output fields
| project VMConnection_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, Computer, Direction, ProcessName, SourceIp, DestinationIp, RemoteIp, Protocol, DestinationPort, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
| extend timestamp = VMConnection_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.4.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 (
VMConnection
| where TimeGenerated >= ago(dt_lookBack)
| extend VMConnection_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.RemoteIp
Stage 9: where
where VMConnection_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize VMConnection_TimeGenerated by IndicatorId, RemoteIp
Stage 11: project
project ActivityGroupNames, Computer, ConfidenceScore, Description, DestinationIp, DestinationPort, Direction, EmailSourceIpAddress, ExpirationDateTime, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, ProcessName, Protocol, RemoteIp, SourceIp, TI_ipEntity, ThreatType, Type, Url, VMConnection_TimeGenerated
Stage 12: extend
extend DnsDomain, HostName, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
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 |
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 |
|---|---|
ActivityGroupNames | project |
Computer | project |
ConfidenceScore | project |
Description | project |
DestinationIp | project |
DestinationPort | project |
Direction | project |
EmailSourceIpAddress | project |
ExpirationDateTime | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
ProcessName | project |
Protocol | project |
RemoteIp | project |
SourceIp | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
VMConnection_TimeGenerated | project |
DnsDomain | extend |
HostName | extend |
timestamp | extend |