Detection rules › Kusto

Kusto rules: 2k

CiscoISE - Backup failed

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

'Detects when backup activity failed.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 4eddd44a-25e4-41af-930d-0c17218bec74
name: CiscoISE - Backup failed
description: |
  'Detects when backup activity failed.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1490
query: |
  let lbtime = 6h;
  CiscoISEEvent
  | where TimeGenerated > ago(lbtime)
  | where EventId in ('60095', '60098')
  | project TimeGenerated, DvcHostname, DvcIpAddr
  | extend HostCustomEntity = DvcHostname
  | extend IPCustomEntity = DvcIpAddr
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let lbtime = 6h;

Stage 1: source

CiscoISEEvent

Stage 2: where

| where TimeGenerated > ago(lbtime)

Stage 3: where

| where EventId in ('60095', '60098')

Stage 4: project

| project TimeGenerated, DvcHostname, DvcIpAddr

Stage 5: extend

| extend HostCustomEntity = DvcHostname

Stage 6: extend

| extend IPCustomEntity = DvcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIdin
  • 60095
  • 60098
field:"EventId" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DvcHostnameproject
DvcIpAddrproject
TimeGeneratedproject
HostCustomEntityextend
IPCustomEntityextend

Digital Guardian - Exfiltration using DNS protocol

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

'Detects exfiltration using DNS protocol.'

MITRE ATT&CK Coverage

Rule Body

id: 39e25deb-49bb-4cdb-89c1-c466d596e2bd
name: Digital Guardian - Exfiltration using DNS protocol
description: |
  'Detects exfiltration using DNS protocol.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  DigitalGuardianDLPEvent
  | where DstPortNumber == 53
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

DigitalGuardianDLPEvent

Stage 2: where

| where DstPortNumber == 53

Stage 3: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

Distributed Password cracking attempts in Microsoft Entra ID

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

Identifies distributed password cracking attempts from the Microsoft Entra ID SigninLogs. The query looks for unusually high number of failed password attempts coming from multiple locations for a user account. References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes 50053 Account is locked because the user tried to sign in too many times with an incorrect user ID or password. 50055 Invalid password, entered expired password. 50056 Invalid or null password - Password does not exist in store for this user. 50126 Invalid username or password, or invalid on-premises username or password.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: bfb1c90f-8006-4325-98be-c7fffbc254d6
name: Distributed Password cracking attempts in Microsoft Entra ID
description: |
  'Identifies distributed password cracking attempts from the Microsoft Entra ID SigninLogs.
  The query looks for unusually high number of failed password attempts coming from multiple locations for a user account.
  References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
  50053   Account is locked because the user tried to sign in too many times with an incorrect user ID or password.
  50055   Invalid password, entered expired password.
  50056   Invalid or null password - Password does not exist in store for this user.
  50126   Invalid username or password, or invalid on-premises username or password.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let s_threshold = 30;
  let l_threshold = 3;
  let aadFunc = (tableName:string){
  table(tableName)
  | where OperationName =~ "Sign-in activity"
  // Error codes that we want to look at as they are related to the use of incorrect password.
  | where ResultType in ("50126", "50053" , "50055", "50056")
  | extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
  | extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
  | extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
  | extend LocationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city))
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), LocationCount=dcount(LocationString), Location = make_set(LocationString,100),
  IPAddress = make_set(IPAddress,100), IPAddressCount = dcount(IPAddress), AppDisplayName = make_set(AppDisplayName,100), ResultDescription = make_set(ResultDescription,50),
  Browser = make_set(Browser,20), OS = make_set(OS,20), SigninCount = count() by UserPrincipalName, Type
  // Setting a generic threshold - Can be different for different environment
  | where SigninCount > s_threshold and LocationCount >= l_threshold
  | extend Location = tostring(Location), IPAddress = tostring(IPAddress), AppDisplayName = tostring(AppDisplayName), ResultDescription = tostring(ResultDescription), Browser = tostring(Browser), OS = tostring(OS)
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])};
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let s_threshold = 30;
let l_threshold = 3;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let Binding: aadFunc

let aadFunc = (tableName:string){
table(tableName)
| where OperationName =~ "Sign-in activity"
| where ResultType in ("50126", "50053" , "50055", "50056")
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend LocationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city))
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), LocationCount=dcount(LocationString), Location = make_set(LocationString,100),
IPAddress = make_set(IPAddress,100), IPAddressCount = dcount(IPAddress), AppDisplayName = make_set(AppDisplayName,100), ResultDescription = make_set(ResultDescription,50),
Browser = make_set(Browser,20), OS = make_set(OS,20), SigninCount = count() by UserPrincipalName, Type
| where SigninCount > s_threshold and LocationCount >= l_threshold
| extend Location = tostring(Location), IPAddress = tostring(IPAddress), AppDisplayName = tostring(AppDisplayName), ResultDescription = tostring(ResultDescription), Browser = tostring(Browser), OS = tostring(OS)
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])};

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: aadSignin, aadNonInt

Leg 1: aadSignin

Leg 2: aadNonInt

Excessive Failed Authentication from Invalid Inputs

#
Status
available
Severity
medium
Time window
15m
Group by
ClientIP, User
Source
github.com/Azure/Azure-Sentinel

'Creates an incident in the event that a user generates an excessive amount of failed authentications due to invalid inputs, indications of a potential brute force.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: c775a46b-21b1-46d7-afa6-37e3e577a27b
name: Excessive Failed Authentication from Invalid Inputs
description: |
  'Creates an incident in the event that a user generates an excessive amount of failed authentications due to invalid inputs, indications of a potential brute force.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let threshold = 15;
  SymantecVIP
  | where isnotempty(RADIUSAuth)
  | where RADIUSAuth =~ "Reject"
  | summarize Total = count() by bin(TimeGenerated, 15m), User, ClientIP
  | where Total > threshold
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 1.0.4
status: Available
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 15;

Stage 1: source

SymantecVIP

Stage 2: where

| where isnotempty(RADIUSAuth)

Stage 3: where

| where RADIUSAuth =~ "Reject"

Stage 4: summarize

| summarize Total = count() by bin(TimeGenerated, 15m), User, ClientIP

Stage 5: where

| where Total > threshold

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPsummarize
Totalsummarize
Usersummarize

Idira - Sensitive Safe/Permission/Entitlement Changes (with customData)

#
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Alerts on control-plane modifications: safes, permissions, roles, entitlements, policy changes. Leverages customData fields such as changeType/role/permission/policy/entitlement to reduce misses.

MITRE ATT&CK Coverage

TacticTechniques
Privilege EscalationNo specific technique

Rule Body

id: 30938118-8812-4b5f-afa4-a8d4ba2b5d86
kind: Scheduled
name: Idira - Sensitive Safe/Permission/Entitlement Changes (with customData)
description: >
  Alerts on control-plane modifications: safes, permissions, roles, entitlements, policy changes.
  Leverages customData fields such as changeType/role/permission/policy/entitlement to reduce misses.
severity: Low
tactics:
  - PrivilegeEscalation
queryFrequency: 10M
queryPeriod: 1D
triggerOperator: GreaterThan
triggerThreshold: 0
query: |
  let indicators = dynamic(["SafeMember","Permission","Access","ACL","Owner","Role","Entitlement","Policy"]);
  let changeVerbs = dynamic(["grant","revoke","assign","elevate","enable","disable","remove","delete","update"]);
  let actions = dynamic(["policy-add", "policy-change"]);
  CyberArk_AuditEvents_CL
  | extend cd = parse_json(tostring(customData))
  | extend cd_changeType = tolower(tostring(cd.changeType)),
           cd_role       = tostring(cd.role),
           cd_perm       = tostring(cd.permission),
           cd_policy     = tostring(cd.policy),
           cd_entitle    = tostring(cd.entitlement),
           cd_description= tostring(cd.description),
           cd_action     = tostring(cd.action)
  | where 
      action has_any(actions) and
      (auditType has_any (indicators)
      or message has_any (indicators)
      or cd_description has_any (indicators)
      or cd_action has_any (indicators)
      or cd_role !~ "" or cd_perm !~ "" or cd_policy !~ "" or cd_entitle !~ ""
      or cd_changeType in (changeVerbs))
  | project TimeGenerated, CyberArkTenantId, serviceName, username, userId, safe,
            action, actionType, auditType,
            cd_action, cd_description, cd_changeType, cd_role, cd_perm, cd_policy, cd_entitle,
            target, message
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: username
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: target
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Parameters

let indicators = dynamic(["SafeMember","Permission","Access","ACL","Owner","Role","Entitlement","Policy"]);
let changeVerbs = dynamic(["grant","revoke","assign","elevate","enable","disable","remove","delete","update"]);
let actions = dynamic(["policy-add", "policy-change"]);

Stage 1: source

CyberArk_AuditEvents_CL

Stage 2: extend

| extend cd = parse_json(tostring(customData))

Stage 3: extend

| extend cd_changeType = tolower(tostring(cd.changeType)),
         cd_role       = tostring(cd.role),
         cd_perm       = tostring(cd.permission),
         cd_policy     = tostring(cd.policy),
         cd_entitle    = tostring(cd.entitlement),
         cd_description= tostring(cd.description),
         cd_action     = tostring(cd.action)

Stage 4: where

| where 
    action has_any(actions) and
    (auditType has_any (indicators)
    or message has_any (indicators)
    or cd_description has_any (indicators)
    or cd_action has_any (indicators)
    or cd_role !~ "" or cd_perm !~ "" or cd_policy !~ "" or cd_entitle !~ ""
    or cd_changeType in (changeVerbs))

Stage 5: project

| project TimeGenerated, CyberArkTenantId, serviceName, username, userId, safe,
          action, actionType, auditType,
          cd_action, cd_description, cd_changeType, cd_role, cd_perm, cd_policy, cd_entitle,
          target, message

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
actionmatch
  • policy-add transforms: term
  • policy-change transforms: term
field:"action" kind:match
auditTypematch
  • ACL transforms: term
  • Access transforms: term
  • Entitlement transforms: term
  • Owner transforms: term
  • Permission transforms: term
  • Policy transforms: term
  • Role transforms: term
  • SafeMember transforms: term
field:"auditType" kind:match
cd_actionmatch
  • ACL transforms: term
  • Access transforms: term
  • Entitlement transforms: term
  • Owner transforms: term
  • Permission transforms: term
  • Policy transforms: term
  • Role transforms: term
  • SafeMember transforms: term
field:"cd_action" kind:match
cd_changeTypein
  • assign
  • delete
  • disable
  • elevate
  • enable
  • grant
  • remove
  • revoke
  • update
field:"cd_changeType" kind:in
cd_descriptionmatch
  • ACL transforms: term
  • Access transforms: term
  • Entitlement transforms: term
  • Owner transforms: term
  • Permission transforms: term
  • Policy transforms: term
  • Role transforms: term
  • SafeMember transforms: term
field:"cd_description" kind:match
messagematch
  • ACL transforms: term
  • Access transforms: term
  • Entitlement transforms: term
  • Owner transforms: term
  • Permission transforms: term
  • Policy transforms: term
  • Role transforms: term
  • SafeMember transforms: term
field:"message" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
CyberArkTenantIdproject
TimeGeneratedproject
actionproject
actionTypeproject
auditTypeproject
cd_actionproject
cd_changeTypeproject
cd_descriptionproject
cd_entitleproject
cd_permproject
cd_policyproject
cd_roleproject
messageproject
safeproject
serviceNameproject
targetproject
userIdproject
usernameproject

Internet Access (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect an OT device communicating with Internet which is possibly an indication of improper configuration of an application or malicious activity on the network.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Lateral Movement

Rule Body

id: 9ff3b13b-287a-4ed0-8f6b-7e7b66cbbcbd
name: Internet Access (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect an OT device communicating with Internet which is possibly an indication of improper configuration of an application or malicious activity on the network.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T0886
query: |
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName has "Internet"
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName has "Internet"

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamematch
  • Internet transforms: term
field:"AlertName" kind:match value:"Internet"
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

Malicious web application requests linked with Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts

#
Severity
medium
Time window
7d
Group by
AttackerIP, FileName, HostName, ShellLocation, SiteName, SystemAlertId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Takes Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts where web scripts are present in the evidence and correlates with requests made to those scripts in the WCSIISLog to surface new alerts for potentially malicious web request activity. The lookback for alerts is set to 1h and the lookback for W3CIISLogs is set to 7d. A sample set of popular web script extensions has been provided in scriptExtensions that should be tailored to your environment.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: fbfbf530-506b-49a4-81ad-4030885a195c
name: Malicious web application requests linked with Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts
description: |
  'Takes Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts where web scripts are present in the evidence and correlates with requests made to those scripts in the WCSIISLog to surface new alerts for potentially malicious web request activity.
  The lookback for alerts is set to 1h and the lookback for W3CIISLogs is set to 7d. A sample set of popular web script extensions has been provided in scriptExtensions that should be tailored to your environment.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftDefenderAdvancedThreatProtection
    dataTypes:
      - SecurityAlert
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1505
query: |
  let alertTimeWindow = 1h;
  let logTimeWindow = 7d;
  // Define script extensions that suit your web application environment - a sample are provided below
  let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);
  let alertData = materialize(SecurityAlert
  | where TimeGenerated > ago(alertTimeWindow)
  | where ProviderName == "MDATP"
  // Parse and expand the alert JSON
  | extend alertData = parse_json(Entities)
  | mvexpand alertData);
  let fileData = alertData
  // Extract web script files from MDATP alerts - our malicious web scripts - candidate webshells
  | where alertData.Type =~ "file"
  | where alertData.Name has_any(scriptExtensions)
  | extend FileName = tostring(alertData.Name), Directory = tostring(alertData.Directory);
  let hostData = alertData
  // Extract server details from alerts and map to alert id
  | where alertData.Type =~ "host"
  | project HostName = tostring(alertData.HostName), DnsDomain = tostring(alertData.DnsDomain), SystemAlertId
  | distinct HostName, DnsDomain, SystemAlertId;
  // Join the files on their impacted servers
  let webshellData = fileData
  | join kind=inner (hostData) on SystemAlertId
  | project TimeGenerated, FileName, Directory, HostName, DnsDomain;
  webshellData
  | join (
  // Find requests that were made to this file on the impacted server in the W3CIISLog table
  W3CIISLog
  | where TimeGenerated > ago(logTimeWindow)
  // Restrict to accesses to script extensions
  | where csUriStem has_any(scriptExtensions)
  | extend splitUriStem = split(csUriStem, "/")
  | extend FileName = splitUriStem[-1], HostName = sComputerName
  // Summarize potential attacker activity
  | summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), RequestUserAgents=make_set(csUserAgent), ReqestMethods=make_set(csMethod), RequestStatusCodes=make_set(scStatus), RequestCookies=make_set(csCookie), RequestReferers=make_set(csReferer), RequestQueryStrings=make_set(csUriQuery) by AttackerIP=cIP, SiteName=sSiteName, ShellLocation=csUriStem, tostring(FileName), HostName
  ) on FileName, HostName
  | project StartTime, EndTime, AttackerIP, RequestUserAgents, Computer = HostName, SiteName, ShellLocation, ReqestMethods, RequestStatusCodes, RequestCookies, RequestReferers, RequestQueryStrings, RequestCount = count_
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: AttackerIP
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Scheduled
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let alertTimeWindow = 1h;
let logTimeWindow = 7d;
let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);

let alertData, let fileData and let webshellData are inlined into the numbered stages below.

Let Binding: hostData used in Stage 9

let hostData = alertData
| where alertData.Type =~ "host"
| project HostName = tostring(alertData.HostName), DnsDomain = tostring(alertData.DnsDomain), SystemAlertId
| distinct HostName, DnsDomain, SystemAlertId;

Stages 1 to 10 define let webshellData as the rule's main pipeline source. Stages 11 to 15 use it.

Stage 1: source

SecurityAlert

Stage 2: where

where TimeGenerated > ago(3600s)

Stage 3: where

where ProviderName == "MDATP"

Stage 4: extend

extend alertData

Stage 5: mv-expand

mv-expand alertData

Stage 6: where

where alertData.Type =~ "file"

Stage 7: where

where (alertData.Name has ".php" or alertData.Name has ".jsp" or alertData.Name has ".js" or alertData.Name has ".aspx" or alertData.Name has ".asmx" or alertData.Name has ".asax" or alertData.Name has ".cfm" or alertData.Name has ".shtml")

Stage 8: extend

extend Directory, FileName

Stage 9: join

| join kind=inner (hostData) on SystemAlertId

Stage 10: project

project Directory, DnsDomain, FileName, HostName, TimeGenerated

Stage 11: join

webshellData
| join (
W3CIISLog
| where TimeGenerated > ago(logTimeWindow)
| where csUriStem has_any(scriptExtensions)
| extend splitUriStem = split(csUriStem, "/")
| extend FileName = splitUriStem[-1], HostName = sComputerName
| summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), RequestUserAgents=make_set(csUserAgent), ReqestMethods=make_set(csMethod), RequestStatusCodes=make_set(scStatus), RequestCookies=make_set(csCookie), RequestReferers=make_set(csReferer), RequestQueryStrings=make_set(csUriQuery) by AttackerIP=cIP, SiteName=sSiteName, ShellLocation=csUriStem, tostring(FileName), HostName
) on FileName, HostName

Stage 12: project

project AttackerIP, Computer, EndTime, ReqestMethods, RequestCookies, RequestCount, RequestQueryStrings, RequestReferers, RequestStatusCodes, RequestUserAgents, ShellLocation, SiteName, StartTime

Stage 13: extend

extend DomainIndex, HostName

Stage 14: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Stage 15: summarize aggregation inside the join branch

summarize by AttackerIP, SiteName, ShellLocation, HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP corpus 3 (kusto 3)
field:"ProviderName" kind:eq value:"MDATP"
alertData.Namematch
  • .asax transforms: term, kusto_dynamic_path
  • .asmx transforms: term, kusto_dynamic_path
  • .aspx transforms: term, kusto_dynamic_path
  • .cfm transforms: term, kusto_dynamic_path
  • .js transforms: term, kusto_dynamic_path
  • .jsp transforms: term, kusto_dynamic_path
  • .php transforms: term, kusto_dynamic_path
  • .shtml transforms: term, kusto_dynamic_path
field:"alertData.Name" kind:match
alertData.Typeeq
  • file transforms: kusto_dynamic_path
  • host transforms: kusto_dynamic_path
field:"alertData.Type" kind:eq
csUriStemmatch
  • .asax transforms: term
  • .asmx transforms: term
  • .aspx transforms: term
  • .cfm transforms: term
  • .js transforms: term
  • .jsp transforms: term
  • .php transforms: term
  • .shtml transforms: term
field:"csUriStem" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
AttackerIPsummarize
HostNamesummarize
ShellLocationsummarize
SiteNamesummarize

McAfee ePO - Deployment failed

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

Detects when errors occur during deployment new changes/policies.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

Rule Body

id: 155243f4-d962-4717-8a7b-b15b6d112660
name: McAfee ePO - Deployment failed
description: |
  'Detects when errors occur during deployment new changes/policies.'
severity: High
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  McAfeeEPOEvent
  | where EventId == '2412'
  | extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

McAfeeEPOEvent

Stage 2: where

| where EventId == '2412'

Stage 3: extend

| extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostCustomEntityextend
IPCustomEntityextend

Netskope - Unsanctioned/Risky Cloud App Access (Shadow IT)

#
Status
available
Severity
medium
Time window
1h
Group by
CsUsername, XCCountry, XCDevice, XCsApp, XCsAppCategory, XCsAppCci, XCsAppCcl, XCsAppTags
Source
github.com/Azure/Azure-Sentinel

Alerts when users access unsanctioned or risky cloud applications based on Cloud Confidence Level (CCL) and app tags. Detects Shadow IT usage.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Exfiltration

Rule Body

id: cdc01279-d6ea-41b1-a32d-49d726be95b8
name: Netskope - Unsanctioned/Risky Cloud App Access (Shadow IT)
description: |
  Alerts when users access unsanctioned or risky cloud applications based on Cloud Confidence Level (CCL) and app tags. Detects Shadow IT usage.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Exfiltration
relevantTechniques:
  - T1199
  - T1567
query: |
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername) and isnotempty(XCsApp)
  | where XCsAppTags contains 'Unsanctioned' 
      or XCsAppCcl =~ 'poor'
      or XCsAppCcl =~ 'low'
      or XCsAppCci < 50
  | summarize 
      EventCount = count(),
      TotalBytesMB = round(sum(Bytes) / 1048576.0, 2),
      Activities = make_set(XCsAppActivity),
      FirstSeen = min(TimeGenerated),
      LastSeen = max(TimeGenerated)
      by 
      CsUsername,
      XCsApp,
      XCsAppCategory,
      XCsAppCcl,
      XCsAppCci,
      XCsAppTags,
      XCCountry,
      XCDevice
  | extend RiskLevel = case(
      XCsAppCci < 30, 'Critical',
      XCsAppCci < 50, 'High',
      XCsAppCci < 70, 'Medium',
      'Low')
  | project 
      TimeGenerated = LastSeen,
      User = CsUsername,
      RiskyApplication = XCsApp,
      AppCategory = XCsAppCategory,
      CloudConfidenceLevel = XCsAppCcl,
      CloudConfidenceIndex = XCsAppCci,
      AppTags = XCsAppTags,
      RiskLevel,
      Country = XCCountry,
      Device = XCDevice,
      Activities,
      EventCount,
      DataTransferMB = TotalBytesMB
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: RiskyApplication
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername) and isnotempty(XCsApp)

Stage 4: where

| where XCsAppTags contains 'Unsanctioned' 
    or XCsAppCcl =~ 'poor'
    or XCsAppCcl =~ 'low'
    or XCsAppCci < 50

Stage 5: summarize

| summarize 
    EventCount = count(),
    TotalBytesMB = round(sum(Bytes) / 1048576.0, 2),
    Activities = make_set(XCsAppActivity),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by 
    CsUsername,
    XCsApp,
    XCsAppCategory,
    XCsAppCcl,
    XCsAppCci,
    XCsAppTags,
    XCCountry,
    XCDevice

Stage 6: extend

| extend RiskLevel = case(
    XCsAppCci < 30, 'Critical',
    XCsAppCci < 50, 'High',
    XCsAppCci < 70, 'Medium',
    'Low')
RiskLevel =
ifXCsAppCci < 30'Critical'
elifXCsAppCci < 50'High'
elifXCsAppCci < 70'Medium'
else'Low'

Stage 7: project

| project 
    TimeGenerated = LastSeen,
    User = CsUsername,
    RiskyApplication = XCsApp,
    AppCategory = XCsAppCategory,
    CloudConfidenceLevel = XCsAppCcl,
    CloudConfidenceIndex = XCsAppCci,
    AppTags = XCsAppTags,
    RiskLevel,
    Country = XCCountry,
    Device = XCDevice,
    Activities,
    EventCount,
    DataTransferMB = TotalBytesMB

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activitiesproject
AppCategoryproject
AppTagsproject
CloudConfidenceIndexproject
CloudConfidenceLevelproject
Countryproject
DataTransferMBproject
Deviceproject
EventCountproject
RiskLevelproject
RiskyApplicationproject
TimeGeneratedproject
Userproject

Pathlock TDnR - SAP Security Audit Log Events

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

Detects security-relevant events from the SAP Security Audit Log (SM20), forwarded by Pathlock Threat Detection and Response. The Security Audit Log captures critical security events including failed logons, authorization failures, and restricted transactions - anomalies here are strong indicators of attack activity or insider threats.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment
Discovery

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c56
name: Pathlock TDnR - SAP Security Audit Log Events
kind: Scheduled
description: >-
  Detects security-relevant events from the SAP Security Audit Log (SM20), forwarded by Pathlock
  Threat Detection and Response. The Security Audit Log captures critical security events including
  failed logons, authorization failures, and restricted transactions - anomalies here are
  strong indicators of attack activity or insider threats.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - DefenseEvasion
relevantTechniques:
  - T1082
  - T1562
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SECURITY_AUDIT_LOG"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "SECURITY_AUDIT_LOG"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Potential NTLM Relay Attack to Domain Controller

#
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below query detects NTLM authentication coming from Domain Controller machine accounts. This is not an expected behavior and it's an indication of NTLM relay attack.
If NTLM Relaying is done towards a Linux machine, this query won't detect that. The attacker must have access to a Linux device in that case though.

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique

Telemetry Coverage

Rule Body

// Author       : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-petitpotam-and-other-domain-controller-account-takeovers-d3364bd9ee0a
//
// Description  : NTLM authentication coming from Domain Controller machine accounts. This is not an expected behavior and it's an indication of NTLM relay attack.   
//                If NTLM Relaying is done towards a Linux machine, this query won't detect that. The attacker must have access to a Linux device in that case though. 
//
// Query parameters:
//
// put FQDNs of the DCs into the list
let DCs = dynamic(["yourdc1.yourdomain.local","yourdc2.yourdomain.local"]);
DeviceLogonEvents
| where Protocol == "NTLM"
| where AccountName endswith "$"
| where DCs has replace_string(AccountName,"$","")

Stages and Predicates

Parameters

let DCs = dynamic(["yourdc1.yourdomain.local","yourdc2.yourdomain.local"]);

Stage 1: source

DeviceLogonEvents

Stage 2: where

| where Protocol == "NTLM"

Stage 3: where

| where AccountName endswith "$"

Stage 4: where

| where DCs has replace_string(AccountName,"$","")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountNameends_with
  • $ corpus 5 (elastic 4, kusto 1)
field:"user" kind:ends_with value:"$"
Protocoleq
  • NTLM
field:"Protocol" kind:eq value:"NTLM"

Restore Point Marked as Clean

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

Detects when a restore point is marked as clean.

Rule Body

id: c4c6cb50-9fc3-4ca9-a2df-22d527240309
name: Restore Point Marked as Clean
description: Detects when a restore point is marked as clean.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42230\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 == 42230

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

Several deny actions registered

#
Status
available
Severity
medium
Time window
1h
Group by
Action, Fqdn, Protocol, SourceIp
Source
github.com/Azure/Azure-Sentinel

'Identifies attack pattern when attacker tries to move, or scan, from resource to resource on the network and creates an incident when a source has more than 1 registered deny action in Azure Firewall.'

MITRE ATT&CK Coverage

Rule Body

id: f8dad4e9-3f19-4d70-ab7f-8f19ccd43a3e
name: Several deny actions registered
description: |
  'Identifies attack pattern when attacker tries to move, or scan, from resource to resource on the network and creates an incident when a source has more than 1 registered deny action in Azure Firewall.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes:
      - AzureDiagnostics
      - AZFWApplicationRule
      - AZFWNetworkRule
      - AZFWFlowTrace
      - AZFWIdpsSignature
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - LateralMovement
  - CommandAndControl
relevantTechniques:
  - T1046
  - T1071
  - T1210
query: |
  let threshold = 2;
  union isfuzzy=true(
  AZFWApplicationRule
  | where Action == "Deny"
  | summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
  | where count_ >= threshold),
  (AZFWNetworkRule
  | where Action == "Deny"
  | extend Fqdn = DestinationIp
  | summarize StartTime = min(TimeGenerated), count_ = dcount(DestinationPort), Protocols = make_set(Protocol) by SourceIp, Fqdn, Action
  | where count_ >= threshold
  | extend Protocol = strcat_array(Protocols, "/")),
  (AZFWFlowTrace
  | where Action == "Deny"
  | extend Fqdn = DestinationIp
  | summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
  | where count_ >= threshold),
  (AZFWIdpsSignature
  | where Action == "Deny"
  | extend Fqdn = DestinationIp
  | summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
  | where count_ >= threshold),
  (AzureDiagnostics
  | where OperationName in ("AzureFirewallApplicationRuleLog","AzureFirewallNetworkRuleLog")
  | extend msg_s_replaced0 = replace(@"\s\s",@" ",msg_s)
  | extend msg_s_replaced1 = replace(@"\.\s",@" ",msg_s_replaced0)
  | extend msg_a = split(msg_s_replaced1," ")
  | extend srcAddr_a = split(msg_a[3],":") , destAddr_a = split(msg_a[5],":")
  | extend Protocol = tostring(msg_a[0]), SourceIp = tostring(srcAddr_a[0]), srcPort = tostring(srcAddr_a[1]), DestinationIp = tostring(destAddr_a[0]), destPort = tostring(destAddr_a[1]), Action = tostring(msg_a[7])
  | where Action == "Deny"
  | extend Fqdn = iff(DestinationIp matches regex "\\d+\\.\\d+\\.\\d+\\.\\d+","",DestinationIp)
  | summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
  | where count_ >= threshold)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Fqdn
customDetails:
  NetworkProtocol: Protocol
alertDetailsOverride:
  alertDisplayNameFormat: 'Multiple Deny Actions Detected from {{SourceIp}} to {{Fqdn}}'
  alertDescriptionFormat: 'Source IP {{SourceIp}} has triggered multiple deny actions to destination {{Fqdn}} using protocol {{Protocol}}, which may indicate malicious activity such as lateral movement or reconnaissance.'
version: 1.1.3
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2;

Union isfuzzy=true (5 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: AZFWApplicationRule, AZFWNetworkRule, AZFWFlowTrace, AZFWIdpsSignature, AzureDiagnostics

Leg 1: AZFWApplicationRule

AZFWApplicationRule
| where Action == "Deny"
| summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
| where count_ >= threshold

Leg 2: AZFWNetworkRule

AZFWNetworkRule
| where Action == "Deny"
| extend Fqdn = DestinationIp
| summarize StartTime = min(TimeGenerated), count_ = dcount(DestinationPort), Protocols = make_set(Protocol) by SourceIp, Fqdn, Action
| where count_ >= threshold
| extend Protocol = strcat_array(Protocols, "/")

Leg 3: AZFWFlowTrace

AZFWFlowTrace
| where Action == "Deny"
| extend Fqdn = DestinationIp
| summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
| where count_ >= threshold

Leg 4: AZFWIdpsSignature

AZFWIdpsSignature
| where Action == "Deny"
| extend Fqdn = DestinationIp
| summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
| where count_ >= threshold

Leg 5: AzureDiagnostics

AzureDiagnostics
| where OperationName in ("AzureFirewallApplicationRuleLog","AzureFirewallNetworkRuleLog")
| extend msg_s_replaced0 = replace(@"\s\s",@" ",msg_s)
| extend msg_s_replaced1 = replace(@"\.\s",@" ",msg_s_replaced0)
| extend msg_a = split(msg_s_replaced1," ")
| extend srcAddr_a = split(msg_a[3],":") , destAddr_a = split(msg_a[5],":")
| extend Protocol = tostring(msg_a[0]), SourceIp = tostring(srcAddr_a[0]), srcPort = tostring(srcAddr_a[1]), DestinationIp = tostring(destAddr_a[0]), destPort = tostring(destAddr_a[1]), Action = tostring(msg_a[7])
| where Action == "Deny"
| extend Fqdn = iff(DestinationIp matches regex "\\d+\\.\\d+\\.\\d+\\.\\d+","",DestinationIp)
| summarize StartTime = min(TimeGenerated), count() by SourceIp, Fqdn, Action, Protocol
| where count_ >= threshold

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • Deny
field:"Action" kind:eq value:"Deny"
OperationNamein
  • AzureFirewallApplicationRuleLog
  • AzureFirewallNetworkRuleLog
field:"OperationName" kind:in
count_ge
  • 2 transforms: number
field:"count_" kind:ge value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsummarize
Fqdnsummarize
Protocolsummarize
SourceIpsummarize
StartTimesummarize

Tenable.ad Indicators of Exposures

#
Severity
low
Time window
2h
Group by
Severity
Source
github.com/Azure/Azure-Sentinel

'Searches for triggered Indicators of Exposures.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 55de1072-e93f-40f9-a14d-f7356d217cf6
name: Tenable.ad Indicators of Exposures
description: |
    'Searches for triggered Indicators of Exposures.'
severity: Low
requiredDataConnectors:
  - connectorId: Tenable.ad
    dataTypes:
      - Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  // For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
  // Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
  // Then, create the Kusto Function with alias afad_parser
  let SeverityTable=datatable(Severity:string,Level:int) [
    "low", 1,
    "medium", 2,
    "high", 3,
    "critical", 4
    ];
    afad_parser
    | where MessageType == 0
    | lookup kind=leftouter SeverityTable on Severity
    | order by Level
    | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let Binding: SeverityTable used in Stage 3

let SeverityTable = datatable(Severity:string,Level:int) [
  "low", 1,
  "medium", 2,
  "high", 3,
  "critical", 4
  ];

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 0

Stage 3: kusto:lookup

lookup kind=leftouter (SeverityTable) on Severity

Stage 4: sort

sort by Level

Stage 5: extend

extend DnsDomain, HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
MessageTypeeq
  • 0 corpus 15 (kusto 15)
field:"MessageType" kind:eq value:"0"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

ThreatConnect TI map IP entity to Network Session Events (ASIM Network Session schema)

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.IoCIP, IndicatorId
Source
github.com/Azure/Azure-Sentinel

ThreatConnect Specific: This rule identifies a match Network Sessions for which the source or destination IP address is a known IoC. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM NetworkSession schema

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: ee1fd303-2081-47b7-8f02-e38bfd0868e6
name: ThreatConnect TI map IP entity to Network Session Events (ASIM Network Session schema)
version: 1.0.2
kind: Scheduled
description: |-
  ThreatConnect Specific:
  This rule identifies a match Network Sessions for which the source or destination IP address is a known IoC.
  This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM NetworkSession schema
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let IP_TI = materialize (
    ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where ExpirationDateTime > now() and Active == true
    | where SourceSystem startswith "ThreatConnect-"
    | extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")
    | where TI_ipEntity != "NO_IP"
  );
  IP_TI
    // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique 
  (
    _Im_NetworkSession (starttime=ago(dt_lookBack))
    | where isnotempty(SrcIpAddr)
    | summarize imNWS_mintime=min(TimeGenerated), imNWS_maxtime=max(TimeGenerated) by SrcIpAddr, DstIpAddr, Dvc, EventProduct, EventVendor  
    | lookup (IP_TI | project TI_ipEntity, Active) on $left.SrcIpAddr == $right.TI_ipEntity
    | project-rename SrcMatch = Active
    | lookup (IP_TI | project TI_ipEntity, Active) on $left.DstIpAddr == $right.TI_ipEntity
    | project-rename DstMatch = Active
    | where SrcMatch or DstMatch
    | extend 
        IoCIP = iff(SrcMatch, SrcIpAddr, DstIpAddr),
        IoCDirection = iff(SrcMatch, "Source", "Destination")
  )on $left.TI_ipEntity == $right.IoCIP
  | where imNWS_mintime < ExpirationDateTime
  | project imNWS_mintime, imNWS_maxtime, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SrcIpAddr, DstIpAddr, IoCDirection, IoCIP, Dvc, EventVendor, EventProduct
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 4h
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: A network session {{IoCDirection}} address {{IoCIP}} matched an IoC.
  alertDescriptionFormat: The {{IoCDirection}} address {{IoCIP}} of a network session  matched a known indicator of compromise of {{ThreatType}}. Consult the threat intelligence blead for more information on the indicator.
customDetails:
  IndicatorId: IndicatorId
  IoCConfidenceScore: ConfidenceScore
  IoCDescription: Description
  IoCExpirationTime: ExpirationDateTime
  EventEndTime: imNWS_maxtime
  ActivityGroupNames: ActivityGroupNames
  ThreatType: ThreatType
  IoCIPDirection: IoCDirection
  EventStartTime: imNWS_mintime
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: IoCIP
suppressionDuration: 1h

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let IP_TI is inlined into the numbered stages below.

Stages 1 to 7 define let IP_TI as the rule's main pipeline source. Stages 8 to 10 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where ExpirationDateTime > now() and Active == true

Stage 5: where

| where SourceSystem startswith "ThreatConnect-"

Stage 6: extend

| extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")

Stage 7: where

| where TI_ipEntity != "NO_IP"

Stage 8: join

join kind=innerunique (_Im_NetworkSession) on $left.TI_ipEntity, $right.IoCIP

Stage 9: where

where imNWS_mintime < ExpirationDateTime

Stage 10: project

project ActivityGroupNames, ConfidenceScore, Description, DstIpAddr, Dvc, EventProduct, EventVendor, ExpirationDateTime, IndicatorId, IoCDirection, IoCIP, SrcIpAddr, ThreatType, imNWS_maxtime, imNWS_mintime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
DstIpAddrproject
Dvcproject
EventProductproject
EventVendorproject
ExpirationDateTimeproject
IndicatorIdproject
IoCDirectionproject
IoCIPproject
SrcIpAddrproject
ThreatTypeproject
imNWS_maxtimeproject
imNWS_mintimeproject

TI Map File Hash to CommonSecurityLog Event

#
Severity
medium
Time window
14d
Group by
$left.FileHashValue, $right.FileHash, FileHashValue, Id, IndicatorId, ObservableValue
Source
github.com/Azure/Azure-Sentinel

'Identifies a match in CommonSecurityLog Event data from any FileHash IOC from TI'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 432996e9-8a93-4407-985f-13707b318a0b
name: TI Map File Hash to CommonSecurityLog Event
description: |
  'Identifies a match in CommonSecurityLog Event data from any FileHash IOC from TI'
severity: Medium
requiredDataConnectors:
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let fileHashIndicators = ThreatIntelIndicators
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "file"
  | extend FileHashType = replace("'", "", substring(ObservableKey, indexof(ObservableKey, "hashes.") + 7, strlen(ObservableKey) - indexof(ObservableKey, "hashes.") - 7))
  | extend FileHashValue = toupper(ObservableValue)
  | extend IndicatorId = tostring(split(Id, "--")[2])
  | extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil));
  // Handle matches against both lower case and uppercase versions of the hash:
  (fileHashIndicators | extend  FileHashValue = tolower(FileHashValue)
  | union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue)))
  | project-reorder *, FileHashType, FileHashValue, Type
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  |  join kind=innerunique (
    CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
    | extend CommonSecurityLog_TimeGenerated = TimeGenerated
    )
  on $left.FileHashValue == $right.FileHash
  | where CommonSecurityLog_TimeGenerated < ValidUntil
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, FileHashValue
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | project CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence,
  SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,
  RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity, FileHashValue, FileHashType, Url
  | extend HostName = tostring(split(DeviceName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))
  | extend Name = tostring(split(SourceUserName, '@', 0)[0]), UPNSuffix = tostring(split(SourceUserName, '@', 1)[0])
  | extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SourceUserName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix 
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: FileHashValue
      - identifier: Algorithm
        columnName: FileHashType
version: 1.3.10
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let fileHashIndicators is inlined into the numbered stages below.

Stages 1 to 7 define let fileHashIndicators as the rule's main pipeline source. Stages 8 to 24 combine it with further branches.

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType == "file"

Stage 4: extend (4 consecutive steps)

| extend FileHashType = replace("'", "", substring(ObservableKey, indexof(ObservableKey, "hashes.") + 7, strlen(ObservableKey) - indexof(ObservableKey, "hashes.") - 7))
| extend FileHashValue = toupper(ObservableValue)
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")

Stage 5: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 6: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 7: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 8: extend

fileHashIndicators
| extend  FileHashValue = tolower(FileHashValue)

Stage 9: union

union of 1 branches

Stage 10: source

ThreatIntelIndicators

Stage 11: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 12: where

| where IndicatorType == "file"

Stage 13: extend (4 consecutive steps)

| extend FileHashType = replace("'", "", substring(ObservableKey, indexof(ObservableKey, "hashes.") + 7, strlen(ObservableKey) - indexof(ObservableKey, "hashes.") - 7))
| extend FileHashValue = toupper(ObservableValue)
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")

Stage 14: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 15: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 16: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 17: extend

| extend FileHashValue = toupper(FileHashValue)

Stage 18: project-reorder

project-reorder

Stage 19: join

| join kind=innerunique (
  CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
  | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  )
on $left.FileHashValue == $right.FileHash

Stage 20: where

where CommonSecurityLog_TimeGenerated < ValidUntil

Stage 21: summarize

summarize CommonSecurityLog_TimeGenerated by IndicatorId, FileHashValue

Stage 22: extend (4 consecutive steps)

extend ActivityGroupNames, Description

Stage 23: project

project Activity, ActivityGroupNames, ApplicationProtocol, CommonSecurityLog_TimeGenerated, Confidence, Description, DestinationIP, DestinationPort, DestinationUserID, DestinationUserName, DeviceAction, DeviceName, FileHashType, FileHashValue, Id, RequestURL, SourceIP, SourcePort, SourceUserID, SourceUserName, Url, ValidUntil

Stage 24: extend (3 consecutive steps)

extend DnsDomain, HostName, Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activityproject
ActivityGroupNamesproject
ApplicationProtocolproject
CommonSecurityLog_TimeGeneratedproject
Confidenceproject
Descriptionproject
DestinationIPproject
DestinationPortproject
DestinationUserIDproject
DestinationUserNameproject
DeviceActionproject
DeviceNameproject
FileHashTypeproject
FileHashValueproject
Idproject
RequestURLproject
SourceIPproject
SourcePortproject
SourceUserIDproject
SourceUserNameproject
Urlproject
ValidUntilproject
DnsDomainextend
HostNameextend
Nameextend
UPNSuffixextend
timestampextend

TI Map URL Entity to PaloAlto Data

#
Severity
medium
Time window
14d
Group by
$left.Url, $right.PA_Url, Id, ObservableValue, PA_Url
Source
github.com/Azure/Azure-Sentinel

'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in PaloAlto Data.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 32b437c4-dddb-45b3-9aae-5188e80624b0
name: TI Map URL Entity to PaloAlto Data
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in PaloAlto Data.'
severity: Medium
requiredDataConnectors:
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  ThreatIntelIndicators
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "url"
  | extend Url = ObservableValue
  | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  // Picking up only IOC's that contain the entities we want
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
     | project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
    CommonSecurityLog
    | extend IngestionTime = ingestion_time()
    | where IngestionTime > ago(dt_lookBack)
    // Select on Palo Alto logs
    | where DeviceVendor =~ "Palo Alto Networks"
    | where DeviceEventClassID =~ 'url'
    //Uncomment the line below to only alert on allowed connections
    //| where DeviceAction !~ "block-url"
    //Select logs where URL data is populated
    | extend PA_Url = column_ifexists("RequestURL", "None")
    | extend PA_Url = iif(isempty(PA_Url), extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
    | extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
    | where isnotempty(PA_Url)
    | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  ) on $left.Url == $right.PA_Url
  | where CommonSecurityLog_TimeGenerated < ValidUntil
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by Id, PA_Url
  | project timestamp = CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, Id, Type, ValidUntil, Confidence, DeviceAction, SourceIP, PA_Url, DeviceName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: PA_Url
version: 1.2.7
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType == "url"

Stage 4: extend

| extend Url = ObservableValue

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 8: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 9: project-reorder

| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type

Stage 10: join

| join kind=innerunique (
  CommonSecurityLog
  | extend IngestionTime = ingestion_time()
  | where IngestionTime > ago(dt_lookBack)
  | where DeviceVendor =~ "Palo Alto Networks"
  | where DeviceEventClassID =~ 'url'
  | extend PA_Url = column_ifexists("RequestURL", "None")
  | extend PA_Url = iif(isempty(PA_Url), extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
  | extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
  | where isnotempty(PA_Url)
  | extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.Url == $right.PA_Url

Stage 11: where

where CommonSecurityLog_TimeGenerated < ValidUntil

Stage 12: extend

extend Description

Stage 13: extend

extend ActivityGroupNames

Stage 14: summarize

summarize CommonSecurityLog_TimeGenerated by Id, PA_Url

Stage 15: project

project ActivityGroupNames, Confidence, Description, DeviceAction, DeviceName, Id, PA_Url, SourceIP, Type, ValidUntil, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Confidenceproject
Descriptionproject
DeviceActionproject
DeviceNameproject
Idproject
PA_Urlproject
SourceIPproject
Typeproject
ValidUntilproject
timestampproject

VMware ESXi - Shared or stolen root account

#
Status
available
Severity
high
Time window
15m
Group by
SrcIpAddr
Source
github.com/Azure/Azure-Sentinel

'Detects when shared or stolen root account.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Privilege Escalation

Rule Body

id: 9c496d6c-42a3-4896-9b6c-00254386928f
name: VMware ESXi - Shared or stolen root account
description: |
  'Detects when shared or stolen root account.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 1
tactics:
  - InitialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1078
query: |
  VMwareESXi
  | where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
  | extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
  | summarize count() by SrcIpAddr, bin(TimeGenerated, 15m)
  | where count_ > 1
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

VMwareESXi

Stage 2: where

| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')

Stage 3: extend

| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)

Stage 4: summarize

| summarize count() by SrcIpAddr, bin(TimeGenerated, 15m)

Stage 5: where

| where count_ > 1

Stage 6: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • UserLoginSessionEvent transforms: term
  • logged in transforms: term
  • root transforms: term
field:"SyslogMessage" kind:match
count_gt
  • 1 transforms: number
field:"count_" kind:gt value:"1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrsummarize
IPCustomEntityextend

VMware SD-WAN Edge - Device Congestion Alert - Packet Drops

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

The VMware Edge Cloud Orchestrator reported an edge congestion event, where the Edge is dropping a large number of packets on one of its interfaces. This could indicate an ongoing Denial of Service attack against an appliance. Please make sure that Network Flood Protection is turned on.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: a88ead0a-f022-48d6-8f53-e5a164c4c72e
name: VMware SD-WAN Edge - Device Congestion Alert - Packet Drops
version: 1.0.0
kind: Scheduled
description: The VMware Edge Cloud Orchestrator reported an edge congestion event, where the Edge is dropping a large number of packets on one of its interfaces. This could indicate an ongoing Denial of Service attack against an appliance. Please make sure that Network Flood Protection is turned on.
severity: Medium
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
  - connectorId: VMwareSDWAN
    dataTypes:
      - SDWAN
tactics:
- Impact
relevantTechniques:
- T1498
query: |-
  VMware_VECO_EventLogs_CL
  | where event == "EDGE_CONGESTED"
  | where message contains "high number of packet drops"
  | extend edgeSerialNumber = extract("edgeSerialNumber: (.+)$", 1, detail)
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails:
  edgeSerialNumber: edgeSerialNumber
suppressionDuration: 5h

Stages and Predicates

Stage 1: source

VMware_VECO_EventLogs_CL

Stage 2: where

| where event == "EDGE_CONGESTED"

Stage 3: where

| where message contains "high number of packet drops"

Stage 4: extend

| extend edgeSerialNumber = extract("edgeSerialNumber: (.+)$", 1, detail)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
edgeSerialNumberextend

Back to all Kusto rules