Detection rules › Kusto

Kusto rules: 3d

Antivirus Detected an Infected File

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

Monitors CTERA platform to detect files infected with viruses identified by the antivirus engine on Edge Filers.

MITRE ATT&CK Coverage

Rule Body

id: 4f767afa-d666-4ed4-b453-a4f5ad35181b
name: Antivirus Detected an Infected File
description: 'Monitors CTERA platform to detect files infected with viruses identified by the antivirus engine on Edge Filers.'
severity: High
status: Available
kind: NRT
requiredDataConnectors:
  - connectorId: CTERA
    dataTypes:
      - Syslog
tactics:
  - Impact
relevantTechniques:
  - T1203
query: |
    Syslog
    | where SyslogMessage contains "found an infected file"
    | extend 
        EdgeFiler = extract("Edge filer (\\w+)", 1, SyslogMessage),
        DetectionTime = extract("found an infected file at ([^ ]+)", 1, SyslogMessage),
        Portal = extract("from portal: (\\w+)", 1, SyslogMessage),
        FilePath = extract("The file path is: ([^\\.]+)", 1, SyslogMessage),
        Virus = extract("The virus is: ([^\\.]+)", 1, SyslogMessage)
    | project TimeGenerated, EdgeFiler, DetectionTime, Portal, FilePath, Virus
suppressionDuration: PT5H
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertnameFormat: 'Antivirus Detected an Infected File'
  alertDescriptionFormat: Antivirus detected an infected file on {{EdgeFiler}} at {{DetectionTime}}.
customDetails:
  EdgeFiler: EdgeFiler
  Portal: Portal
  FilePath: FilePath
  Virus: Virus
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: EdgeFiler
version: 1.0.0

Stages and Predicates

Stage 1: source

Syslog

Stage 2: where

| where SyslogMessage contains "found an infected file"

Stage 3: extend

| extend 
    EdgeFiler = extract("Edge filer (\\w+)", 1, SyslogMessage),
    DetectionTime = extract("found an infected file at ([^ ]+)", 1, SyslogMessage),
    Portal = extract("from portal: (\\w+)", 1, SyslogMessage),
    FilePath = extract("The file path is: ([^\\.]+)", 1, SyslogMessage),
    Virus = extract("The virus is: ([^\\.]+)", 1, SyslogMessage)

Stage 4: project

| project TimeGenerated, EdgeFiler, DetectionTime, Portal, FilePath, Virus

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DetectionTimeproject
EdgeFilerproject
FilePathproject
Portalproject
TimeGeneratedproject
Virusproject

Apache - Known malicious user agent

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

'Detects known malicious user agents'

MITRE ATT&CK Coverage

Rule Body

id: e9edfe1c-3afd-11ec-8d3d-0242ac130003
name: Apache - Known malicious user agent
description: |
  'Detects known malicious user agents'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - ApacheHTTPServer_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let mal_uas = dynamic(['Metasploit', '360Spider', '404checker', 'AllSubmitter', 'BackDoorBot', 'CATExplorador', 'Nikto', 'havij']);
  ApacheHTTPServer
  | where HttpUserAgentOriginal has_any (mal_uas)
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let mal_uas = dynamic(['Metasploit', '360Spider', '404checker', 'AllSubmitter', 'BackDoorBot', 'CATExplorador', 'Nikto', 'havij']);

Stage 1: source

ApacheHTTPServer

Stage 2: where

| where HttpUserAgentOriginal has_any (mal_uas)

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentOriginalmatch
  • 360Spider transforms: term
  • 404checker transforms: term
  • AllSubmitter transforms: term
  • BackDoorBot transforms: term
  • CATExplorador transforms: term
  • Metasploit transforms: term
  • Nikto transforms: term
  • havij transforms: term
field:"HttpUserAgentOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

AWSCloudTrail - Creation of Lambda policy and then privilege escalation

#
Status
available
Severity
medium
Time window
1d
Group by
AccountName, AccountUPNSuffix, EventName, EventSource, PolicyName, RecipientAccountId, SourceIpAddress, UserIdentityArn, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

Identifies creation of IAM policies that grant broad AWS Lambda permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to add execution capabilities that support privilege escalation.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 796a45ee-220b-42be-8415-c8c933cf3b6d
name: AWSCloudTrail - Creation of Lambda policy and then privilege escalation
description: |
  Identifies creation of IAM policies that grant broad AWS Lambda permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to add execution capabilities that support privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1484
  - T1098.003
query: |
  let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
  let createPolicy =  dynamic(["CreatePolicy", "CreatePolicyVersion"]);
  let timeframe = 1d;
  let lookback = 14d;
  // Creating Master table with all the events to use with materialize for better performance
  let EventInfo = materialize(AWSCloudTrail
  | where TimeGenerated >= ago(lookback)
  | where EventName in (EventNameList) or EventName in (createPolicy)
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), ""));
  //Checking for Policy creation event with Full Admin Privileges since lookback period.
  let FullAdminPolicyEvents =  EventInfo
  | where TimeGenerated >= ago(lookback)
  | where EventName in (createPolicy)
  | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
  | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
  | mvexpand Statement
  | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
  | project-rename StartTime = TimeGenerated;
  let PolicyAttach = EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix,  PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
  | project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;
  // Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
  // These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
  FullAdminPolicyEvents
  | join kind=leftouter
  (
      PolicyAttach
  )
  on PolicyName
  | project-away PolicyName1
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  PolicyName: PolicyName
  EventName: EventName
  RecipientAccountId: RecipientAccountId
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Lambda privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;

let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.

Let Binding: PolicyAttach used in Stage 15

let PolicyAttach = EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix,  PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;

Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 use it.

Stage 1: source

AWSCloudTrail

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: where

| where EventName in (EventNameList) or EventName in (createPolicy)

Stage 4: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Stage 5: where

| where TimeGenerated >= ago(lookback)

Stage 6: where

| where EventName in (createPolicy)

Stage 7: extend

| extend PolicyName = tostring(parse_json(RequestParameters).policyName)

Stage 8: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement

Stage 9: mv-expand

| mvexpand Statement

Stage 10: extend

| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)

Stage 11: extend

| extend Action = tostring(Action)

Stage 12: where

| where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""

Stage 13: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix

Stage 14: project-rename

| project-rename StartTime = TimeGenerated

Stage 15: join

FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName

Stage 16: project-away

project-away PolicyName1

Stage 17: summarize aggregation inside the join branch

summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • dynamodb:*
  • dynamodb:Create*
  • dynamodb:CreateTable
  • dynamodb:Put*
  • dynamodb:PutItem
  • iam:*
  • iam:PassRole
  • lambda:*
  • lambda:Create*
  • lambda:CreateEventSourceMapping
  • lambda:CreateFunction
  • lambda:Invoke*
  • lambda:InvokeFunction
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachGroupPolicy
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy
  • CreatePolicy corpus 14 (kusto 12, panther 2)
  • CreatePolicy
  • CreatePolicyVersion corpus 14 (kusto 12, panther 2)
  • CreatePolicyVersion
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
AccountUPNSuffixsummarize
EventNamesummarize
EventSourcesummarize
PolicyNamesummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
UserIdentityArnsummarize
UserIdentityTypesummarize

AWSCloudTrail - Suspicious command sent to EC2

#
Status
available
Severity
high
Time window
1d
Group by
s3bucket
Source
github.com/Azure/Azure-Sentinel

An attacker with the necessary AWS permissions could be executing code remotely on an EC2 instance via SSM and saving the output to their own S3 bucket. Verify this action with the user identity and confirm it was authorized.

MITRE ATT&CK Coverage

TacticTechniques
Execution

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 21702832-aff3-4bd6-a8e1-663b6818503d
name: AWSCloudTrail - Suspicious command sent to EC2
description: |
  An attacker with the necessary AWS permissions could be executing code remotely on an EC2 instance via SSM and saving the output to their own S3 bucket. Verify this action with the user identity and confirm it was authorized.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1651
query: |
    let command_executed = AWSCloudTrail
    | where EventName in ("SendCommand","CreateAssociation") and isempty(ErrorCode) and isempty(ErrorMessage)
    | extend params = tostring(parse_json(RequestParameters).parameters)
    | extend s3bucketCommand = tostring(parse_json(RequestParameters).outputS3BucketName)
    | extend s3bucketAssociation = tostring(parse_json(RequestParameters).outputLocation.s3Location.outputS3BucketName)
    | where isnotempty(params)
    | extend commandId = tostring(parse_json(ResponseElements).command.commandId)
    | extend associationId = tostring(parse_json(ResponseElements).associationDescription.associationId)
    | extend executionId = iff(isnotempty(commandId), commandId, associationId)
    | extend s3bucket = iff(isnotempty(s3bucketCommand), s3bucketCommand, s3bucketAssociation)
    | extend UserIdentityUserName = iff(isnotempty(UserIdentityUserName), UserIdentityUserName, tostring(split(UserIdentityArn,'/')[-1]));
    AWSCloudTrail
    | where EventName == "PutObject" and isempty(ErrorCode) and isempty(ErrorMessage)
    | extend s3bucket = tostring(parse_json(RequestParameters).bucketName)
    | mv-expand todynamic(Resources)
    | extend accountId=tostring(todynamic(Resources.['accountId']))
    | where Resources contains "accountId" and accountId <> RecipientAccountId
    | join command_executed on s3bucket
    | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
    | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
    | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
    | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
      AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  S3Bucket: s3bucket
  ExecutionId: executionId
  AWSRegion: AWSRegion
alertDetailsOverride:
  alertDisplayNameFormat: Suspicious EC2 command execution by {{AccountName}} with output to S3 {{s3bucket}}
  alertDescriptionFormat: User {{AccountName}} sent a remote command to EC2 (execution ID {{executionId}}) with output directed to external S3 bucket {{s3bucket}}.
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: command_executed used in Stage 7

let command_executed = AWSCloudTrail
| where EventName in ("SendCommand","CreateAssociation") and isempty(ErrorCode) and isempty(ErrorMessage)
| extend params = tostring(parse_json(RequestParameters).parameters)
| extend s3bucketCommand = tostring(parse_json(RequestParameters).outputS3BucketName)
| extend s3bucketAssociation = tostring(parse_json(RequestParameters).outputLocation.s3Location.outputS3BucketName)
| where isnotempty(params)
| extend commandId = tostring(parse_json(ResponseElements).command.commandId)
| extend associationId = tostring(parse_json(ResponseElements).associationDescription.associationId)
| extend executionId = iff(isnotempty(commandId), commandId, associationId)
| extend s3bucket = iff(isnotempty(s3bucketCommand), s3bucketCommand, s3bucketAssociation)
| extend UserIdentityUserName = iff(isnotempty(UserIdentityUserName), UserIdentityUserName, tostring(split(UserIdentityArn,'/')[-1]));

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName == "PutObject" and isempty(ErrorCode) and isempty(ErrorMessage)

Stage 3: extend

| extend s3bucket = tostring(parse_json(RequestParameters).bucketName)

Stage 4: mv-expand

| mv-expand todynamic(Resources)

Stage 5: extend

| extend accountId=tostring(todynamic(Resources.['accountId']))

Stage 6: where

| where Resources contains "accountId" and accountId <> RecipientAccountId

Stage 7: join

| join command_executed on s3bucket

Stage 8: extend (4 consecutive steps)

extend AccountName, AccountUPNSuffix, UserIdentityArn, UserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
s3bucketextend
accountIdextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

Conditional Access - A Conditional Access app exclusion has changed

#
Severity
low
Time window
5m
Source
github.com/Azure/Azure-Sentinel

A Conditional Access app exclusion has changed in Entra ID.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

PlatformRecord / event type
AzureUpdate conditional access policy

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 0990a481-3bc8-4682-838c-313918dd858c
name: Conditional Access - A Conditional Access app exclusion has changed
version: 1.0.1
kind: Scheduled
description: A Conditional Access app exclusion has changed in Entra ID.
severity: Low
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |+
  // A Conditional Access app exclusion has changed.
  AuditLogs
  | where OperationName in ("Update conditional access policy")
  | extend excludeApplicationsOld = extractjson("$.conditions.applications.excludeApplications", tostring(TargetResources[0].modifiedProperties[0].oldValue))
  | extend excludeApplicationsNew = extractjson("$.conditions.applications.excludeApplications", tostring(TargetResources[0].modifiedProperties[0].newValue))
  | where excludeApplicationsOld != excludeApplicationsNew
  | extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
  | extend accountName = tostring(split(modifiedBy, "@")[0])
  | extend upnSuffix = tostring(split(modifiedBy, "@")[1])
  | project
      TimeGenerated,
      OperationName,
      policy = TargetResources[0].displayName,
      modifiedBy,
      accountName,
      upnSuffix,
      result = Result,
      excludeApplicationsOld,
      excludeApplicationsNew
  | order by TimeGenerated desc

entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: accountName
  - identifier: UPNSuffix
    columnName: upnSuffix
suppressionEnabled: false
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName in ("Update conditional access policy")

Stage 3: extend

| extend excludeApplicationsOld = extractjson("$.conditions.applications.excludeApplications", tostring(TargetResources[0].modifiedProperties[0].oldValue))

Stage 4: extend

| extend excludeApplicationsNew = extractjson("$.conditions.applications.excludeApplications", tostring(TargetResources[0].modifiedProperties[0].newValue))

Stage 5: where

| where excludeApplicationsOld != excludeApplicationsNew

Stage 6: extend (3 consecutive steps)

| extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
| extend accountName = tostring(split(modifiedBy, "@")[0])
| extend upnSuffix = tostring(split(modifiedBy, "@")[1])

Stage 7: project

| project
    TimeGenerated,
    OperationName,
    policy = TargetResources[0].displayName,
    modifiedBy,
    accountName,
    upnSuffix,
    result = Result,
    excludeApplicationsOld,
    excludeApplicationsNew

Stage 8: sort

| order by TimeGenerated desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNamein
  • Update conditional access policy corpus 6 (kusto 6)
field:"OperationName" kind:in value:"Update conditional access policy"

Output Fields

These fields are emitted when the rule matches.

FieldSource
OperationNameproject
TimeGeneratedproject
accountNameproject
excludeApplicationsNewproject
excludeApplicationsOldproject
modifiedByproject
policyproject
resultproject
upnSuffixproject

Detect presence of uncommon user agents in web requests (ASIM Web Session)

#
Status
available
Severity
medium
Time window
14d
Group by
DstIpAddr, DstPortNumber, HttpUserAgent, SrcHostname, SrcIpAddr, SrcUsername, Url
Source
github.com/Azure/Azure-Sentinel

'This rule assists in detecting rare user agents, which may indicate web browsing activity by an unconventional process different from the usual ones. The rule specifically searches for UserAgent strings that have not been seen in the past 14 days. This query will perform better when run over summarized data'

MITRE ATT&CK Coverage

Rule Body

id: 2d50d937-d7f2-4c05-b151-9af7f9ec747e
name: Detect presence of uncommon user agents in web requests (ASIM Web Session)
description: |
  'This rule assists in detecting rare user agents, which may indicate web browsing activity by an unconventional process different from the usual ones. The rule specifically searches for UserAgent strings that have not been seen in the past 14 days. This query will perform better when run over summarized data'
severity: Medium
status: Available 
tags:
  - Schema: WebSession
    SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let lookBack = 14d;
  let timeframe = 1h;
  // 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_SrcInfo_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 allUserAgents = union isfuzzy=true
          (
          (datatable(exists: int, sumexist: bool)[1, false]
          | where toscalar(eps) > 1000
          | join (summarizationexist) on sumexist)
          | join (
              _Im_WebSession(starttime=todatetime(ago(2d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
              | where isnotempty(HttpUserAgent)
              | summarize UserAgentsSet =  make_set(HttpUserAgent) // This could contain upto 10,48,576 unique values
              | extend exists=int(1)
              )
              on exists
          | project-away exist*, maxv, sum*
          ),
          (
          (datatable(exists: int, sumexist: bool)[1, false]
          | where toscalar(eps) between (501 .. 1000)
          | join (summarizationexist) on sumexist)
          | join (
              _Im_WebSession(starttime=todatetime(ago(3d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
              | where isnotempty(HttpUserAgent)
              | summarize UserAgentsSet = make_set(HttpUserAgent) // This could contain upto 10,48,576 unique values
              | extend exists=int(1)
              )
              on exists
          | project-away exist*, maxv, sum*
          ),
          (
          (datatable(exists: int, sumexist: bool)[1, false]
          | where toscalar(eps) <= 500
          | join (summarizationexist) on sumexist)
          | join (
              _Im_WebSession(starttime=todatetime(ago(4d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
              | where isnotempty(HttpUserAgent)
              | summarize UserAgentsSet = make_set(HttpUserAgent) // This could contain upto 10,48,576 unique values
              | extend exists=int(1)
              )
              on exists
          | project-away exist*, maxv, sum*
          ),
          (
          WebSession_Summarized_SrcInfo_CL
          | where EventTime_t between (ago(14d) .. ago(timeframe))
              and isnotempty(HttpUserAgent_s)
              and EventResult_s =~ 'Success'
          | summarize UserAgentsSet = make_set(HttpUserAgent_s) // This could contain upto 10,48,576 unique values
          );
  _Im_WebSession (starttime=bin(ago(timeframe), 1h), eventresult='Success')
  | project
      SrcIpAddr,
      SrcUsername,
      SrcHostname,
      DstIpAddr,
      DstPortNumber,
      Url,
      HttpUserAgent,
      TimeGenerated
  | where isnotempty(HttpUserAgent) and HttpUserAgent !in~ (allUserAgents)
  | summarize
      EventCount=count(),
      EventStartTime=min(TimeGenerated),
      EventEndTime=max(TimeGenerated)
      by
      SrcIpAddr,
      SrcUsername,
      SrcHostname,
      DstIpAddr,
      Url,
      HttpUserAgent,
      DstPortNumber
  | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DstIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcHostname
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  EventCount: EventCount
  EventStartTime: EventStartTime
  EventEndTime: EventEndTime
  HttpUserAgent: HttpUserAgent
  DstPortNumber: DstPortNumber
alertDetailsOverride:
  alertDisplayNameFormat: "User '{{SrcUsername}}' with IP '{{SrcIpAddr}}' has been observed accessing URL '{{Url}}' using a rare user agent."
  alertDescriptionFormat: "The user agent '{{HttpUserAgent}}' has not been observed in the past 14 days. Conduct research on the user agent string to determine if it is associated with a known legitimate bot or if it is potentially linked to malicious activity. The URL is associated with the IP address '{{DstIpAddr}}'."
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let lookBack = 14d;
let timeframe = 1h;

Let Binding: eps

let eps = materialize(_Im_WebSession(starttime=ago(1d))
    | project TimeGenerated
    | summarize AvgPerSec = count() / 3600 by bin(TimeGenerated, 1h)
    | summarize round(avg(AvgPerSec))
    );

Let Binding: summarizationexist

let summarizationexist = (
    union isfuzzy=true 
        (
        WebSession_Summarized_SrcInfo_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: allUserAgents used in Stage 3

let allUserAgents = union isfuzzy=true
        (
        (datatable(exists: int, sumexist: bool)[1, false]
        | where toscalar(eps) > 1000
        | join (summarizationexist) on sumexist)
        | join (
            _Im_WebSession(starttime=todatetime(ago(2d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
            | where isnotempty(HttpUserAgent)
            | summarize UserAgentsSet =  make_set(HttpUserAgent)
            | extend exists=int(1)
            )
            on exists
        | project-away exist*, maxv, sum*
        ),
        (
        (datatable(exists: int, sumexist: bool)[1, false]
        | where toscalar(eps) between (501 .. 1000)
        | join (summarizationexist) on sumexist)
        | join (
            _Im_WebSession(starttime=todatetime(ago(3d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
            | where isnotempty(HttpUserAgent)
            | summarize UserAgentsSet = make_set(HttpUserAgent)
            | extend exists=int(1)
            )
            on exists
        | project-away exist*, maxv, sum*
        ),
        (
        (datatable(exists: int, sumexist: bool)[1, false]
        | where toscalar(eps) <= 500
        | join (summarizationexist) on sumexist)
        | join (
            _Im_WebSession(starttime=todatetime(ago(4d)), endtime=bin(ago(timeframe), 1h), eventresult='Success')
            | where isnotempty(HttpUserAgent)
            | summarize UserAgentsSet = make_set(HttpUserAgent)
            | extend exists=int(1)
            )
            on exists
        | project-away exist*, maxv, sum*
        ),
        (
        WebSession_Summarized_SrcInfo_CL
        | where EventTime_t between (ago(14d) .. ago(timeframe))
            and isnotempty(HttpUserAgent_s)
            and EventResult_s =~ 'Success'
        | summarize UserAgentsSet = make_set(HttpUserAgent_s)
        );

Stage 1: source

_Im_WebSession (starttime=bin(ago(timeframe), 1h), eventresult='Success')

Stage 2: project

| project
    SrcIpAddr,
    SrcUsername,
    SrcHostname,
    DstIpAddr,
    DstPortNumber,
    Url,
    HttpUserAgent,
    TimeGenerated

Stage 3: where

| where isnotempty(HttpUserAgent) and HttpUserAgent !in~ (allUserAgents)

Stage 4: summarize

| summarize
    EventCount=count(),
    EventStartTime=min(TimeGenerated),
    EventEndTime=max(TimeGenerated)
    by
    SrcIpAddr,
    SrcUsername,
    SrcHostname,
    DstIpAddr,
    Url,
    HttpUserAgent,
    DstPortNumber

Stage 5: extend

| 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
HttpUserAgentis_not_null
  • (no value, null check)
field:"HttpUserAgent" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
HttpUserAgenteqallUserAgentsexcludes:HttpUserAgent field:"HttpUserAgent" value:"allUserAgents"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrsummarize
DstPortNumbersummarize
EventCountsummarize
EventEndTimesummarize
EventStartTimesummarize
HttpUserAgentsummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
Urlsummarize
Nameextend
UPNSuffixextend

Encryption Password Added

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

Detects when an encryption password is added to Veeam Backup & Replication.

Rule Body

id: cfc967be-0428-4ab0-8c15-06c85649078f
name: Encryption Password Added
description: Detects when an encryption password is added to Veeam Backup & Replication.
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 == 31600\n| extend Hint = extract(\"\
  Hint=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    UserName = user,\n    [\"Hint\"] = Hint,\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 == 31600

Stage 3: extend

| extend Hint = extract("Hint=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
    ["Hint"] = Hint,
    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
Hintproject
MessageDetailsproject
Severityproject
UserNameproject

Palo Alto Prisma Cloud - Anomalous access key usage

#
Status
available
Severity
medium
Time window
5m
Group by
UserName
Source
github.com/Azure/Azure-Sentinel

'Detects anomalous API key usage activity.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: bd602b90-f7f9-4ae9-bf8c-3672a24deb39
name: Palo Alto Prisma Cloud - Anomalous access key usage
description: |
  'Detects anomalous API key usage activity.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: PaloAltoPrismaCloud
    dataTypes:
      - PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 10;
  PaloAltoPrismaCloud
  | where ResourceType =~ 'Login'
  | where EventResult =~ 'Failed'
  | where EventMessage has 'access key'
  | summarize count() by UserName, bin(TimeGenerated, 5m)
  | where count_ > threshold
  | extend AccountCustomEntity = UserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 10;

Stage 1: source

PaloAltoPrismaCloud

Stage 2: where

| where ResourceType =~ 'Login'

Stage 3: where

| where EventResult =~ 'Failed'

Stage 4: where

| where EventMessage has 'access key'

Stage 5: summarize

| summarize count() by UserName, bin(TimeGenerated, 5m)

Stage 6: where

| where count_ > threshold

Stage 7: extend

| extend AccountCustomEntity = UserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNamesummarize
AccountCustomEntityextend

SAP ETD - Login from unexpected network

#

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
medium
Time window
30m
Source
github.com/Azure/Azure-Sentinel

Identifies logons from an unexpected network. Source Action: Logon to the backend system from an IP address which is not assigned to one of the networks. networks can be maintained in the "SAP - Networks" watchlist of the Microsoft Sentinel Solution for SAP package. Data Sources: SAP Enterprise Thread Detection Solution - Alerts

MITRE ATT&CK Coverage

TacticTechniques
DiscoveryNo specific technique

Rule Body

id: 5dd72ebe-03ac-43ac-851b-68cfe5106e4f
kind: Scheduled
name: SAP ETD - Login from unexpected network
description: |
  Identifies logons from an unexpected network.
  Source Action: Logon to the backend system from an IP address which is not assigned to one of the networks.
  networks can be maintained in the "SAP - Networks" watchlist of the Microsoft Sentinel Solution for SAP package.
  
  *Data Sources: SAP Enterprise Thread Detection Solution -  Alerts*
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SAPETDAlerts
    dataTypes:
      - SAPETDAlerts_CL
queryFrequency: 5m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics: 
  - Discovery
relevantTechniques: []
query: |
  let AuditTimeAgo = 60m;
  let minThreshold= 1;
  let minScore= 50;
  let regex_sid = @"^([A-Z0-9]{3})/"; 
  let regex_client = @"/(\d{3})$";
  let SAPNetworks = _GetWatchlist('SAP - Networks');
  SAPETDAlerts_CL
  | where TimeGenerated > ago(AuditTimeAgo)
  | where Threshold >= minThreshold and Score >= minScore
  | where PatternName in ("Logon from external with SAP standard users","Access via unallowed IP Address")
  | mv-expand NormalizedTriggeringEvents
  | extend sapOriginalEvent = tostring(NormalizedTriggeringEvents)
  | extend Id_ = NormalizedTriggeringEvents.Id
  | extend extracted_user_ip = tostring(NormalizedTriggeringEvents.NetworkIPAddressInitiator)
  | where isnotempty(extracted_user_ip)
  | extend extracted_sap_user = NormalizedTriggeringEvents.UserAccountTargeted
  | extend extracted_sid = extract(regex_sid, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
  | extend extracted_client = extract(regex_client, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
  | extend extracted_instance_name = NormalizedTriggeringEvents.NetworkHostnameActor
  | extend extracted_instance_host = NormalizedTriggeringEvents.NetworkHostnameInitiator
  | evaluate ipv4_lookup(SAPNetworks, extracted_user_ip, Network, return_unmatched = true)
  | where isempty(Network)
  | project TimeGenerated, extracted_user_ip, extracted_sap_user, extracted_sid, extracted_client, extracted_instance_name, extracted_instance_host, AlertId, PatternName, PatternDescription, Status, NormalizedTriggeringEvents, Users
  | extend GeoLocation= iff(ipv4_is_private(extracted_user_ip), dynamic({"IsPrivate": true}), geo_info_from_ip_address(extracted_user_ip))
  | mv-expand Users
  | extend
    UserAccountName = tostring(Users.UserAccountName),
    UserEmail = tostring(Users.EmailAddresses[0])
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserAccountName
  - entityType: Mailbox
    fieldMappings:
      - identifier: MailboxPrimaryAddress
        columnName: UserEmail
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: extracted_sid
      - identifier: InstanceName
        columnName: extracted_instance_name
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: extracted_instance_host
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: extracted_user_ip
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP ETD - {{PatternName}} '
  alertDescriptionFormat: |
    {{PatternDescription}}
customDetails:
  SAP_User: extracted_sap_user
  SAP_UserEmail: UserEmail
  ETD_AlertNumber: AlertId
version: 1.0.4

Stages and Predicates

Parameters

let AuditTimeAgo = 60m;
let minThreshold = 1;
let minScore = 50;
let regex_sid = @"^([A-Z0-9]{3})/";
let regex_client = @"/(\d{3})$";
let SAPNetworks = _GetWatchlist('SAP - Networks');

Stage 1: source

SAPETDAlerts_CL

Stage 2: where

| where TimeGenerated > ago(AuditTimeAgo)

Stage 3: where

| where Threshold >= minThreshold and Score >= minScore

Stage 4: where

| where PatternName in ("Logon from external with SAP standard users","Access via unallowed IP Address")

Stage 5: mv-expand

| mv-expand NormalizedTriggeringEvents

Stage 6: extend (3 consecutive steps)

| extend sapOriginalEvent = tostring(NormalizedTriggeringEvents)
| extend Id_ = NormalizedTriggeringEvents.Id
| extend extracted_user_ip = tostring(NormalizedTriggeringEvents.NetworkIPAddressInitiator)

Stage 7: where

| where isnotempty(extracted_user_ip)

Stage 8: extend (5 consecutive steps)

| extend extracted_sap_user = NormalizedTriggeringEvents.UserAccountTargeted
| extend extracted_sid = extract(regex_sid, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
| extend extracted_client = extract(regex_client, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
| extend extracted_instance_name = NormalizedTriggeringEvents.NetworkHostnameActor
| extend extracted_instance_host = NormalizedTriggeringEvents.NetworkHostnameInitiator

Stage 9: evaluate

| evaluate ipv4_lookup(SAPNetworks, extracted_user_ip, Network, return_unmatched = true)

Stage 10: where

| where isempty(Network)

Stage 11: project

| project TimeGenerated, extracted_user_ip, extracted_sap_user, extracted_sid, extracted_client, extracted_instance_name, extracted_instance_host, AlertId, PatternName, PatternDescription, Status, NormalizedTriggeringEvents, Users

Stage 12: extend

| extend GeoLocation= iff(ipv4_is_private(extracted_user_ip), dynamic({"IsPrivate": true}), geo_info_from_ip_address(extracted_user_ip))
GeoLocation =
if(ipv4_is_in_range(extracted_user_ip, "10.0.0.0/8") or ipv4_is_in_range(extracted_user_ip, "172.16.0.0/12") or ipv4_is_in_range(extracted_user_ip, "192.168.0.0/16") or ipv4_is_in_range(extracted_user_ip, "169.254.0.0/16") or ipv4_is_in_range(extracted_user_ip, "127.0.0.0/8"))dynamic({<dynamic>, <dynamic>})
elsegeo_info_from_ip_address(extracted_user_ip)

Stage 13: mv-expand

| mv-expand Users

Stage 14: extend

| extend
  UserAccountName = tostring(Users.UserAccountName),
  UserEmail = tostring(Users.EmailAddresses[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Networkis_null
  • (no value, null check)
field:"Network" kind:is_null
PatternNamein
  • Access via unallowed IP Address
  • Logon from external with SAP standard users
field:"PatternName" kind:in
Scorege
  • 50 transforms: number
field:"Score" kind:ge value:"50"
Thresholdge
  • 1 transforms: number
field:"Threshold" kind:ge value:"1"
extracted_user_ipis_not_null
  • (no value, null check)
field:"extracted_user_ip" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertIdproject
NormalizedTriggeringEventsproject
PatternDescriptionproject
PatternNameproject
Statusproject
TimeGeneratedproject
Usersproject
extracted_clientproject
extracted_instance_hostproject
extracted_instance_nameproject
extracted_sap_userproject
extracted_sidproject
extracted_user_ipproject
GeoLocationextend
UserAccountNameextend
UserEmailextend

Silverfort - UserBruteForce Incident

#
Severity
high
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'A security weakness that allows attackers to gain unauthorized access to user accounts by systematically guessing the username and password combinations.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 46ff357b-9e98-465b-9e45-cd52fa4a7522
name: Silverfort - UserBruteForce Incident
description: |
  'A security weakness that allows attackers to gain unauthorized access to user accounts by systematically guessing the username and password combinations.'
severity: High
requiredDataConnectors:
  - connectorId: SilverfortAma
    dataTypes:
      - CommonSecurityLog
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110 #Brute Force
query: |-
  CommonSecurityLog 
  | where DeviceVendor has 'Silverfort'
  | where DeviceProduct has 'Admin Console'
  | where DeviceEventClassID == "NewIncident"
  | where Message has "UserBruteForce"
  | extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor has 'Silverfort'

Stage 3: where

| where DeviceProduct has 'Admin Console'

Stage 4: where

| where DeviceEventClassID == "NewIncident"

Stage 5: where

| where Message has "UserBruteForce"

Stage 6: extend

| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNameextend

Sonrai Ticket Escalation Executed

#

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
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'Checks if Sonrai tickets have had an escalation executed. It uses the action type to check if a ticket has had an escalation executed'

MITRE ATT&CK Coverage

Rule Body

id: 822fff15-ea68-4d0f-94ee-b4482ddb6f3a
name: Sonrai Ticket Escalation Executed
description: |
  'Checks if Sonrai tickets have had an escalation executed. 
  It uses the action type to check if a ticket has had an escalation executed'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SonraiDataConnector
    dataTypes:
      - Sonrai_Tickets_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - CommandAndControl
  - CredentialAccess
  - DefenseEvasion
  - Discovery
  - Execution
  - Exfiltration
  - Impact
  - InitialAccess
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1566
  - T1059
  - T1547
  - T1548
  - T1562
  - T1003
  - T1087
  - T1021
  - T1119
  - T1071
  - T1041
  - T1499
query: |
  Sonrai_Tickets_CL
  | where action_d == 8
entityMappings: 
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: digest_criticalResourceName_s
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  ticketName: digest_title_s
  ticketStatus: digest_status_s
  ticketSeverity: digest_severityCategory_s
  resourceLabel: digest_resourceLabel_s
  resourceType: digest_resourceType_s
  criticalResource: digest_criticalResourceName_s
  ticketOrg: digest_org_s
alertDetailsOverride:
  alertSeverityColumnName: digest_severityCategory_s
  alertDisplayNameFormat: Escalation Executed - {{digest_ticketSrn_s}} - {{digest_ticketKeyName_s}}
  alertDescriptionFormat: digest_ticketKeyDescription_s
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

Sonrai_Tickets_CL

Stage 2: where

| where action_d == 8

Indicators

These rows show field, operator, and value matches.

SUNSPOT malware hashes

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

This query uses Microsoft Defender for Endpoint data to look for IoCs associated with the SUNSPOT malware shared by Crowdstrike. More details: - https://www.crowdstrike.com/blog/sunspot-malware-technical-analysis/ - https://techcommunity.microsoft.com/blog/microsoftsentinelblog/monitoring-the-software-supply-chain-with-azure-sentinel/2176463/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 53e936c6-6c30-4d12-8343-b8a0456e8429
name: SUNSPOT malware hashes
description: |
  'This query uses Microsoft Defender for Endpoint data to look for IoCs associated with the SUNSPOT malware shared by Crowdstrike.
  More details: 
    - https://www.crowdstrike.com/blog/sunspot-malware-technical-analysis/
    - https://techcommunity.microsoft.com/blog/microsoftsentinelblog/monitoring-the-software-supply-chain-with-azure-sentinel/2176463/'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceImageLoadEvents
      - DeviceEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1554
tags:
  - Solorigate
  - NOBELIUM
query: |
  let SUNSPOT_Hashes = dynamic(["c45c9bda8db1d470f1fd0dcc346dc449839eb5ce9a948c70369230af0b3ef168", "0819db19be479122c1d48743e644070a8dc9a1c852df9a8c0dc2343e904da389"]);
  union isfuzzy=true(
  DeviceEvents
  | where InitiatingProcessSHA256 in (SUNSPOT_Hashes)),
  (DeviceImageLoadEvents
  | where InitiatingProcessSHA256 in (SUNSPOT_Hashes))
  | extend timestamp=TimeGenerated
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName 
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName 
        columnName: InitiatingProcessAccountUpn
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: UPNSuffix
        columnName: InitiatingProcessAccountDomain
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let SUNSPOT_Hashes = dynamic(["c45c9bda8db1d470f1fd0dcc346dc449839eb5ce9a948c70369230af0b3ef168", "0819db19be479122c1d48743e644070a8dc9a1c852df9a8c0dc2343e904da389"]);

Union isfuzzy=true (2 Sources)

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

Leg 1: DeviceEvents

DeviceEvents
| where InitiatingProcessSHA256 in (SUNSPOT_Hashes)

Leg 2: DeviceImageLoadEvents

DeviceImageLoadEvents
| where InitiatingProcessSHA256 in (SUNSPOT_Hashes)

Applied to the Combined Result

| extend timestamp=TimeGenerated
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
InitiatingProcessSHA256in
  • 0819db19be479122c1d48743e644070a8dc9a1c852df9a8c0dc2343e904da389
  • c45c9bda8db1d470f1fd0dcc346dc449839eb5ce9a948c70369230af0b3ef168
field:"InitiatingProcessSHA256" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
timestampextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Back to all Kusto rules