Detection rules › Kusto
Kusto rules: 13
AWSGuardDuty - GuardDuty Alert
#Identifies Amazon GuardDuty findings and creates a Microsoft Sentinel alert for each finding. Use the GuardDuty finding details in the alert to determine the specific malicious or suspicious activity that was detected.
Rule Body
id: bf0cde21-0c41-48f6-a40c-6b5bd71fa106
name: AWSGuardDuty - GuardDuty Alert
description: |
Identifies Amazon GuardDuty findings and creates a Microsoft Sentinel alert for each finding. Use the GuardDuty finding details in the alert to determine the specific malicious or suspicious activity that was detected.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWSS3
dataTypes:
- AWSGuardDuty
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html
AWSGuardDuty
// Parse the finding
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-format.html
// Example: "ThreatPurpose:ResourceTypeAffected/ThreatFamilyName.DetectionMechanism!Artifact"
| extend findingTokens = split(ActivityType, ":")
| extend ThreatPurpose=findingTokens[0], findingTokens=split(findingTokens[1], "/")
| extend ResourceTypeAffected=findingTokens[0], findingTokens= split(findingTokens[1], ".")
| extend ThreatFamilyName=findingTokens[0], findingTokens=split(findingTokens[1], "!")
| extend DetectionMechanism=findingTokens[0], Artifact=findingTokens[1]
// Assign severity level
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html#guardduty_findings-severity
| extend Severity =
case (
Severity >= 7.0, "High",
Severity between (4.0 .. 6.9), "Medium",
Severity between (1.0 .. 3.9), "Low",
"Unknown"
)
// Pull out any available resource details we can extract entities from. These may not exist in the alert.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Resource.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsDbUserDetails.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesDetails.html
| extend AccessKeyDetails=ResourceDetails.accessKeyDetails
| extend RdsDbUserDetails=ResourceDetails.rdsDbUserDetails
| extend KubernetesDetails=ResourceDetails.kubernetesDetails
// Pull out any available action details we can extract entities from. These may not exist in the alert.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Action.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_NetworkConnectionAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsLoginAttemptAction.html
| extend ServiceAction =
case(
isnotempty(ServiceDetails.action.awsApiCallAction), ServiceDetails.action.awsApiCallAction,
isnotempty(ServiceDetails.action.kubernetesApiCallAction), ServiceDetails.action.kubernetesApiCallAction,
isnotempty(ServiceDetails.action.networkConnectionAction), ServiceDetails.action.networkConnectionAction,
isnotempty(ServiceDetails.action.rdsLoginAttemptAction), ServiceDetails.action.rdsLoginAttemptAction,
dynamic(null)
)
// The IPv4 remote address of the connection
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteIpDetails.html
// or
// The IP of the Kubernetes API caller and the IPs of any proxies or load balancers between the caller and the API endpoint
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html
| extend RemoteIpAddress =
coalesce(
tostring(ServiceAction.remoteIpDetails.ipAddressV4),
tostring(parse_json(ServiceAction.sourceIPs)[0])
)
// The IPv4 local address of the connection
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_LocalIpDetails.html
| extend LocalIpAddress = ServiceAction.localIpDetails.ipAddressV4
// The AWS account ID of the remote API caller.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteAccountDetails.html
| extend RemoteAWSAccountId = ServiceAction.remoteAccountDetails.accountId
// The IAM access key details (user information) of a user that engaged in the activity that prompted GuardDuty to generate a finding
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html
| extend AccountUpn =
case(
AccessKeyDetails.userType == "IAMUser", AccessKeyDetails.userName,
AccessKeyDetails.userType == "AssumedRole", split(AccessKeyDetails.principalId, ":", 1)[0],
isnotempty(RdsDbUserDetails.user), RdsDbUserDetails.user,
isnotempty(KubernetesDetails.kubernetesUserDetails.username), KubernetesDetails.kubernetesUserDetails.username,
""
)
| extend AccountName = split(AccountUpn, "@", 0)[0]
| extend UPNSuffix = split(AccountUpn, "@", 1)[0]
// Clean up the output
| extend GuardDutyDetails =
bag_pack(
"DetectorId", ServiceDetails.detectorId,
"Partition", Partition,
"Region", Region
)
| extend FindingLink =
iff(
isnotempty(Region) and isnotempty(Id),
strcat("https://", Region, ".console.aws.amazon.com/guardduty/home?region=", Region, "#/findings?fId=", Id),
""
)
| extend FindingLinkDescription =
iff(
isnotempty(FindingLink),
strcat("Link to GuardDuty finding (AWS): ", FindingLink),
""
)
| project-rename
FindingArn=Arn,
FindingId=Id,
AWSAccountId=AccountId
| project-away
ActivityType,
findingTokens,
Partition,
Region,
SchemaVersion,
TimeGenerated,
Type
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: ObjectGuid
columnName: RemoteAWSAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIpAddress
- entityType: IP
fieldMappings:
- identifier: Address
columnName: LocalIpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: FindingLink
customDetails:
ThreatPurpose: ThreatPurpose
ResourceTypeAffected: ResourceTypeAffected
ThreatFamilyName: ThreatFamilyName
DetectionMechanism: DetectionMechanism
Artifact: Artifact
alertDetailsOverride:
alertDisplayNameFormat: '{{Title}}'
alertDescriptionFormat: '{{Description}}'
alertTacticsColumnName: ThreatPurpose
alertSeverityColumnName: Severity
kind: Scheduled
version: 1.0.7
Stages and Predicates
Stage 1: source
AWSGuardDuty
Stage 2: extend (19 consecutive steps)
| extend findingTokens = split(ActivityType, ":")
| extend ThreatPurpose=findingTokens[0], findingTokens=split(findingTokens[1], "/")
| extend ResourceTypeAffected=findingTokens[0], findingTokens= split(findingTokens[1], ".")
| extend ThreatFamilyName=findingTokens[0], findingTokens=split(findingTokens[1], "!")
| extend DetectionMechanism=findingTokens[0], Artifact=findingTokens[1]
| extend Severity =
case (
Severity >= 7.0, "High",
Severity between (4.0 .. 6.9), "Medium",
Severity between (1.0 .. 3.9), "Low",
"Unknown"
)
| extend AccessKeyDetails=ResourceDetails.accessKeyDetails
| extend RdsDbUserDetails=ResourceDetails.rdsDbUserDetails
| extend KubernetesDetails=ResourceDetails.kubernetesDetails
| extend ServiceAction =
case(
isnotempty(ServiceDetails.action.awsApiCallAction), ServiceDetails.action.awsApiCallAction,
isnotempty(ServiceDetails.action.kubernetesApiCallAction), ServiceDetails.action.kubernetesApiCallAction,
isnotempty(ServiceDetails.action.networkConnectionAction), ServiceDetails.action.networkConnectionAction,
isnotempty(ServiceDetails.action.rdsLoginAttemptAction), ServiceDetails.action.rdsLoginAttemptAction,
dynamic(null)
)
| extend RemoteIpAddress =
coalesce(
tostring(ServiceAction.remoteIpDetails.ipAddressV4),
tostring(parse_json(ServiceAction.sourceIPs)[0])
)
| extend LocalIpAddress = ServiceAction.localIpDetails.ipAddressV4
| extend RemoteAWSAccountId = ServiceAction.remoteAccountDetails.accountId
| extend AccountUpn =
case(
AccessKeyDetails.userType == "IAMUser", AccessKeyDetails.userName,
AccessKeyDetails.userType == "AssumedRole", split(AccessKeyDetails.principalId, ":", 1)[0],
isnotempty(RdsDbUserDetails.user), RdsDbUserDetails.user,
isnotempty(KubernetesDetails.kubernetesUserDetails.username), KubernetesDetails.kubernetesUserDetails.username,
""
)
| extend AccountName = split(AccountUpn, "@", 0)[0]
| extend UPNSuffix = split(AccountUpn, "@", 1)[0]
| extend GuardDutyDetails =
bag_pack(
"DetectorId", ServiceDetails.detectorId,
"Partition", Partition,
"Region", Region
)
| extend FindingLink =
iff(
isnotempty(Region) and isnotempty(Id),
strcat("https://", Region, ".console.aws.amazon.com/guardduty/home?region=", Region, "#/findings?fId=", Id),
""
)
| extend FindingLinkDescription =
iff(
isnotempty(FindingLink),
strcat("Link to GuardDuty finding (AWS): ", FindingLink),
""
)
Stage 3: project-rename
| project-rename
FindingArn=Arn,
FindingId=Id,
AWSAccountId=AccountId
Stage 4: project-away
| project-away
ActivityType,
findingTokens,
Partition,
Region,
SchemaVersion,
TimeGenerated,
Type
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ThreatPurpose | extend |
ResourceTypeAffected | extend |
ThreatFamilyName | extend |
Artifact | extend |
DetectionMechanism | extend |
Severity | extend |
AccessKeyDetails | extend |
RdsDbUserDetails | extend |
KubernetesDetails | extend |
ServiceAction | extend |
RemoteIpAddress | extend |
LocalIpAddress | extend |
RemoteAWSAccountId | extend |
AccountUpn | extend |
AccountName | extend |
UPNSuffix | extend |
GuardDutyDetails | extend |
FindingLink | extend |
FindingLinkDescription | extend |
AWSAccountId | project-rename |
FindingArn | project-rename |
FindingId | project-rename |
CybleVision Alerts Darkweb Marketplace Alerts
#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 stolen credentials, financial information, stealer logs, and related payloads listed on Darkweb marketplaces such as RussianMarket. Extracts card data, victim info, metadata, and marketplace context via Alerts_DarkwebMarketplaces.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Credential Access | |
| Collection | |
| Exfiltration | |
| Exfiltration |
Rule Body
id: e80eedb4-cbae-45cc-b1be-a2a8dc31af3b
name: CybleVision Alerts Darkweb Marketplace Alerts
description: |
'Detects stolen credentials, financial information, stealer logs, and related payloads listed on Darkweb marketplaces such as RussianMarket. Extracts card data, victim info, metadata, and marketplace context via Alerts_DarkwebMarketplaces.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Collection
- Exfiltration
- Reconnaissance
relevantTechniques:
- T1555
- T1005
- T1041
- T1589
- T1646
subTechniques: []
query: |
Alerts_darkweb_marketplaces
| where Service == "darkweb_marketplaces"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: CybleVision Darkweb Listing Found ({{DW_Marketplace}})
alertDescriptionFormat: |
A darkweb marketplace listing referencing your monitored assets was discovered. Vendor {{DW_Vendor}} Region {{DW_Region}} Links {{DW_Links}}\n
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
Marketplace: DW_Marketplace
Vendor: DW_Vendor
Price: DW_Price
Country: DW_Country
Region: DW_Region
Stealer: DW_StealerName
Date: DW_Date
UpdatedDate: DW_UpdatedDate
Links: DW_Links
BankCard: DW_Bank_CardNo
BankBrand: DW_Bank_Brand
BankExpiry: DW_Bank_Expiry
RealName: DW_Bank_RealName
RawData: DW_RawData
Info: DW_Info
Tags: DW_Tags
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DW_Links
- entityType: File
fieldMappings:
- identifier: Name
columnName: DW_Bank_BIN
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DW_IP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_darkweb_marketplaces
Stage 2: where
| where Service == "darkweb_marketplaces"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"darkweb_marketplaces" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Detect presence of private IP addresses in URLs (ASIM Web Session)
#'This rule identifies requests made to atypical URLs, as malware can exploit IP addresses for communication with command-and-control (C2) servers. The detection identifies network requests that contain either plain text or Base64 encoded IP addresses. Alerts are triggered when a private IP address is observed as plain text or base64 encoded in an outbound web request. This method of concealing the IP address was observed in the utilization of the RunningRAT tool by POLONIUM.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: e3a7722a-e099-45a9-9afb-6618e8f05405
name: Detect presence of private IP addresses in URLs (ASIM Web Session)
description: |
'This rule identifies requests made to atypical URLs, as malware can exploit IP addresses for communication with command-and-control (C2) servers. The detection identifies network requests that contain either plain text or Base64 encoded IP addresses. Alerts are triggered when a private IP address is observed as plain text or base64 encoded in an outbound web request. This method of concealing the IP address was observed in the utilization of the RunningRAT tool by POLONIUM.'
severity: Medium
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1041
- T1071.001
- T1001
query: |
let lookback = 1h;
// Identified base64 encoded IPv4 addresses
let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)";
// Extractes IPv4 addresses as hex values
let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){6,14}(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d))";
let ipV4_Private_FromPlainString = _Im_WebSession(starttime=ago(lookback), eventresult='Success')
| where isnotempty(Url)
| extend ip_inURL = extract(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", 0, Url)
| where ipv4_is_private(ip_inURL)
| where not(ipv4_is_private(DstIpAddr)) // only take traffic going to internet
| summarize
EventCount=count(),
EventEndTime = max(TimeGenerated),
EventStartTime = min(TimeGenerated),
Urls=tostring(make_set(Url, 100))
by SrcIpAddr, SrcUsername, SrcHostname, ip_inURL;
let ipV4_Private_FromEncodedString =
_Im_WebSession(starttime=ago(lookback), eventresult='Success')
| where isnotempty(Url)
// Identify requests with encoded IPv4 addresses
| where Url matches regex ipv4_encoded_identification_regex
| where not(ipv4_is_private(DstIpAddr)) // only take traffic going to internet
| project TimeGenerated, Url, SrcIpAddr, SrcUsername, SrcHostname
// Extract IP candidates in their base64 encoded format, significantly reducing the dataset
| extend extracted_encoded_ip_inURL = extract_all(ipv4_encoded_identification_regex, Url)
// We could have more than one ip, expand them out
| mv-expand extracted_encoded_ip_inURL to typeof(string)
| summarize
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated),
make_set(Url, 100),
EventCount=count()
by extracted_encoded_ip_inURL, SrcIpAddr, SrcUsername, SrcHostname
// Pad if we need to
| extend extracted_encoded_ip_inURL = iff(strlen(extracted_encoded_ip_inURL) % 2 == 0, extracted_encoded_ip_inURL, strcat(extracted_encoded_ip_inURL, "="))
// Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first
| extend extracted_encoded_ip_inURL = tostring(base64_decode_toarray(extracted_encoded_ip_inURL))
// Extract the IP candidates from the array
| extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_inURL)
// Expand, it's still possible that we might have more than 1 IP
| mv-expand hex_extracted
// Now we should have a clean string. We need to put it back into a dynamic array to convert back to a string.
| extend hex_extracted = trim_end(",", tostring(hex_extracted))
| extend hex_extracted = strcat("[", hex_extracted, "]")
| extend hex_extracted = todynamic(hex_extracted)
// Convert the array back into a string
| extend decoded_ip_inURL = unicode_codepoints_to_string(hex_extracted)
| where ipv4_is_private(decoded_ip_inURL)
| project
ip_inURL=decoded_ip_inURL,
Urls=tostring(set_Url),
EventStartTime,
EventEndTime,
EventCount,
SrcIpAddr,
SrcUsername,
SrcHostname;
union ipV4_Private_FromPlainString, ipV4_Private_FromEncodedString
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ip_inURL
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcHostname
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
EventCount: EventCount
Urls: Urls
EventStartTime: EventStartTime
EventEndTime: EventEndTime
alertDetailsOverride:
alertDisplayNameFormat: "Detected a private ip address '{{ip_inURL}}' carved in URL"
alertDescriptionFormat: "User '{{SrcUsername}}' has been detected requesting URL '{{Urls}}' that contains private IP address '{{ip_inURL}}'. Encoding private IP addresses in a URL can be a method used by attackers to exfiltrate data from a compromised system"
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 1h;
Let Binding: ipv4_encoded_identification_regex
let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)";
Let Binding: ipv4_decoded_hex_extract
let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){6,14}(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d))";
Union (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: ipV4_Private_FromPlainString, ipV4_Private_FromEncodedString
Leg 1: ipV4_Private_FromPlainString
Leg 2: ipV4_Private_FromEncodedString
Applied to the Combined Result
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Url | is_not_null | field:"Url" kind:is_not_null | |
Url | regex_match |
| field:"Url" kind:regex_match value:"\=([a-zA-Z0-9\/+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/+]{2,4}){3}[a-zA-Z0-9\/+\=]*)" |
decoded_ip_inURL | cidr_match |
| field:"decoded_ip_inURL" kind:cidr_match |
ip_inURL | cidr_match |
| field:"ip_inURL" kind:cidr_match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstIpAddr | 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:DstIpAddr |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCount | project |
EventEndTime | project |
EventStartTime | project |
SrcHostname | project |
SrcIpAddr | project |
SrcUsername | project |
Urls | project |
ip_inURL | project |
Name | extend |
UPNSuffix | extend |
Detect unauthorized data transfers using timeseries anomaly (ASIM Web Session)
#'This query utilizes built-in KQL anomaly detection algorithms to identify anomalous data transfers to public networks. It detects significant deviations from a baseline pattern, allowing the detection of sudden increases in data transferred to unknown public networks, which may indicate data exfiltration attempts. Investigating such anomalies is crucial. The score indicates the degree to which the data transfer deviates from the baseline value. A higher score indicates a greater deviation. The query's output provides an aggregated summary view of the traffic observed in the flagged anomaly hour, including unique combinations of source IP addresses, destination IP addresses, and port bytes sent. It may be necessary to run queries for individual source IP addresses from the provided 'SourceIPlist' to identify any suspicious activity that warrants further investigation'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 5965d3e7-8ed0-477c-9b42-e75d9237fab0
name: Detect unauthorized data transfers using timeseries anomaly (ASIM Web Session)
description: |
'This query utilizes built-in KQL anomaly detection algorithms to identify anomalous data transfers to public networks. It detects significant deviations from a baseline pattern, allowing the detection of sudden increases in data transferred to unknown public networks, which may indicate data exfiltration attempts. Investigating such anomalies is crucial.
The score indicates the degree to which the data transfer deviates from the baseline value. A higher score indicates a greater deviation. The query's output provides an aggregated summary view of the traffic observed in the flagged anomaly hour, including unique combinations of source IP addresses, destination IP addresses, and port bytes sent. It may be necessary to run queries for individual source IP addresses from the provided 'SourceIPlist' to identify any suspicious activity that warrants further investigation'
severity: Medium
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1030
query: |
let startTime = 14d;
let endTime = 1d;
let timeframe = 1h;
let scorethreshold = 5;
let bytessentperhourthreshold = 10;
// calculate avg. eps(events per second)
let eps = materialize(_Im_WebSession(starttime=ago(1d))
| project TimeGenerated
| summarize AvgPerSec = count() / 3600 by bin(TimeGenerated, 1h)
| summarize round(avg(AvgPerSec))
);
let summarizationexist = (
union isfuzzy=true
(
WebSession_Summarized_SrcIP_CL
| where EventTime_t > ago(1d)
| project v = int(2)
),
(
print int(1)
| project v = print_0
)
| summarize maxv = max(v)
| extend sumexist = (maxv > 1)
);
let TimeSeriesData = union isfuzzy=true
(
(datatable(exists: int, sumexist: bool)[1, false]
| where toscalar(eps) > 1000
| join (summarizationexist) on sumexist)
| join (
_Im_WebSession(starttime=ago(2d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
| project-away exists*, maxv, sum*
),
(
(datatable(exists: int, sumexist: bool)[1, false]
| where toscalar(eps) between (501 .. 1000)
| join (summarizationexist) on sumexist)
| join (
_Im_WebSession(starttime=ago(3d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
| project-away exists*, maxv, sum*
),
(
(datatable(exists: int, sumexist: bool)[1, false]
| where toscalar(eps) <= 500
| join (summarizationexist) on sumexist)
| join (
_Im_WebSession(starttime=ago(4d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
| project-away exists*, maxv, sum*
),
(
WebSession_Summarized_SrcIP_CL
| where EventTime_t between (ago(startTime) .. now())
| where isnotempty(SrcBytes_d) and not(DstIPIsPrivate_b)
| project
SrcBytesSum=tolong(SrcBytes_d),
EventTime=EventTime_t,
EventProduct = EventProduct_s
)
| make-series TotalBytesSent = sum(SrcBytesSum) on EventTime from startofday(ago(startTime)) to startofday(now()) step timeframe by EventProduct;
// TimeSeriesData block ends here
//Take only anomalies in TimeSeriesData
let TimeSeriesAnomalies = materialize(TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(TotalBytesSent, scorethreshold, -1, 'linefit')
| mv-expand
TotalBytesSent to typeof(long),
EventTime to typeof(datetime),
anomalies to typeof(double),
score to typeof(double),
baseline to typeof(long)
| where anomalies > 0 and baseline > 0
| extend AnomalyHour = EventTime
| extend
TotalBytesSentinMBperHour = round(((TotalBytesSent / 1024) / 1024), 2),
BaselineBytesSentinMBperHour = round(((baseline / 1024) / 1024), 2),
score = round(score, 2)
| project
EventProduct,
AnomalyHour,
TotalBytesSentinMBperHour,
BaselineBytesSentinMBperHour,
anomalies,
score
| where AnomalyHour between (startofday(ago(endTime)) .. startofday(now())) // Get TimeSeriesAnomalies in previous day
);
// TimeSeriesAlerts block end here
let AnomalyHours = materialize (TimeSeriesAnomalies
| project AnomalyHour);
//Previous day aggregated per hour
let PreviousDayLogs =
_Im_WebSession(starttime=startofday(ago(endTime)), endtime=startofday(now()))
| where isnotempty(DstIpAddr) and isnotempty(SrcIpAddr) and isnotempty(SrcBytes)
| where not(ipv4_is_private(DstIpAddr))
| project
TimeGenerated,
DstIpAddr,
SrcIpAddr,
SrcBytes,
DstBytes,
DstPortNumber,
EventProduct
| extend DateHour = bin(TimeGenerated, timeframe) // create a new column and round to hour
| where DateHour in (AnomalyHours) // Filter dataset to include only anomaly AnomalyHours
| extend
SentBytesinMB = ((SrcBytes / 1024) / 1024),
ReceivedBytesinMB = ((DstBytes / 1024) / 1024)
| summarize
HourlyCount = count(),
TimeGeneratedMax = arg_max(TimeGenerated, *),
DestinationIPList = make_set(DstIpAddr, 100),
DestinationPortList = make_set(DstPortNumber, 100),
SentBytesinMB = tolong(sum(SentBytesinMB)),
ReceivedBytesinMB = tolong(sum(ReceivedBytesinMB))
by SrcIpAddr, EventProduct, TimeGeneratedHour = bin(TimeGenerated, timeframe)
| where SentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, SentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour) // Ranking the dataset per Hourly Partition
| where Rank <= 10 // Selecting Top 10 records with Highest BytesSent in each Hour
| project
EventProduct,
TimeGeneratedHour,
TimeGeneratedMax,
SrcIpAddr,
DestinationIPList,
DestinationPortList,
SentBytesinMB,
ReceivedBytesinMB,
Rank,
HourlyCount;
// PreviousDayLogs block ends here
TimeSeriesAnomalies
| join kind = inner (PreviousDayLogs
| extend AnomalyHour = TimeGeneratedHour)
on EventProduct, AnomalyHour
| sort by score desc
| project
EventProduct,
AnomalyHour,
TimeGeneratedMax,
SrcIpAddr,
DestinationIPList,
DestinationPortList,
SentBytesinMB,
ReceivedBytesinMB,
TotalBytesSentinMBperHour,
BaselineBytesSentinMBperHour,
score,
anomalies,
HourlyCount
| summarize
EventCount = sum(HourlyCount),
startTimeUtc = min(TimeGeneratedMax),
EndTimeUtc = max(TimeGeneratedMax),
SentBytesinMB = sum(SentBytesinMB),
ReceivedBytesinMB = sum(ReceivedBytesinMB),
SourceIP = take_any(SrcIpAddr),
SourceIPList = make_set(SrcIpAddr, 10),
DestinationIPList = make_set(DestinationIPList, 100),
DestinationPortList = make_set(DestinationPortList, 100)
by
AnomalyHour,
TotalBytesSentinMBperHour,
BaselineBytesSentinMBperHour,
score,
anomalies,
EventProduct
| project
EventProduct,
AnomalyHour,
startTimeUtc,
EndTimeUtc,
SourceIP,
SourceIPList,
DestinationIPList,
DestinationPortList,
SentBytesinMB,
ReceivedBytesinMB,
TotalBytesSentinMBperHour,
BaselineBytesSentinMBperHour,
anomalies,
score,
EventCount
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
EventCount: EventCount
SourceIPList: SourceIPList
DestinationIPList: DestinationIPList
DestinationPortList: DestinationPortList
SentBytesinMB: SentBytesinMB
ReceivedBytesinMB: ReceivedBytesinMB
anomalies: anomalies
score: score
alertDetailsOverride:
alertDisplayNameFormat: "IP address '{{SourceIP}}' is engaged in data transfers to a public network that exceeds usual levels"
alertDescriptionFormat: "Please conduct a thorough investigation of each IPAddresses listed in SourceIPList: '{{SourceIPList}}' to identify any suspicious activities that may require further investigation. 'SourceIPList' include the top 10 client IP addresses that transmitted the highest amount of data during the anomalous hour"
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let startTime = 14d;
let endTime = 1d;
let timeframe = 1h;
let scorethreshold = 5;
let bytessentperhourthreshold = 10;
let TimeSeriesData and let TimeSeriesAnomalies are inlined into the numbered stages below.
Let Binding: eps
let eps = materialize(_Im_WebSession(starttime=ago(1d))
| project TimeGenerated
| summarize AvgPerSec = count() / 3600 by bin(TimeGenerated, 1h)
| summarize round(avg(AvgPerSec))
);
Let Binding: summarizationexist
let summarizationexist = (
union isfuzzy=true
(
WebSession_Summarized_SrcIP_CL
| where EventTime_t > ago(1d)
| project v = int(2)
),
(
print int(1)
| project v = print_0
)
| summarize maxv = max(v)
| extend sumexist = (maxv > 1)
);
Let Binding: AnomalyHours
let AnomalyHours = materialize (TimeSeriesAnomalies
| project AnomalyHour);
Let Binding: PreviousDayLogs
let PreviousDayLogs = _Im_WebSession(starttime=startofday(ago(endTime)), endtime=startofday(now()))
| where isnotempty(DstIpAddr) and isnotempty(SrcIpAddr) and isnotempty(SrcBytes)
| where not(ipv4_is_private(DstIpAddr))
| project
TimeGenerated,
DstIpAddr,
SrcIpAddr,
SrcBytes,
DstBytes,
DstPortNumber,
EventProduct
| extend DateHour = bin(TimeGenerated, timeframe)
| where DateHour in (AnomalyHours)
| extend
SentBytesinMB = ((SrcBytes / 1024) / 1024),
ReceivedBytesinMB = ((DstBytes / 1024) / 1024)
| summarize
HourlyCount = count(),
TimeGeneratedMax = arg_max(TimeGenerated, *),
DestinationIPList = make_set(DstIpAddr, 100),
DestinationPortList = make_set(DstPortNumber, 100),
SentBytesinMB = tolong(sum(SentBytesinMB)),
ReceivedBytesinMB = tolong(sum(ReceivedBytesinMB))
by SrcIpAddr, EventProduct, TimeGeneratedHour = bin(TimeGenerated, timeframe)
| where SentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, SentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour)
| where Rank <= 10
| project
EventProduct,
TimeGeneratedHour,
TimeGeneratedMax,
SrcIpAddr,
DestinationIPList,
DestinationPortList,
SentBytesinMB,
ReceivedBytesinMB,
Rank,
HourlyCount;
Stages 1 to 28 define let TimeSeriesAnomalies as the rule's main pipeline source. Stages 29 to 33 use it.
Stage 1: union
union of 4 branches
Stage 2: source
datatable(...)
Stage 3: where
where /* macro: (toscalar(eps) > 1000) */
Stage 4: join
join (summarizationexist) on sumexist
Stage 5: join
| join (
_Im_WebSession(starttime=ago(2d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
Stage 6: project-away
project-away exists*, maxv, sum*
Stage 7: source
datatable(...)
Stage 8: where
where /* macro: (toscalar(eps) between (501 .. 1000)) */
Stage 9: join
join (summarizationexist) on sumexist
Stage 10: join
| join (
_Im_WebSession(starttime=ago(3d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
Stage 11: project-away
project-away exists*, maxv, sum*
Stage 12: source
datatable(...)
Stage 13: where
where /* macro: (toscalar(eps) <= 500) */
Stage 14: join
join (summarizationexist) on sumexist
Stage 15: join
| join (
_Im_WebSession(starttime=ago(4d), endtime=now())
| project DstIpAddr, SrcBytes, TimeGenerated, EventProduct
| where isnotempty(DstIpAddr)
and not(ipv4_is_private(DstIpAddr))
and isnotempty(SrcBytes)
| summarize SrcBytesSum=tolong(sum(SrcBytes)) by EventProduct, bin(TimeGenerated, 1h)
| extend EventTime = TimeGenerated, exists=int(1)
)
on exists
Stage 16: project-away
project-away exists*, maxv, sum*
Stage 17: source
WebSession_Summarized_SrcIP_CL
Stage 18: where
| where EventTime_t between (ago(startTime) .. now())
Stage 19: where
| where isnotempty(SrcBytes_d) and not(DstIPIsPrivate_b)
Stage 20: project
| project
SrcBytesSum=tolong(SrcBytes_d),
EventTime=EventTime_t,
EventProduct = EventProduct_s
Stage 21: summarize
summarize TotalBytesSent by EventProduct
Stage 22: extend
extend anomalies, baseline, score
Stage 23: mv-expand
mv-expand TotalBytesSent
Stage 24: where
where anomalies > 0 and baseline > 0
Stage 25: extend
extend AnomalyHour
Stage 26: extend
extend BaselineBytesSentinMBperHour, TotalBytesSentinMBperHour, score
Stage 27: project
project AnomalyHour, BaselineBytesSentinMBperHour, EventProduct, TotalBytesSentinMBperHour, anomalies, score
Stage 28: where
where /* macro: (AnomalyHour between (startofday(ago(1d)) .. startofday(now()))) */
Stage 29: join
TimeSeriesAnomalies
| join kind = inner (PreviousDayLogs
| extend AnomalyHour = TimeGeneratedHour)
on EventProduct, AnomalyHour
Stage 30: sort
sort by score
Stage 31: project
project AnomalyHour, BaselineBytesSentinMBperHour, DestinationIPList, DestinationPortList, EventProduct, HourlyCount, ReceivedBytesinMB, SentBytesinMB, SrcIpAddr, TimeGeneratedMax, TotalBytesSentinMBperHour, anomalies, score
Stage 32: summarize
summarize DestinationIPList, DestinationPortList, EndTimeUtc, EventCount, ReceivedBytesinMB, SentBytesinMB, SourceIP, SourceIPList, startTimeUtc by AnomalyHour, TotalBytesSentinMBperHour, BaselineBytesSentinMBperHour, score, anomalies, EventProduct
Stage 33: project
project AnomalyHour, BaselineBytesSentinMBperHour, DestinationIPList, DestinationPortList, EndTimeUtc, EventCount, EventProduct, ReceivedBytesinMB, SentBytesinMB, SourceIP, SourceIPList, TotalBytesSentinMBperHour, anomalies, score, startTimeUtc
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstIpAddr | 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:DstIpAddr |
DstIPIsPrivate_b | eq | true | excludes:DstIPIsPrivate_b field:"DstIPIsPrivate_b" value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AnomalyHour | project |
BaselineBytesSentinMBperHour | project |
DestinationIPList | project |
DestinationPortList | project |
EndTimeUtc | project |
EventCount | project |
EventProduct | project |
ReceivedBytesinMB | project |
SentBytesinMB | project |
SourceIP | project |
SourceIPList | project |
TotalBytesSentinMBperHour | project |
anomalies | project |
score | project |
startTimeUtc | project |
Dev-0228 File Path Hashes November 2021
#This hunting query looks for file paths/hashes related to observed activity by Dev-0228. The actor is known to use custom version of popular tool like PsExec, Procdump etc. to carry its activity. The risk score associated with each result is based on a number of factors, hosts with higher risk events should be investigated first.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 3b443f22-9be9-4c35-ac70-a94757748439
name: Dev-0228 File Path Hashes November 2021
description: |
'This hunting query looks for file paths/hashes related to observed activity by Dev-0228. The actor is known to use custom version of popular tool like PsExec, Procdump etc. to carry its activity.
The risk score associated with each result is based on a number of factors, hosts with higher risk events should be investigated first.'
severity: High
requiredDataConnectors:
- connectorId: MicrosoftDefenderAdvancedThreatProtection
dataTypes:
- SecurityAlert (MDATP)
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Execution
relevantTechniques:
- T1569
- T1003
query: |
let files1 = dynamic(["C:\\Windows\\TAPI\\lsa.exe", "C:\\Windows\\TAPI\\pa.exe", "C:\\Windows\\TAPI\\pc.exe", "C:\\Windows\\TAPI\\Rar.exe"]);
let files2 = dynamic(["svchost.exe","wdmsvc.exe"]);
let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77", "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);
let FileHash2 = dynamic(["2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7", "9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd", "18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b"]);
DeviceProcessEvents
| where ( FolderPath has_any (files1) and SHA256 has_any (FileHash1)) or (FolderPath has_any (files2) and SHA256 has_any (FileHash2))
| extend DvcId = DeviceId
| join kind=leftouter (SecurityAlert
| where ProviderName =~ "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| mv-expand todynamic(Entities)
| extend DvcId = tostring(parse_json(Entities).MdatpDeviceId)
| where isnotempty(DvcId)
// Higher risk score are for Defender alerts related to threat actor
| extend AlertRiskScore = iif(ThreatName has_any ("Backdoor:MSIL/ShellClient.A", "Backdoor:MSIL/ShellClient.A!dll", "Trojan:MSIL/Mimikatz.BA!MTB"), 1.0, 0.5)
| project DvcId, AlertRiskScore) on DvcId
| extend AlertRiskScore = iif(isempty(AlertRiskScore), 0.0, AlertRiskScore)
| extend InitiatingProcessAccount = strcat(InitiatingProcessAccountDomain, "\\", InitiatingProcessAccountName)
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
| extend timestamp = TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingProcessAccount
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: NTDomain
columnName: InitiatingProcessAccountDomain
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileName
version: 1.1.2
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - 0-day Vulnerability" ]
Stages and Predicates
Parameters
let files1 = dynamic(["C:\\Windows\\TAPI\\lsa.exe", "C:\\Windows\\TAPI\\pa.exe", "C:\\Windows\\TAPI\\pc.exe", "C:\\Windows\\TAPI\\Rar.exe"]);
let files2 = dynamic(["svchost.exe","wdmsvc.exe"]);
Let Binding: FileHash1
let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77", "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);
Let Binding: FileHash2
let FileHash2 = dynamic(["2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7", "9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd", "18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b"]);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where ( FolderPath has_any (files1) and SHA256 has_any (FileHash1)) or (FolderPath has_any (files2) and SHA256 has_any (FileHash2))
Stage 3: extend
| extend DvcId = DeviceId
Stage 4: join
| join kind=leftouter (SecurityAlert
| where ProviderName =~ "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| mv-expand todynamic(Entities)
| extend DvcId = tostring(parse_json(Entities).MdatpDeviceId)
| where isnotempty(DvcId)
| extend AlertRiskScore = iif(ThreatName has_any ("Backdoor:MSIL/ShellClient.A", "Backdoor:MSIL/ShellClient.A!dll", "Trojan:MSIL/Mimikatz.BA!MTB"), 1.0, 0.5)
| project DvcId, AlertRiskScore) on DvcId
Stage 5: extend (5 consecutive steps)
extend AlertRiskScore, DomainIndex, HostName, HostNameDomain, InitiatingProcessAccount, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcId | is_not_null | field:"DvcId" kind:is_not_null | |
FolderPath | match |
| field:"Image" kind:match |
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
SHA256 | match |
| field:"sha256" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DvcId | extend |
AlertRiskScore | extend |
InitiatingProcessAccount | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
timestamp | extend |
GSA - Detect Abnormal Deny Rate for Source to Destination IP
#Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules. Configurable Parameters: - minimumOfStdsThreshold: The number of stds to use in the threshold calculation. Default is set to 3. - learningPeriodTime: Learning period for threshold calculation in days. Default is set to 5. - binTime: Learning buckets time in hours. Default is set to 1 hour. - minimumThreshold: Minimum threshold for alert. Default is set to 5. - minimumBucketThreshold: Minimum learning buckets threshold for alert. Default is set to 5.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b
name: GSA - Detect Abnormal Deny Rate for Source to Destination IP
description: |
Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules.
Configurable Parameters:
- minimumOfStdsThreshold: The number of stds to use in the threshold calculation. Default is set to 3.
- learningPeriodTime: Learning period for threshold calculation in days. Default is set to 5.
- binTime: Learning buckets time in hours. Default is set to 1 hour.
- minimumThreshold: Minimum threshold for alert. Default is set to 5.
- minimumBucketThreshold: Minimum learning buckets threshold for alert. Default is set to 5.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- NetworkAccessTrafficLogs
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 1
tactics:
- InitialAccess
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1571
query: |
let NumOfStdsThreshold = 3;
let LearningPeriod = 5d;
let BinTime = 1h;
let MinThreshold = 5.0;
let MinLearningBuckets = 5;
let TrafficLogs = NetworkAccessTraffic
| where Action == "Denied"
| where isnotempty(DestinationIp) and isnotempty(SourceIp);
let LearningSrcIpDenyRate = TrafficLogs
| where TimeGenerated between (ago(LearningPeriod + 1d) .. ago(1d))
| summarize count_ = count() by SourceIp, bin(TimeGenerated, BinTime), DestinationIp
| summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, DestinationIp
| where LearningTimeBuckets > MinLearningBuckets;
let AlertTimeSrcIpDenyRate = TrafficLogs
| where TimeGenerated between (ago(1h) .. now())
| summarize AlertTimeSrcIpDenyRateCount = count() by SourceIp, DestinationIp;
AlertTimeSrcIpDenyRate
| join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, DestinationIp
| extend LearningThreshold = max_of(LearningTimeSrcIpDenyRateAvg + NumOfStdsThreshold * LearningTimeSrcIpDenyRateStd, MinThreshold)
| where AlertTimeSrcIpDenyRateCount > LearningThreshold
| project SourceIp, DestinationIp, AlertTimeSrcIpDenyRateCount, LearningThreshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: DestinationIp
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let NumOfStdsThreshold = 3;
let LearningPeriod = 5d;
let BinTime = 1h;
let MinThreshold = 5.0;
let MinLearningBuckets = 5;
let TrafficLogs and let AlertTimeSrcIpDenyRate are inlined into the numbered stages below.
Let Binding: LearningSrcIpDenyRate
let LearningSrcIpDenyRate = TrafficLogs
| where TimeGenerated between (ago(LearningPeriod + 1d) .. ago(1d))
| summarize count_ = count() by SourceIp, bin(TimeGenerated, BinTime), DestinationIp
| summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, DestinationIp
| where LearningTimeBuckets > MinLearningBuckets;
Stages 1 to 5 define let AlertTimeSrcIpDenyRate as the rule's main pipeline source. Stages 6 to 9 use it.
Stage 1: source
NetworkAccessTraffic
Stage 2: where
| where Action == "Denied"
Stage 3: where
| where isnotempty(DestinationIp) and isnotempty(SourceIp)
Stage 4: where
| where TimeGenerated between (ago(1h) .. now())
Stage 5: summarize
| summarize AlertTimeSrcIpDenyRateCount = count() by SourceIp, DestinationIp
Stage 6: join
AlertTimeSrcIpDenyRate
| join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, DestinationIp
Stage 7: extend
extend LearningThreshold
Stage 8: where
where AlertTimeSrcIpDenyRateCount > LearningThreshold
Stage 9: project
project AlertTimeSrcIpDenyRateCount, DestinationIp, LearningThreshold, SourceIp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"Denied" |
DestinationIp | is_not_null | field:"DestinationIp" kind:is_not_null | |
LearningTimeBuckets | gt |
| field:"LearningTimeBuckets" kind:gt value:"5" |
SourceIp | is_not_null | field:"SourceIp" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTimeSrcIpDenyRateCount | project |
DestinationIp | project |
LearningThreshold | project |
SourceIp | project |
Guardian- URL Detection Policy Violation Detection
#'This alert creates an incident when URL Detection Policy Violation detected from the Guardian.'
Rule Body
id: de6f4636-a51a-411d-95d8-4f9099865990
name: Guardian- URL Detection Policy Violation Detection
description: |
'This alert creates an incident when URL Detection Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'URL Detection'
| where Severity =~ 'Informational'
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- URL Detection Policy Violation detection
alertDescriptionFormat: |
This query detects URL Detection 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 =~ 'URL Detection'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"URL Detection" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
Masquerading Renamed executables of interest
#This query searches for the original file name of a set of binaries that is known to be used by attackers. The OriginalFileName field is then matched to the actual file name. Where there isn't a match the results are returned, indicating the file has been renamed. The original file name field is derived from the PE header of the executable, which is the name of the binary during compilation.
Known False Positives
- There might be a legitimate use to rename the binaries. There are some cases where the shipped binary file name is already different than the embedded original file name, for example, with PSExec.exe where the original file name is PSExec.c.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action ProcessCreated: Process created |
Rule Body
let timeframe = 2*1h;
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
let binaries_of_interest = dynamic(["net.exe", "net1.exe", "whoami.exe", "ipconfig.exe", "tasklist.exe", "quser.exe", "tracert.exe", "route.exe", "runas.exe", "klist.exe", "wevtutil.exe", "wmiprvse.exe", "powershell.exe", "bash.exe", "qwinsta.exe", "rwinsta.exe", "replace.exe", "findstr.exe", "icacls.exe", "cacls.exe", "xcopy.exe", "robocopy.exe", "takeown.exe", "vssadmin.exe", "nltest.exe", "nltestk.exe", "sctasks.exe", "nbtstat.exe", "nbtinfo.exe", "mofcomp.exe", "nltestrk.exe", "dnscmd.exe", "registercimprovider.exe", "registercimprovider2.exe", "procdump", "ru.exe", "pspasswd.exe", "psexec.c", "psexec.exe", "pslist.exe", "regsize", "pskill.exe", "pkill.exe", "wsmprovhost.exe", "fltmc.exe", "sdbinst.exe"]);
// Merge both lists into one reference list.
let original_file_name_set=array_concat(lolbins,binaries_of_interest);
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| extend process_name=tolower(FileName)
| extend original_file_name=tolower(ProcessVersionInfoOriginalFileName)
| where original_file_name in~ (original_file_name_set)
| where original_file_name != ""
// Filter some known mismatches between PE header FileName and the binary FileName.
| where not(process_name=~"schtasks.exe" and original_file_name=~"schtasks.exe" and (FolderPath=~@"C:\Windows\System32\schtasks.exe" or FolderPath=~@"C:\Windows\SysWOW64\schtasks.exe"))
| where not(process_name=~"nbtstat.exe" and original_file_name=~"nbtinfo.exe" and FolderPath=~@"C:\Windows\System32\nbtstat.exe")
| where not(process_name=~"bginfo64.exe" and original_file_name=~"bginfo.exe" and (FolderPath=~@"C:\Windows\System32\Bginfo64.exe" or FolderPath =~@"C:\Program Files\SysInternals BGInfo\Bginfo64.exe"))
// Filter MS Excel file format converter.
| where not(process_name=~"excelcnv.exe" and original_file_name=~"excel.exe" and (FolderPath startswith @"C:\Program Files\Microsoft Office Web Apps\ExcelServicesEcs\" or FolderPath startswith @"C:\Program Files\Microsoft Office\" or FolderPath startswith @"C:\Program Files (x86)\Microsoft Office\"))
// Optionally filter this (when psexec is actually common in your environment).
| where not(process_name=~"psexec.exe" and original_file_name=~"psexec.c")
| where not(process_name=~"psexec64.exe" and original_file_name=~"psexec.c")
| where process_name != original_file_name
| project Timestamp,DeviceName,DeviceId,AccountName,process_name, original_file_name, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessVersionInfoOriginalFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName, ReportId, InitiatingProcessAccountUpn
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let original_file_name_set = array_concat(lolbins,binaries_of_interest);
Let Binding: lolbins
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
Let Binding: binaries_of_interest
let binaries_of_interest = dynamic(["net.exe", "net1.exe", "whoami.exe", "ipconfig.exe", "tasklist.exe", "quser.exe", "tracert.exe", "route.exe", "runas.exe", "klist.exe", "wevtutil.exe", "wmiprvse.exe", "powershell.exe", "bash.exe", "qwinsta.exe", "rwinsta.exe", "replace.exe", "findstr.exe", "icacls.exe", "cacls.exe", "xcopy.exe", "robocopy.exe", "takeown.exe", "vssadmin.exe", "nltest.exe", "nltestk.exe", "sctasks.exe", "nbtstat.exe", "nbtinfo.exe", "mofcomp.exe", "nltestrk.exe", "dnscmd.exe", "registercimprovider.exe", "registercimprovider2.exe", "procdump", "ru.exe", "pspasswd.exe", "psexec.c", "psexec.exe", "pslist.exe", "regsize", "pskill.exe", "pkill.exe", "wsmprovhost.exe", "fltmc.exe", "sdbinst.exe"]);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where ingestion_time() >= ago(timeframe)
Stage 3: where
| where ActionType =~ "ProcessCreated"
Stage 4: extend
| extend process_name=tolower(FileName)
Stage 5: extend
| extend original_file_name=tolower(ProcessVersionInfoOriginalFileName)
Stage 6: where
| where original_file_name in~ (original_file_name_set)
Stage 7: where
| where original_file_name != ""
Stage 8: where
| where not(process_name=~"schtasks.exe" and original_file_name=~"schtasks.exe" and (FolderPath=~@"C:\Windows\System32\schtasks.exe" or FolderPath=~@"C:\Windows\SysWOW64\schtasks.exe"))
Stage 9: where
| where not(process_name=~"nbtstat.exe" and original_file_name=~"nbtinfo.exe" and FolderPath=~@"C:\Windows\System32\nbtstat.exe")
Stage 10: where
| where not(process_name=~"bginfo64.exe" and original_file_name=~"bginfo.exe" and (FolderPath=~@"C:\Windows\System32\Bginfo64.exe" or FolderPath =~@"C:\Program Files\SysInternals BGInfo\Bginfo64.exe"))
Stage 11: where
| where not(process_name=~"excelcnv.exe" and original_file_name=~"excel.exe" and (FolderPath startswith @"C:\Program Files\Microsoft Office Web Apps\ExcelServicesEcs\" or FolderPath startswith @"C:\Program Files\Microsoft Office\" or FolderPath startswith @"C:\Program Files (x86)\Microsoft Office\"))
Stage 12: where
| where not(process_name=~"psexec.exe" and original_file_name=~"psexec.c")
Stage 13: where
| where not(process_name=~"psexec64.exe" and original_file_name=~"psexec.c")
Stage 14: where
| where process_name != original_file_name
Stage 15: project
| project Timestamp,DeviceName,DeviceId,AccountName,process_name, original_file_name, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessVersionInfoOriginalFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName, ReportId, InitiatingProcessAccountUpn
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ProcessCreated" |
original_file_name | in |
| field:"original_file_name" kind:in value:"original_file_name_set" |
Exclusions
The rule actively suppresses these predicates.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
DeviceId | project |
DeviceName | project |
FolderPath | project |
InitiatingProcessAccountUpn | project |
InitiatingProcessCommandLine | project |
InitiatingProcessFileName | project |
InitiatingProcessParentFileName | project |
InitiatingProcessVersionInfoOriginalFileName | project |
ProcessCommandLine | project |
ReportId | project |
Timestamp | project |
original_file_name | project |
process_name | project |
Mimecast Targeted Threat Protection - Attachment Protect
#'Detects a threat for an unsafe attachment in an email.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 617a55be-a8d8-49c1-8687-d19a0231056f
name: Mimecast Targeted Threat Protection - Attachment Protect
description: |
'Detects a threat for an unsafe attachment in an email.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MimecastTTPAPI
dataTypes:
- MimecastTTPAttachment
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Discovery
relevantTechniques:
- T0865
query: |
MimecastTTPAttachment
| where Result != "safe"
| extend SenderAddress = ['Sender Address'] ,RecipientAddress = ['Recipient Address']
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: SenderAddress
- identifier: Recipient
columnName: RecipientAddress
- identifier: Subject
columnName: Subject
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastTTPAttachment
Stage 2: where
| where Result != "safe"
Stage 3: extend
| extend SenderAddress = ['Sender Address'] ,RecipientAddress = ['Recipient Address']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Result | ne |
| field:"Result" kind:ne value:"safe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RecipientAddress | extend |
SenderAddress | extend |
NRT Microsoft Entra ID Hybrid Health AD FS New Server
#This detection uses AzureActivity logs (Administrative category) to identify the creation or update of a server instance in an Microsoft Entra ID Hybrid Health AD FS service. A threat actor can create a new AD Health ADFS service and create a fake server instance to spoof AD FS signing logs. There is no need to compromise an on-premises AD FS server. This can be done programmatically via HTTP requests to Azure. More information in this blog: https://o365blog.com/post/hybridhealthagent/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- Azure Active Directory Hybrid Health AD FS New Server (Sigma)
- Azure Active Directory Hybrid Health AD FS Service Delete (Sigma)
- Azure Subscription Permission Elevation Via AuditLogs (Sigma)
- Detect Custom Script or Run Command deployment by risky user (Kusto)
- Microsoft Entra ID Hybrid Health AD FS New Server (Kusto)
- Microsoft Entra ID Hybrid Health AD FS Service Delete (Kusto)
- Microsoft Entra ID Hybrid Health AD FS Suspicious Application (Kusto)
Rule Body
id: ec491363-5fe7-4eff-b68e-f42dcb76fcf6
name: NRT Microsoft Entra ID Hybrid Health AD FS New Server
description: |
'This detection uses AzureActivity logs (Administrative category) to identify the creation or update of a server instance in an Microsoft Entra ID Hybrid Health AD FS service.
A threat actor can create a new AD Health ADFS service and create a fake server instance to spoof AD FS signing logs. There is no need to compromise an on-premises AD FS server.
This can be done programmatically via HTTP requests to Azure. More information in this blog: https://o365blog.com/post/hybridhealthagent/'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
tactics:
- DefenseEvasion
relevantTechniques:
- T1578
tags:
- SimuLand
query: |
AzureActivity
| where CategoryValue =~ 'Administrative'
| where ResourceProviderValue =~ 'Microsoft.ADHybridHealthService'
| where _ResourceId has 'AdFederationService'
| where OperationNameValue =~ 'Microsoft.ADHybridHealthService/services/servicemembers/action'
| extend claimsJson = parse_json(Claims)
| extend AppId = tostring(claimsJson.appid), AccountName = tostring(claimsJson.name), Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
| project-away claimsJson
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
version: 2.0.3
kind: NRT
Stages and Predicates
Stage 1: source
AzureActivity
Stage 2: where
| where CategoryValue =~ 'Administrative'
Stage 3: where
| where ResourceProviderValue =~ 'Microsoft.ADHybridHealthService'
Stage 4: where
| where _ResourceId has 'AdFederationService'
Stage 5: where
| where OperationNameValue =~ 'Microsoft.ADHybridHealthService/services/servicemembers/action'
Stage 6: extend
| extend claimsJson = parse_json(Claims)
Stage 7: extend
| extend AppId = tostring(claimsJson.appid), AccountName = tostring(claimsJson.name), Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
Stage 8: project-away
| project-away claimsJson
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CategoryValue | eq |
| field:"CategoryValue" kind:eq value:"Administrative" |
OperationNameValue | eq |
| field:"azure_ad::operation_name_value" kind:eq value:"Microsoft.ADHybridHealthService/services/servicemembers/action" |
ResourceProviderValue | eq |
| field:"ResourceProviderValue" kind:eq value:"Microsoft.ADHybridHealthService" |
_ResourceId | match |
| field:"_ResourceId" kind:match value:"AdFederationService" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | extend |
AppId | extend |
Name | extend |
UPNSuffix | extend |
OCI - Unexpected user agent
#'Detects unexpected user agent strings.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: a0b9a7ca-3e6d-4996-ae35-759df1d67a54
name: OCI - Unexpected user agent
description: |
'Detects unexpected user agent strings.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: OracleCloudInfrastructureLogsConnector
dataTypes:
- OCILogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
OCILogs
| where isnotempty(HttpUserAgentOriginal)
| where strlen(HttpUserAgentOriginal) <= 10
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
OCILogs
Stage 2: where
| where isnotempty(HttpUserAgentOriginal)
Stage 3: where
| where strlen(HttpUserAgentOriginal) <= 10
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | is_not_null | field:"HttpUserAgentOriginal" kind:is_not_null | |
HttpUserAgentOriginal | le |
| field:"HttpUserAgentOriginal" kind:le value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Tape Medium Deleted
#Detects when a tape medium is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: d92b2d65-1037-41e2-8d04-c18b8403d895
name: Tape Medium Deleted
description: Detects when a tape medium is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 23632\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 23632
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"23632" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
User Accessed Suspicious URL Categories
#'Creates an incident in the event the requested URL accessed by the user has been identified as Suspicious, Phishing, or Hacking.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Rule Body
id: fb0f4a93-d8ad-4b54-9931-85bdb7550f90
name: User Accessed Suspicious URL Categories
description: |
'Creates an incident in the event the requested URL accessed by the user has been identified as Suspicious, Phishing, or Hacking.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1566
- T1071
query: |
SymantecProxySG
| mv-expand cs_categories
| where cs_categories has_any ("Suspicious","phishing", "hacking")
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by sc_filter_result, cs_userdn, c_ip, cs_host, Computer, tostring(cs_categories)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: cs_userdn
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- entityType: IP
fieldMappings:
- identifier: Address
columnName: c_ip
version: 1.0.5
kind: Scheduled
Stages and Predicates
Stage 1: source
SymantecProxySG
Stage 2: mv-expand
| mv-expand cs_categories
Stage 3: where
| where cs_categories has_any ("Suspicious","phishing", "hacking")
Stage 4: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by sc_filter_result, cs_userdn, c_ip, cs_host, Computer, tostring(cs_categories)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
cs_categories | match |
| field:"cs_categories" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
EndTime | summarize |
StartTime | summarize |
c_ip | summarize |
cs_host | summarize |
cs_userdn | summarize |
sc_filter_result | summarize |
VirtualNetworkPeerings Alerts From Prancer
#'High severity virtual network peerings alerts found by Prancer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 6bd031cf-78d0-4edd-8191-60f84b6eef7a
name: VirtualNetworkPeerings Alerts From Prancer
description: |
'High severity virtual network peerings alerts found by Prancer.'
severity: High
requiredDataConnectors:
- connectorId: PrancerLogData
dataTypes:
- prancer_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: SingleAlert
status: Available
tactics:
- Reconnaissance
relevantTechniques:
- T1595
query: |
union prancer_CL
| where deviceProduct_s == 'azure'
| where parse_json(data_data_snapshots_s)[0].type == 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings'
| where data_data_severity_s == 'High' and data_data_result_s == 'failed'
| extend snapshot = parse_json(data_data_snapshots_s)
| mv-expand snapshot
| extend
id = tostring(snapshot.id),
structure = tostring(snapshot.structure),
reference = tostring(snapshot.reference),
source = tostring(snapshot.source),
collection = tostring(snapshot.collection),
type = tostring(snapshot.type),
region = tostring(snapshot.region),
resourceTypes = tostring(snapshot.resourceTypes),
path = tostring(snapshot.path)
customDetails:
entityMappings:
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: path
alertDetailsOverride:
alertDisplayNameFormat: "{{data_data_message_s}}"
alertDescriptionFormat: "{{data_data_description_s}}"
alertSeverityColumnName: "{{data_data_severity_s}}"
alertDynamicProperties:
- alertProperty: RemediationSteps
value: data_data_remediation_description_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: union
union of 1 branches
Stage 2: source
prancer_CL
Stage 3: where
where deviceProduct_s == "azure"
Stage 4: where
where parse_json(data_data_snapshots_s)[0].type == "Microsoft.Network/virtualNetworks/virtualNetworkPeerings"
Stage 5: where
where data_data_result_s == "failed" and data_data_severity_s == "High"
Stage 6: extend
extend snapshot
Stage 7: mv-expand
mv-expand snapshot
Stage 8: extend
extend collection, id, path, reference, region, resourceTypes, source, structure, type
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
data_data_result_s | eq |
| field:"data_data_result_s" kind:eq value:"failed" |
data_data_severity_s | eq |
| field:"data_data_severity_s" kind:eq value:"High" |
data_data_snapshots_s[0].type | eq |
| field:"data_data_snapshots_s[0].type" kind:eq value:"Microsoft.Network/virtualNetworks/virtualNetworkPeerings" |
deviceProduct_s | eq |
| field:"deviceProduct_s" kind:eq value:"azure" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
snapshot | extend |
collection | extend |
id | extend |
path | extend |
reference | extend |
region | extend |
resourceTypes | extend |
source | extend |
structure | extend |
type | extend |
VMware Cloud Web Security - Policy Change Detected
#This Analytics rule provides notifications when a VMware CWS policy has been modified. These alerts serve audit purposes. Policy changes might lower the level of security controls.
Rule Body
id: 3efebd49-c985-431b-9da8-d7d397092d18
name: VMware Cloud Web Security - Policy Change Detected
version: 1.0.0
kind: Scheduled
description: This Analytics rule provides notifications when a VMware CWS policy has been modified. These alerts serve audit purposes. Policy changes might lower the level of security controls.
severity: Informational
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
- connectorId: VMwareSDWAN
dataTypes:
- CWS
query: |+
VMware_VECO_EventLogs_CL
| where event == "CWS_EVENT"
| extend cwsPolicyAction = todynamic(detail).subEvent
| where cwsPolicyAction contains "SECURITY_RULE"
| extend cwsRuleName = todynamic(detail).name
| extend cwsRuleType = todynamic(detail).data.ruleType
| extend cwsPolicyName = todynamic(detail).policyName
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDescriptionFormat: |+
CWS Policy Change Detected:
CWS Policy Name: {{cwsPolicyName}}
- Rule changed: {{cwsRuleName}}
- Audited Action: {{cwsPolicyAction}}
alertDynamicProperties:
- alertProperty: ProductComponentName
value: cwsRuleType
suppressionDuration: 5h
Stages and Predicates
Stage 1: source
VMware_VECO_EventLogs_CL
Stage 2: where
| where event == "CWS_EVENT"
Stage 3: extend
| extend cwsPolicyAction = todynamic(detail).subEvent
Stage 4: where
| where cwsPolicyAction contains "SECURITY_RULE"
Stage 5: extend (3 consecutive steps)
| extend cwsRuleName = todynamic(detail).name
| extend cwsRuleType = todynamic(detail).data.ruleType
| extend cwsPolicyName = todynamic(detail).policyName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
cwsPolicyAction | contains |
| field:"cwsPolicyAction" kind:contains value:"SECURITY_RULE" |
event | eq |
| field:"event" kind:eq value:"CWS_EVENT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
cwsPolicyAction | extend |
cwsRuleName | extend |
cwsRuleType | extend |
cwsPolicyName | extend |