Detection rules › Kusto

Kusto rules: 13

AWSGuardDuty - GuardDuty Alert

#
Status
available
Severity
medium
Time window
5h
Source
github.com/Azure/Azure-Sentinel

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.

FieldSource
ThreatPurposeextend
ResourceTypeAffectedextend
ThreatFamilyNameextend
Artifactextend
DetectionMechanismextend
Severityextend
AccessKeyDetailsextend
RdsDbUserDetailsextend
KubernetesDetailsextend
ServiceActionextend
RemoteIpAddressextend
LocalIpAddressextend
RemoteAWSAccountIdextend
AccountUpnextend
AccountNameextend
UPNSuffixextend
GuardDutyDetailsextend
FindingLinkextend
FindingLinkDescriptionextend
AWSAccountIdproject-rename
FindingArnproject-rename
FindingIdproject-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.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'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

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.

FieldKindValuesSearch
Serviceeq
  • darkweb_marketplaces
field:"Service" kind:eq value:"darkweb_marketplaces"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Detect presence of private IP addresses in URLs (ASIM Web Session)

#
Status
available
Severity
medium
Time window
1h
Group by
SrcHostname, SrcIpAddr, SrcUsername, extracted_encoded_ip_inURL, ip_inURL
Source
github.com/Azure/Azure-Sentinel

'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

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.

FieldKindValuesSearch
Urlis_not_null
  • (no value, null check)
field:"Url" kind:is_not_null
Urlregex_match
  • \=([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\/+\=]*)
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_inURLcidr_match
  • 10.0.0.0/8
  • 127.0.0.0/8
  • 169.254.0.0/16
  • 172.16.0.0/12
  • 192.168.0.0/16
field:"decoded_ip_inURL" kind:cidr_match
ip_inURLcidr_match
  • 10.0.0.0/8
  • 127.0.0.0/8
  • 169.254.0.0/16
  • 172.16.0.0/12
  • 192.168.0.0/16
field:"ip_inURL" kind:cidr_match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DstIpAddrcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:DstIpAddr

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCountproject
EventEndTimeproject
EventStartTimeproject
SrcHostnameproject
SrcIpAddrproject
SrcUsernameproject
Urlsproject
ip_inURLproject
Nameextend
UPNSuffixextend

Detect unauthorized data transfers using timeseries anomaly (ASIM Web Session)

#
Status
available
Severity
medium
Time window
14d
Group by
AnomalyHour, BaselineBytesSentinMBperHour, EventProduct, TotalBytesSentinMBperHour, anomalies, exists, score, sumexist
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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 used in Stages 3, 8, 13

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 used in Stages 4, 9, 14

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 used in Stage 29

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.

FieldKindExcluded valuesSearch
DstIpAddrcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:DstIpAddr
DstIPIsPrivate_beqtrueexcludes:DstIPIsPrivate_b field:"DstIPIsPrivate_b" value:"true"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AnomalyHourproject
BaselineBytesSentinMBperHourproject
DestinationIPListproject
DestinationPortListproject
EndTimeUtcproject
EventCountproject
EventProductproject
ReceivedBytesinMBproject
SentBytesinMBproject
SourceIPproject
SourceIPListproject
TotalBytesSentinMBperHourproject
anomaliesproject
scoreproject
startTimeUtcproject

Dev-0228 File Path Hashes November 2021

#
Severity
high
Time window
6h
Group by
DvcId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Execution
Credential Access

Telemetry Coverage

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 used in Stage 2

let FileHash1 = dynamic(["43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3", "ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb", "010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77",         "56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7"]);

Let Binding: FileHash2 used in Stage 2

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.

FieldKindValuesSearch
DvcIdis_not_null
  • (no value, null check)
field:"DvcId" kind:is_not_null
FolderPathmatch
  • C:\Windows\TAPI\Rar.exe transforms: term
  • C:\Windows\TAPI\lsa.exe transforms: term
  • C:\Windows\TAPI\pa.exe transforms: term
  • C:\Windows\TAPI\pc.exe transforms: term
  • svchost.exe transforms: term
  • wdmsvc.exe transforms: term
field:"Image" kind:match
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"
SHA256match
  • 010e32be0f86545e116a8bc3381a8428933eb8789f32c261c81fd5e7857d4a77 transforms: term
  • 18a072ccfab239e140d8f682e2874e8ff19d94311fc8bb9564043d3e0deda54b transforms: term
  • 2a1044e9e6e87a032f80c6d9ea6ae61bbbb053c0a21b186ecb3b812b49eb03b7 transforms: term
  • 43109fbe8b752f7a9076eaafa417d9ae5c6e827cd5374b866672263fdebd5ec3 transforms: term
  • 56cd102b9fc7f3523dad01d632525ff673259dbc9a091be0feff333c931574f7 transforms: term
  • 9ab7e99ed84f94a7b6409b87e56dc6e1143b05034a5e4455e8c555dbbcd0d2dd transforms: term
  • ab50d8d707b97712178a92bbac74ccc2a5699eb41c17aa77f713ff3e568dcedb transforms: term
field:"sha256" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
DvcIdextend
AlertRiskScoreextend
InitiatingProcessAccountextend
DomainIndexextend
HostNameextend
HostNameDomainextend
timestampextend

GSA - Detect Abnormal Deny Rate for Source to Destination IP

#
Status
available
Severity
medium
Time window
7d
Group by
DestinationIp, SourceIp
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
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 used in Stage 6

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTimeSrcIpDenyRateCountproject
DestinationIpproject
LearningThresholdproject
SourceIpproject

Guardian- URL Detection Policy Violation Detection

#
Status
available
Severity
informational
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'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.

Masquerading Renamed executables of interest

#
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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

References

Telemetry Coverage

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
FolderPatheqC:\Windows\SysWOW64\schtasks.exeexcludes:FolderPath field:"FolderPath" value:"C:\Windows\SysWOW64\schtasks.exe"
FolderPatheqC:\Windows\System32\schtasks.exeexcludes:FolderPath field:"FolderPath" value:"C:\Windows\System32\schtasks.exe"
original_file_nameeqschtasks.exeexcludes:original_file_name field:"original_file_name" value:"schtasks.exe"
process_nameeqschtasks.exeexcludes:process_name field:"process_name" value:"schtasks.exe"
FolderPatheqC:\Windows\System32\nbtstat.exeexcludes:FolderPath field:"FolderPath" value:"C:\Windows\System32\nbtstat.exe"
original_file_nameeqnbtinfo.exeexcludes:original_file_name field:"original_file_name" value:"nbtinfo.exe"
process_nameeqnbtstat.exeexcludes:process_name field:"process_name" value:"nbtstat.exe"
FolderPatheqC:\Program Files\SysInternals BGInfo\Bginfo64.exeexcludes:FolderPath field:"FolderPath" value:"C:\Program Files\SysInternals BGInfo\Bginfo64.exe"
FolderPatheqC:\Windows\System32\Bginfo64.exeexcludes:FolderPath field:"FolderPath" value:"C:\Windows\System32\Bginfo64.exe"
original_file_nameeqbginfo.exeexcludes:original_file_name field:"original_file_name" value:"bginfo.exe"
process_nameeqbginfo64.exeexcludes:process_name field:"process_name" value:"bginfo64.exe"
FolderPathstarts_withC:\Program Files (x86)\Microsoft Office\excludes:FolderPath field:"FolderPath" value:"C:\Program Files (x86)\Microsoft Office\"
FolderPathstarts_withC:\Program Files\Microsoft Office Web Apps\ExcelServicesEcs\excludes:FolderPath field:"FolderPath" value:"C:\Program Files\Microsoft Office Web Apps\ExcelServicesEcs\"
FolderPathstarts_withC:\Program Files\Microsoft Office\excludes:FolderPath field:"FolderPath" value:"C:\Program Files\Microsoft Office\"
original_file_nameeqexcel.exeexcludes:original_file_name field:"original_file_name" value:"excel.exe"
process_nameeqexcelcnv.exeexcludes:process_name field:"process_name" value:"excelcnv.exe"
original_file_nameeqpsexec.cexcludes:original_file_name field:"original_file_name" value:"psexec.c"
process_nameeqpsexec.exeexcludes:process_name field:"process_name" value:"psexec.exe"
process_nameeqpsexec64.exeexcludes:process_name field:"process_name" value:"psexec64.exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
DeviceIdproject
DeviceNameproject
FolderPathproject
InitiatingProcessAccountUpnproject
InitiatingProcessCommandLineproject
InitiatingProcessFileNameproject
InitiatingProcessParentFileNameproject
InitiatingProcessVersionInfoOriginalFileNameproject
ProcessCommandLineproject
ReportIdproject
Timestampproject
original_file_nameproject
process_nameproject

Mimecast Targeted Threat Protection - Attachment Protect

#
Status
available
Severity
high
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Detects a threat for an unsafe attachment in an email.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
RecipientAddressextend
SenderAddressextend

NRT Microsoft Entra ID Hybrid Health AD FS New Server

#
Status
available
Severity
medium
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Defense Impairment

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

FieldKindValuesSearch
CategoryValueeq
  • Administrative corpus 6 (kusto 4, sigma 2)
field:"CategoryValue" kind:eq value:"Administrative"
OperationNameValueeq
  • Microsoft.ADHybridHealthService/services/servicemembers/action corpus 3 (kusto 2, sigma 1)
field:"azure_ad::operation_name_value" kind:eq value:"Microsoft.ADHybridHealthService/services/servicemembers/action"
ResourceProviderValueeq
  • Microsoft.ADHybridHealthService corpus 6 (kusto 4, sigma 2)
field:"ResourceProviderValue" kind:eq value:"Microsoft.ADHybridHealthService"
_ResourceIdmatch
  • AdFederationService transforms: term corpus 4 (kusto 4)
field:"_ResourceId" kind:match value:"AdFederationService"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AppIdextend
Nameextend
UPNSuffixextend

OCI - Unexpected user agent

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects unexpected user agent strings.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

Tape Medium Deleted

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

User Accessed Suspicious URL Categories

#
Status
available
Severity
medium
Time window
1h
Group by
Computer, c_ip, cs_host, cs_userdn, sc_filter_result
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

FieldKindValuesSearch
cs_categoriesmatch
  • Suspicious transforms: term
  • hacking transforms: term
  • phishing transforms: term
field:"cs_categories" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
EndTimesummarize
StartTimesummarize
c_ipsummarize
cs_hostsummarize
cs_userdnsummarize
sc_filter_resultsummarize

VirtualNetworkPeerings Alerts From Prancer

#
Status
available
Severity
high
Time window
5h
Source
github.com/Azure/Azure-Sentinel

'High severity virtual network peerings alerts found by Prancer.'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
data_data_result_seq
  • failed
field:"data_data_result_s" kind:eq value:"failed"
data_data_severity_seq
  • High
field:"data_data_severity_s" kind:eq value:"High"
data_data_snapshots_s[0].typeeq
  • Microsoft.Network/virtualNetworks/virtualNetworkPeerings transforms: kusto_accessor_expr:{"expression":"parse_json(data_data_snapshots_s)[0].type","field":"data_data_snapshots_s[0].type"}, kusto_dynamic_path
field:"data_data_snapshots_s[0].type" kind:eq value:"Microsoft.Network/virtualNetworks/virtualNetworkPeerings"
deviceProduct_seq
  • azure
field:"deviceProduct_s" kind:eq value:"azure"

Output Fields

These fields are emitted when the rule matches.

FieldSource
snapshotextend
collectionextend
idextend
pathextend
referenceextend
regionextend
resourceTypesextend
sourceextend
structureextend
typeextend

VMware Cloud Web Security - Policy Change Detected

#
Severity
informational
Time window
1h
Source
github.com/Azure/Azure-Sentinel

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
cwsPolicyActionextend
cwsRuleNameextend
cwsRuleTypeextend
cwsPolicyNameextend

Back to all Kusto rules