Detection rules › Kusto
Kusto rules: 3d
Antivirus Detected an Infected File
#Monitors CTERA platform to detect files infected with viruses identified by the antivirus engine on Edge Filers.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | contains |
| field:"SyslogMessage" kind:contains value:"found an infected file" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DetectionTime | project |
EdgeFiler | project |
FilePath | project |
Portal | project |
TimeGenerated | project |
Virus | project |
Apache - Known malicious user agent
#'Detects known malicious user agents'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | match |
| field:"HttpUserAgentOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
AWSCloudTrail - Creation of Lambda policy and then privilege escalation
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- ASL AWS Create Policy Version to allow all resources (Splunk)
- AWS IAM CompromisedKeyQuarantine Policy Attached to User (Elastic)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - CloudFormation policy created then used for privilege escalation (Kusto)
- AWSCloudTrail - Created CRUD S3 policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD DynamoDB policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD Lambda policy and then privilege escalation (Kusto)
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"ErrorCode" kind:is_null | |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
AccountUPNSuffix | summarize |
EventName | summarize |
EventSource | summarize |
PolicyName | summarize |
RecipientAccountId | summarize |
SourceIpAddress | summarize |
UserIdentityArn | summarize |
UserIdentityType | summarize |
AWSCloudTrail - Suspicious command sent to EC2
#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
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"ErrorCode" kind:is_null | |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | eq |
| field:"aws::eventName" kind:eq value:"PutObject" |
EventName | in |
| field:"EventName" kind:in |
Resources | contains |
| field:"Resources" kind:contains value:"accountId" |
params | is_not_null | field:"params" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
s3bucket | extend |
accountId | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Conditional Access - A Conditional Access app exclusion has changed
#A Conditional Access app exclusion has changed in Entra ID.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Update conditional access policy |
Rules Detecting the Same Action
These rules filter on the same operation.
- Azure MFA Disabled (Panther)
- Azure Policy Violation Detected (Panther)
- CA Policy Removed by Non Approved Actor (Sigma)
- CA Policy Updated by Non Approved Actor (Sigma)
- Certificate-Based Authentication Enabled (Sigma)
- Changes to Device Registration Policy (Sigma)
- Conditional Access - A Conditional Access Device platforms condition has changed (the Device platforms condition can be spoofed) (Kusto)
- Conditional Access - A Conditional Access policy was deleted (Kusto)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | in |
| field:"OperationName" kind:in value:"Update conditional access policy" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
OperationName | project |
TimeGenerated | project |
accountName | project |
excludeApplicationsNew | project |
excludeApplicationsOld | project |
modifiedBy | project |
policy | project |
result | project |
upnSuffix | project |
Detect presence of uncommon user agents in web requests (ASIM Web Session)
#'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
| Tactic | Techniques |
|---|---|
| Initial Access |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgent | is_not_null | field:"HttpUserAgent" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
HttpUserAgent | eq | allUserAgents | excludes:HttpUserAgent field:"HttpUserAgent" value:"allUserAgents" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | summarize |
DstPortNumber | summarize |
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
HttpUserAgent | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
Name | extend |
UPNSuffix | extend |
Encryption Password Added
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"31600" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
Hint | project |
MessageDetails | project |
Severity | project |
UserName | project |
Palo Alto Prisma Cloud - Anomalous access key usage
#'Detects anomalous API key usage activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"access key" |
EventResult | eq |
| field:"EventResult" kind:eq value:"Failed" |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"Login" |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | summarize |
AccountCustomEntity | extend |
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.
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
| Tactic | Techniques |
|---|---|
| Discovery | No 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 =(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>})geo_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.
| Field | Kind | Values | Search |
|---|---|---|---|
Network | is_null | field:"Network" kind:is_null | |
PatternName | in |
| field:"PatternName" kind:in |
Score | ge |
| field:"Score" kind:ge value:"50" |
Threshold | ge |
| field:"Threshold" kind:ge value:"1" |
extracted_user_ip | is_not_null | field:"extracted_user_ip" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertId | project |
NormalizedTriggeringEvents | project |
PatternDescription | project |
PatternName | project |
Status | project |
TimeGenerated | project |
Users | project |
extracted_client | project |
extracted_instance_host | project |
extracted_instance_name | project |
extracted_sap_user | project |
extracted_sid | project |
extracted_user_ip | project |
GeoLocation | extend |
UserAccountName | extend |
UserEmail | extend |
Silverfort - UserBruteForce Incident
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"NewIncident" |
DeviceProduct | match |
| field:"DeviceProduct" kind:match value:"Admin Console" |
DeviceVendor | match |
| field:"DeviceVendor" kind:match value:"Silverfort" |
Message | match |
| field:"Message" kind:match value:"UserBruteForce" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | extend |
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.
'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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
action_d | eq |
| field:"action_d" kind:eq value:"8" |
SUNSPOT malware hashes
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingProcessSHA256 | in |
| field:"InitiatingProcessSHA256" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |