Detection rules › Kusto
Kusto rules: 2y
AWSCloudTrail - Creation of DataPipeline policy and then privilege escalation
#Identifies creation of IAM policies that combine AWS Data Pipeline permissions with IAM privileges such as iam:PassRole, followed by attachment activity to users, roles, or groups. This sequence can enable escalation paths and should be reviewed for unauthorized privilege assignment.
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: 6009c632-94e9-4ffb-a11a-b4b99f457f88
name: AWSCloudTrail - Creation of DataPipeline policy and then privilege escalation
description: |
Identifies creation of IAM policies that combine AWS Data Pipeline permissions with IAM privileges such as
iam:PassRole, followed by attachment activity to users, roles, or groups. This sequence can enable escalation
paths and should be reviewed for unauthorized privilege assignment.
severity: High
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 = 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 = materialize( 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 "datapipeline:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:CreatePipeline" and Action contains "datapipeline:PutPipelineDefinition" and Action contains "datapipeline:ActivatePipeline") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:Create*" and Action contains "datapipeline:Put*" and Action contains "datapipeline:Activate*")) and Resource == "*" and Condition == ""
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
| project-rename StartTime = TimeGenerated );
let PolicyAttach = materialize( 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 Data Pipeline privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.4
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 = materialize( 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 "datapipeline:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:CreatePipeline" and Action contains "datapipeline:PutPipelineDefinition" and Action contains "datapipeline:ActivatePipeline") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:Create*" and Action contains "datapipeline:Put*" and Action contains "datapipeline:Activate*")) 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 |
Cisco SE - Unexpected binary file
#'Detects binary files in uncommon locations.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: eabb9c20-7b0b-4a77-81e8-b06944f351c6
name: Cisco SE - Unexpected binary file
description: |
'Detects binary files in uncommon locations.'
severity: Medium
requiredDataConnectors:
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
CiscoSecureEndpoint
| where isnotempty(SrcFileName)
| where SrcFilePath contains @'AppData\Local\Temp' or SrcFilePath contains @'OUTLOOK_TEMP'
| extend ext = extract(@"(.*\/)?(.*)", 2, tostring(SrcFilePath))
| where ext contains 'dll' or ext contains @'ps1' or ext contains @'exe'
| extend HostCustomEntity = DstHostname
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSecureEndpoint
Stage 2: where
| where isnotempty(SrcFileName)
Stage 3: where
| where SrcFilePath contains @'AppData\Local\Temp' or SrcFilePath contains @'OUTLOOK_TEMP'
Stage 4: extend
| extend ext = extract(@"(.*\/)?(.*)", 2, tostring(SrcFilePath))
Stage 5: where
| where ext contains 'dll' or ext contains @'ps1' or ext contains @'exe'
Stage 6: extend
| extend HostCustomEntity = DstHostname
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SrcFileName | is_not_null | field:"SrcFileName" kind:is_not_null | |
SrcFilePath | contains |
| field:"SrcFilePath" kind:contains |
ext | contains |
| field:"ext" kind:contains |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ext | extend |
HostCustomEntity | extend |
CiscoISE - Device changed IP in last 24 hours
#'Detects when device changes IP address in last 24 hours.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 0c509e9b-121e-4951-9f9b-43722e052b4f
name: CiscoISE - Device changed IP in last 24 hours
description: |
'Detects when device changes IP address in last 24 hours.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1568
query: |
let lbtime_48h = 48h;
let lbtime_24h = 24h;
CiscoISEEvent
| where TimeGenerated between (ago(lbtime_48h) .. ago(lbtime_24h))
| where notempty(DvcIpAddr) and notempty(DvcHostname)
| summarize knownIPs = make_set(DvcIpAddr) by DvcHostname
| join (CiscoISEEvent
| where TimeGenerated > ago(lbtime_24h)
| where notempty(DvcIpAddr) and notempty(DvcHostname)
| summarize evts = count() by DvcHostname, DvcIpAddr
| project-away evts) on DvcHostname
| project-away DvcHostname1
| where knownIPs !contains DvcIpAddr
| extend HostCustomEntity = DvcHostname
| extend IPCustomEntity = DvcIpAddr
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let lbtime_48h = 48h;
let lbtime_24h = 24h;
Stage 1: source
CiscoISEEvent
Stage 2: where
| where TimeGenerated between (ago(lbtime_48h) .. ago(lbtime_24h))
Stage 3: where
| where notempty(DvcIpAddr) and notempty(DvcHostname)
Stage 4: summarize
| summarize knownIPs = make_set(DvcIpAddr) by DvcHostname
Stage 5: join
| join (CiscoISEEvent
| where TimeGenerated > ago(lbtime_24h)
| where notempty(DvcIpAddr) and notempty(DvcHostname)
| summarize evts = count() by DvcHostname, DvcIpAddr
| project-away evts) on DvcHostname
Stage 6: project-away
project-away DvcHostname1
Stage 7: where
where knownIPs !contains DvcIpAddr
Stage 8: extend
extend HostCustomEntity
Stage 9: extend
extend IPCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcHostname | is_not_null | field:"DvcHostname" kind:is_not_null | |
DvcIpAddr | is_not_null | field:"DvcIpAddr" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DvcHostname | summarize |
knownIPs | summarize |
HostCustomEntity | extend |
IPCustomEntity | extend |
CTE - Possible Company-Wide C2 Campaign (Multiple Devices, Same Destination)
#Multiple distinct devices within the same tenant were blocked attempting to reach the same URL/domain within a 24-hour window. Isolated blocks are usually just one user's browsing; several independent devices converging on the same destination is a strong signal of a coordinated malware/C2 campaign spreading across the company (e.g. a phishing link opened by several employees, or malware propagating internally).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | |
| Command & Control |
Rule Body
id: ec08477d-87a1-498d-8f34-72b8ec8f3758
name: CTE - Possible Company-Wide C2 Campaign (Multiple Devices, Same Destination)
description: |
Multiple distinct devices within the same tenant were blocked attempting to reach the same URL/domain within a 24-hour window. Isolated blocks are usually just one user's browsing; several independent devices converging on the same destination is a strong signal of a coordinated malware/C2 campaign spreading across the company (e.g. a phishing link opened by several employees, or malware propagating internally).
severity: High
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 30m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- LateralMovement
relevantTechniques:
- T1071
- T1570
query: |
CybereinforceCTE_CL
| where EventType == "block.url"
| extend d = parse_json(Details)
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
| summarize DeviceCount=dcount(DeviceId), AffectedHosts=make_set(HostName, 50), SampleIps=make_set(ClientIp, 20), AnyIoc=max(IsIoc), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Pattern, Url, TenantId
| where DeviceCount >= 3
| extend ClientIp = tostring(SampleIps[0])
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "block.url"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0])
HostName =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("cte-device-", substring(EntityId, 0, 8))Stage 5: summarize
| summarize DeviceCount=dcount(DeviceId), AffectedHosts=make_set(HostName, 50), SampleIps=make_set(ClientIp, 20), AnyIoc=max(IsIoc), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Pattern, Url, TenantId
Stage 6: where
| where DeviceCount >= 3
Stage 7: extend
| extend ClientIp = tostring(SampleIps[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceCount | ge |
| field:"DeviceCount" kind:ge value:"3" |
EventType | eq |
| field:"EventType" kind:eq value:"block.url" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedHosts | summarize |
AnyIoc | summarize |
DeviceCount | summarize |
FirstSeen | summarize |
LastSeen | summarize |
Pattern | summarize |
SampleIps | summarize |
TenantId | summarize |
Url | summarize |
ClientIp | extend |
Cyren High-Risk IP Indicators
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Detects high-risk IP indicators (risk score >= 80) from Cyren threat intelligence feeds in the last 24 hours. These IPs are associated with malicious activity such as malware distribution, phishing, or botnet command and control.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 5d7e8b3a-1f2c-4e5d-9a0b-c1d2e3f4a5b6
name: Cyren High-Risk IP Indicators
description: |
'Detects high-risk IP indicators (risk score >= 80) from Cyren threat intelligence feeds in the last 24 hours.
These IPs are associated with malicious activity such as malware distribution, phishing, or botnet command and control.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CyrenThreatIntel
dataTypes:
- Cyren_Indicators_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Impact
relevantTechniques:
- T1071
- T1568
query: |
Cyren_Indicators_CL
| where TimeGenerated > ago(1d)
| where isnotempty(ip_s)
| extend Risk = toint(risk_d)
| where Risk >= 80
| summarize
DetectionCount = count(),
MaxRisk = max(Risk),
Categories = make_set(category_s)
by IP = ip_s, Source = source_s
| where DetectionCount >= 1
| extend
IPAddress = IP,
ThreatCategories = strcat_array(Categories, ", ")
suppressionDuration: 1h
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: Selected
groupByEntities:
- IP
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
RiskScore: MaxRisk
DetectionCount: DetectionCount
Categories: ThreatCategories
Source: Source
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Cyren_Indicators_CL
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where isnotempty(ip_s)
Stage 4: extend
| extend Risk = toint(risk_d)
Stage 5: where
| where Risk >= 80
Stage 6: summarize
| summarize
DetectionCount = count(),
MaxRisk = max(Risk),
Categories = make_set(category_s)
by IP = ip_s, Source = source_s
Stage 7: where
| where DetectionCount >= 1
Stage 8: extend
| extend
IPAddress = IP,
ThreatCategories = strcat_array(Categories, ", ")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DetectionCount | ge |
| field:"DetectionCount" kind:ge value:"1" |
Risk | ge |
| field:"Risk" kind:ge value:"80" |
ip_s | is_not_null | field:"ip_s" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Categories | summarize |
DetectionCount | summarize |
IP | summarize |
MaxRisk | summarize |
Source | summarize |
IPAddress | extend |
ThreatCategories | extend |
Detect Unknown process launched via WinRM
#When an unknown process is being launched from the WinRM service on a server, this might indicate a malicious actor spreading malware on various servers via the WinRM protocol.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
References
Telemetry Coverage
Rule Body
DeviceProcessEvents
| where InitiatingProcessFileName contains "wsmprovhost.exe"
| invoke FileProfile(SHA1)
| where GlobalPrevalence < 1000
| join kind=leftouter (
DeviceNetworkEvents
| where ActionType == "InboundConnectionAccepted"
| where LocalPort in ("5985", "5986")
| distinct RemoteIP, DeviceId
) on DeviceId
| project-away DeviceId1
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where InitiatingProcessFileName contains "wsmprovhost.exe"
Stage 3: invoke
| invoke FileProfile(SHA1)
Stage 4: where
| where GlobalPrevalence < 1000
Stage 5: join
| join kind=leftouter (
DeviceNetworkEvents
| where ActionType == "InboundConnectionAccepted"
| where LocalPort in ("5985", "5986")
| distinct RemoteIP, DeviceId
) on DeviceId
Stage 6: project-away
project-away DeviceId1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"InboundConnectionAccepted" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"1000" |
InitiatingProcessFileName | contains |
| field:"parent_process_basename" kind:contains value:"wsmprovhost.exe" |
LocalPort | in |
| field:"LocalPort" kind:in |
Digital Guardian - Sensitive data transfer over insecure channel
#'Detects sensitive data transfer over insecure channel.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: b52cda18-c1af-40e5-91f3-1fcbf9fa267e
name: Digital Guardian - Sensitive data transfer over insecure channel
description: |
'Detects sensitive data transfer over insecure channel.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
query: |
DigitalGuardianDLPEvent
| where isnotempty(MatchedPolicies)
| where isnotempty(inspected_document)
| where NetworkApplicationProtocol =~ 'HTTP'
| extend AccountCustomEntity = SrcUserName, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
DigitalGuardianDLPEvent
Stage 2: where
| where isnotempty(MatchedPolicies)
Stage 3: where
| where isnotempty(inspected_document)
Stage 4: where
| where NetworkApplicationProtocol =~ 'HTTP'
Stage 5: extend
| extend AccountCustomEntity = SrcUserName, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
Disable or Modify Windows Defender
#This detection watches the commandline logs for known commands that are used to disable the Defender AV. This is based on research performed by @olafhartong on a large sample of malware for varying purposes. Note that this detection is imperfect and is only meant to serve as basis for building a more resilient detection rule. Make the detection more resilient, currently the order of parameters matters. You don't want that for a production rule. See blogpost (https://medium.com/falconforce/falconfriday-av-manipulation-0xff0e-67ed4387f9ab?source=friends_link&sk=3c7c499797bbb4d74879e102ef3ecf8f) for more resilience considerations. The current approach can easily be bypassed by not using the powershell.exe executable. Consider adding more ways to detect this behavior.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 20d52a04-b5d8-402d-88e2-7929d12cbdcd
name: Disable or Modify Windows Defender
description: |
This detection watches the commandline logs for known commands that are used to disable the Defender AV. This is based on research performed by @olafhartong on a large sample of malware for varying purposes.
Note that this detection is imperfect and is only meant to serve as basis for building a more resilient detection rule. Make the detection more resilient, currently the order of parameters matters. You don't want that for a production rule.
See blogpost (https://medium.com/falconforce/falconfriday-av-manipulation-0xff0e-67ed4387f9ab?source=friends_link&sk=3c7c499797bbb4d74879e102ef3ecf8f) for more resilience considerations. The current approach can easily be bypassed by not using the powershell.exe executable.
Consider adding more ways to detect this behavior.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562.001
query: |
let defendertampering=dynamic(["Set-MpPreference -DisableRealtimeMonitoring $true","sc stop WinDefend","sc delete WinDefend","Set-MpPreference -DisableBehaviorMonitoring $true","Set-MpPreference -ExclusionProcess", "Set-MpPreference -ExclusionExtension dll","net stop security center"]);
DeviceProcessEvents
| where ProcessCommandLine has_any (defendertampering)
// If you have a lot of false positives coming from JetBrains, you can use the line below.
//| where InitiatingProcessFolderPath !startswith @"c:\program files\jetbrains\" and InitiatingProcessVersionInfoProductName !~ ("Android Studio")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: AccountSid
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: defendertampering
let defendertampering = dynamic(["Set-MpPreference -DisableRealtimeMonitoring $true","sc stop WinDefend","sc delete WinDefend","Set-MpPreference -DisableBehaviorMonitoring $true","Set-MpPreference -ExclusionProcess", "Set-MpPreference -ExclusionExtension dll","net stop security center"]);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where ProcessCommandLine has_any (defendertampering)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
Guardian- Racial Bias Policy Violation Detection
#'This alert creates an incident when Racial Bias Policy Violation detected from the Guardian.'
Rule Body
id: 46103101-43d9-4c09-b8c8-898dcafe73c0
name: Guardian- Racial Bias Policy Violation Detection
description: |
'This alert creates an incident when Racial Bias Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Racial Bias'
| where Severity =~ 'Low'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Racial Bias Policy Violation detection
alertDescriptionFormat: |
This query detects Racial Bias Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Racial Bias'
Stage 3: where
| where Severity =~ 'Low'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Racial Bias" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
NordPass - Activity token revocation
#This will alert you when the event reporting token is revoked, posing the risk of active integration being blocked.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule Body
id: 800314a6-759a-4575-93e2-1e080b1d33f9
name: NordPass - Activity token revocation
kind: Scheduled
version: 1.0.0
displayName: Activity token revocation
description: This will alert you when the event reporting token is revoked, posing the risk of active integration being blocked.
severity: Medium
query: |
NordPassEventLogs_CL
| where event_type == "integrations"
| where action == "activities_token_revoked"
| extend TargetEmail = user_email
requiredDataConnectors:
- connectorId: NordPass
dataTypes:
- NordPassEventLogs_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1134
incidentConfiguration:
createIncident: false
entityMappings:
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: TargetEmail
Stages and Predicates
Stage 1: source
NordPassEventLogs_CL
Stage 2: where
| where event_type == "integrations"
Stage 3: where
| where action == "activities_token_revoked"
Stage 4: extend
| extend TargetEmail = user_email
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"activities_token_revoked" |
event_type | eq |
| field:"event_type" kind:eq value:"integrations" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetEmail | extend |
Oracle - Multiple user agents for single source
#'Detects requests with different user agents from one source in short timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 44c7d12a-573f-11ec-bf63-0242ac130002
name: Oracle - Multiple user agents for single source
description: |
'Detects requests with different user agents from one source in short timeframe.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- OracleWebLogicServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let threshold = 5;
OracleWebLogicServerEvent
| where isnotempty(HttpUserAgentOriginal)
| summarize ua_count=makeset(HttpUserAgentOriginal) by SrcIpAddr, bin(TimeGenerated, 5m)
| where array_length(ua_count) > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
OracleWebLogicServerEvent
Stage 2: where
| where isnotempty(HttpUserAgentOriginal)
Stage 3: summarize
| summarize ua_count=makeset(HttpUserAgentOriginal) by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where array_length(ua_count) > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | is_not_null | field:"HttpUserAgentOriginal" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | summarize |
ua_count | summarize |
IPCustomEntity | extend |
Pathlock TDnR - OData Application Log Events
#Detects security events from SAP OData service application logs (SLG1), forwarded by Pathlock Threat Detection and Response. OData anomalies may indicate API abuse, unauthorized mass data extraction via OData endpoints, or exploitation of SAP Fiori and S/4HANA OData services.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c59
name: Pathlock TDnR - OData Application Log Events
kind: Scheduled
description: >-
Detects security events from SAP OData service application logs (SLG1), forwarded by Pathlock
Threat Detection and Response. OData anomalies may indicate API abuse, unauthorized mass data
extraction via OData endpoints, or exploitation of SAP Fiori and S/4HANA OData services.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1213
- T1048
query: |
Pathlock_TDnR_CL
| where DataSource == "SLG1_ODATA"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "SLG1_ODATA"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"SLG1_ODATA" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Pathlock TDnR - SAP RFC Gateway Events
#Detects security-relevant events from the SAP RFC gateway logfile, forwarded by Pathlock Threat Detection and Response. Suspicious gateway activity may indicate unauthorized RFC connections, lateral movement across SAP systems, or command-and-control communication through the gateway.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | |
| Command & Control |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c26
name: Pathlock TDnR - SAP RFC Gateway Events
kind: Scheduled
description: >-
Detects security-relevant events from the SAP RFC gateway logfile, forwarded by Pathlock Threat
Detection and Response. Suspicious gateway activity may indicate unauthorized RFC connections,
lateral movement across SAP systems, or command-and-control communication through the gateway.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- CommandAndControl
relevantTechniques:
- T1021
- T1071
query: |
Pathlock_TDnR_CL
| where DataSource == "GATEWAY_LOG"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "GATEWAY_LOG"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"GATEWAY_LOG" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Potential Build Process Compromise - MDE
#The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process. This query uses Microsoft Defender for Endpoint telemetry. More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 1bf6e165-5e32-420e-ab4f-0da8558a8be2
name: Potential Build Process Compromise - MDE
description: |
'The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process. This query uses Microsoft Defender for Endpoint telemetry.
More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
- DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1554
tags:
- Solorigate
- NOBELIUM
query: |
// How far back to look for events from
let timeframe = 1d;
// How close together build events and file modifications should occur to alert (make this smaller to reduce FPs)
let time_window = 5m;
// Edit this to include build processes used
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
// Include any processes that you want to allow to edit files during/around the build process
let allow_list = dynamic([]);
DeviceProcessEvents
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where FileName has_any (build_processes)
| summarize by BuildParentProcess=InitiatingProcessFileName, BuildProcess=FileName, BuildAccount = AccountName, DeviceName, BuildCommand=ProcessCommandLine,
timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
DeviceFileEvents
| where TimeGenerated > ago(timeframe)
| where InitiatingProcessFileName !in (allow_list)
| where ActionType == "FileCreated" or ActionType == "FileModified"
// Look for code files, edit this to include file extensions used in build.
| where FileName endswith ".cs" or FileName endswith ".cpp"
| summarize by FileEditParentProcess=InitiatingProcessParentFileName, FileEditAccount = InitiatingProcessAccountName, FileEditDomain = InitiatingProcessAccountDomain, FileEditUpn = InitiatingProcessAccountUpn,
DeviceName, FileEdited=FileName, FileEditProcess=InitiatingProcessFileName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, DeviceName
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess) by timekey, DeviceName, BuildParentProcess, BuildProcess, FileEditAccount, FileEditDomain, FileEditUpn
| 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: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: FileEditUpn
- identifier: Name
columnName: FileEditAccount
- identifier: UPNSuffix
columnName: FileEditDomain
version: 1.1.0
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
let time_window = 5m;
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
let allow_list = dynamic([]);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where TimeGenerated > ago(timeframe)
Stage 3: where
| where FileName has_any (build_processes)
Stage 4: summarize
| summarize by BuildParentProcess=InitiatingProcessFileName, BuildProcess=FileName, BuildAccount = AccountName, DeviceName, BuildCommand=ProcessCommandLine,
timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
Stage 5: join
| join kind=inner(
DeviceFileEvents
| where TimeGenerated > ago(timeframe)
| where InitiatingProcessFileName !in (allow_list)
| where ActionType == "FileCreated" or ActionType == "FileModified"
| where FileName endswith ".cs" or FileName endswith ".cpp"
| summarize by FileEditParentProcess=InitiatingProcessParentFileName, FileEditAccount = InitiatingProcessAccountName, FileEditDomain = InitiatingProcessAccountDomain, FileEditUpn = InitiatingProcessAccountUpn,
DeviceName, FileEdited=FileName, FileEditProcess=InitiatingProcessFileName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
on timekey, DeviceName
Stage 6: where
where BuildProcessTime <= FileEditTime
Stage 7: summarize
summarize by timekey, DeviceName, BuildParentProcess, BuildProcess, FileEditAccount, FileEditDomain, FileEditUpn
Stage 8: extend
extend DomainIndex, HostName
Stage 9: extend
extend HostNameDomain
HostNameDomain =DomainIndex != -1substring(DeviceName, (DomainIndex + 1))DeviceNameIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq |
FileName | ends_with |
| field:"FileName" kind:ends_with |
FileName | match |
| field:"process_basename" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessFileName | eq | [] | excludes:InitiatingProcessFileName field:"InitiatingProcessFileName" value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
BuildParentProcess | summarize |
BuildProcess | summarize |
DeviceName | summarize |
FileEditAccount | summarize |
FileEditDomain | summarize |
FileEditUpn | summarize |
timekey | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
ProofpointPOD - Multiple archived attachments to the same recipient
#'Detects when multiple emails where sent to the same recipient with large archived attachments.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: bda5a2bd-979b-4828-a91f-27c2a5048f7f
name: ProofpointPOD - Multiple archived attachments to the same recipient
description: |
'Detects when multiple emails where sent to the same recipient with large archived attachments.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ProofpointPOD
dataTypes:
- ProofpointPOD_message_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
let lbtime = 30m;
let msgthreshold = 3;
let compressedTypes = dynamic(['zip', 'rar', 'tar', 'x-7z-compressed']);
ProofpointPOD
| where TimeGenerated > ago(lbtime)
| where EventType == 'message'
| where NetworkDirection == 'outbound'
| extend attachedMimeType = todynamic(MsgParts)[0]['detectedMime']
| where attachedMimeType has_any (compressedTypes)
| summarize count(), make_set(attachedMimeType) by SrcUserUpn, DstUserUpn
| where count_ > msgthreshold
| extend AccountCustomEntity = SrcUserUpn
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let lbtime = 30m;
let msgthreshold = 3;
let compressedTypes = dynamic(['zip', 'rar', 'tar', 'x-7z-compressed']);
Stage 1: source
ProofpointPOD
Stage 2: where
| where TimeGenerated > ago(lbtime)
Stage 3: where
| where EventType == 'message'
Stage 4: where
| where NetworkDirection == 'outbound'
Stage 5: extend
| extend attachedMimeType = todynamic(MsgParts)[0]['detectedMime']
Stage 6: where
| where attachedMimeType has_any (compressedTypes)
Stage 7: summarize
| summarize count(), make_set(attachedMimeType) by SrcUserUpn, DstUserUpn
Stage 8: where
| where count_ > msgthreshold
Stage 9: extend
| extend AccountCustomEntity = SrcUserUpn
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"message" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"outbound" |
attachedMimeType | match |
| field:"attachedMimeType" kind:match |
count_ | gt |
| field:"count_" kind:gt value:"3" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserUpn | summarize |
SrcUserUpn | summarize |
AccountCustomEntity | extend |
Recovery Token Deleted
#Detects when a recovery token is deleted. This might indicate unauthorized removal of critical components.
Rule Body
id: 9f37ded4-f27a-4ef6-b5e6-de6430070808
name: Recovery Token Deleted
description: Detects when a recovery token is deleted. This might indicate unauthorized
removal of critical components.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 36013\n| extend TokenID = extract(\"\
TokenID=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"User Name\"] = user,\n [\"Token ID\"] = TokenID,\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 == 36013
Stage 3: extend
| extend TokenID = extract("TokenID=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Token ID"] = TokenID,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"36013" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
Token ID | project |
User Name | project |
Semperis DSP RBAC Changes
#Alerts when there are RBAC changes in the DSP system.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Semperis-Operation-Log | Event ID 20012: DSP RBAC change |
Rule Body
id: "e5edf3f3-de53-45e6-b0d7-1ce1c048df4a"
name: Semperis DSP RBAC Changes
description: |
'Alerts when there are RBAC changes in the DSP system.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SemperisDSP
dataTypes:
- dsp_parser
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1548
- T1098
query: |
SecurityEvent
| where EventSourceName == 'Semperis-Operation-Log' and EventID == 20012
| order by TimeGenerated desc
| extend p1Xml = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array p1Xml
| evaluate bag_unpack(p1Xml)
| extend Name=column_ifexists('@Name', ''), Value=column_ifexists('#text', '')
| evaluate pivot(Name, any(Value), TimeGenerated, Computer, Level, EventLevelName, EventID, Type, _ResourceId)
| extend det = column_ifexists('details', '')
| parse det with "Occured at (UTC): " OccurredAt "Session ID: " SessionID "Trustee Name: " TrusteeName "Correlation ID: " CorrelationID "Source: " Source "WebSite Target: " WebSiteTarget "Product: " Product "Component: " Component "AD Information: " ADInformation "Object GUID: " ObjectGUID "Attribute: " Attribute "Distinguished Name: " DistinguishedName "Additional Information: "AdditionalInformation "Operation Detail: " OperationDetail "operationName: " operationName "trustee: " trustee "personas: " personas "Status: " status "Granted: " Granted "Result: " Result
| extend _AccessGranted = iif(operationName contains "CreateRbacIdentity", "Added", "Removed")
| extend _Identity = iif(operationName contains "CreateRbacIdentity", trustee, tostring(substring(trustee, 1, strlen(trustee))))
| extend _Identity = iif(operationName contains "CreateRbacIdentity", _Identity, replace_string(_Identity, "'", ""))
| extend add_personas = replace_string(replace_string(replace_string(personas, "{ Name = ", ""), " }", ""), ";", ",")
| extend remove_personas = replace_string(personas, ";", ",")
| extend grid_personas = iif(operationName contains "CreateRbacIdentity", add_personas, remove_personas)
| extend date_to_sort = format_datetime(TimeGenerated, "yyyy-mm-dd HH:mm:ss")
| order by date_to_sort desc
| extend NTDomain = tostring(split(TrusteeName, '\\', 0)[0]), LoginUser = tostring(split(TrusteeName, '\\', 1)[0])
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: LoginUser
- identifier: NTDomain
columnName: NTDomain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: RBAC Change -- Alert from Semperis Directory Services Protector
alertDescriptionFormat: A RBAC change was detected in the DSP system.
version: 2.0.7
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventSourceName == 'Semperis-Operation-Log' and EventID == 20012
Stage 3: sort
| order by TimeGenerated desc
Stage 4: extend
| extend p1Xml = parse_xml(EventData).DataItem.EventData.Data
Stage 5: mv-expand
| mv-expand bagexpansion=array p1Xml
Stage 6: evaluate
| evaluate bag_unpack(p1Xml)
Stage 7: extend
| extend Name=column_ifexists('@Name', ''), Value=column_ifexists('#text', '')
Stage 8: evaluate
| evaluate pivot(Name, any(Value), TimeGenerated, Computer, Level, EventLevelName, EventID, Type, _ResourceId)
Stage 9: extend
| extend det = column_ifexists('details', '')
Stage 10: parse
| parse det with "Occured at (UTC): " OccurredAt "Session ID: " SessionID "Trustee Name: " TrusteeName "Correlation ID: " CorrelationID "Source: " Source "WebSite Target: " WebSiteTarget "Product: " Product "Component: " Component "AD Information: " ADInformation "Object GUID: " ObjectGUID "Attribute: " Attribute "Distinguished Name: " DistinguishedName "Additional Information: "AdditionalInformation "Operation Detail: " OperationDetail "operationName: " operationName "trustee: " trustee "personas: " personas "Status: " status "Granted: " Granted "Result: " Result
Stage 11: extend (7 consecutive steps)
| extend _AccessGranted = iif(operationName contains "CreateRbacIdentity", "Added", "Removed")
| extend _Identity = iif(operationName contains "CreateRbacIdentity", trustee, tostring(substring(trustee, 1, strlen(trustee))))
| extend _Identity = iif(operationName contains "CreateRbacIdentity", _Identity, replace_string(_Identity, "'", ""))
| extend add_personas = replace_string(replace_string(replace_string(personas, "{ Name = ", ""), " }", ""), ";", ",")
| extend remove_personas = replace_string(personas, ";", ",")
| extend grid_personas = iif(operationName contains "CreateRbacIdentity", add_personas, remove_personas)
| extend date_to_sort = format_datetime(TimeGenerated, "yyyy-mm-dd HH:mm:ss")
Stage 12: sort
| order by date_to_sort desc
Stage 13: extend
| extend NTDomain = tostring(split(TrusteeName, '\\', 0)[0]), LoginUser = tostring(split(TrusteeName, '\\', 1)[0])
Stage 14: extend
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"20012" |
EventSourceName | eq |
| field:"Category" kind:eq value:"Semperis-Operation-Log" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
p1Xml | extend |
Name | extend |
Value | extend |
det | extend |
_AccessGranted | extend |
_Identity | extend |
add_personas | extend |
remove_personas | extend |
grid_personas | extend |
date_to_sort | extend |
LoginUser | extend |
NTDomain | extend |
DnsDomain | extend |
HostName | extend |
Tailscale: Exit node advertised or approved
#Identifies when a device starts advertising itself as an exit node, or when an admin approves one. Validate the device and operator - rogue exit nodes can intercept tailnet egress.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: f42f2906-c8e6-23d0-e48c-0620e50d5510
name: "Tailscale: Exit node advertised or approved"
description: |
Identifies when a device starts advertising itself as an exit node, or when an admin approves one. Validate the device and operator - rogue exit nodes can intercept tailnet egress.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Audit_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1090
query: |
Tailscale_Audit_CL
| where Action contains "EXIT" or tostring(New.advertisedExitNode) == "true" or tostring(New.allowedExitNode) == "true"
| extend ActorLogin = tostring(Actor.loginName)
| extend NodeName = tostring(Target.name)
| extend NodeId = tostring(Target.id)
| project TimeGenerated, ActorLogin, Action, NodeName, NodeId, Origin, New, Old
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorLogin
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: NodeName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Tailscale_Audit_CL
Stage 2: where
| where Action contains "EXIT" or tostring(New.advertisedExitNode) == "true" or tostring(New.allowedExitNode) == "true"
Stage 3: extend (3 consecutive steps)
| extend ActorLogin = tostring(Actor.loginName)
| extend NodeName = tostring(Target.name)
| extend NodeId = tostring(Target.id)
Stage 4: project
| project TimeGenerated, ActorLogin, Action, NodeName, NodeId, Origin, New, Old
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains value:"EXIT" |
New.advertisedExitNode | eq |
| field:"New.advertisedExitNode" kind:eq value:"true" |
New.allowedExitNode | eq |
| field:"New.allowedExitNode" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActorLogin | project |
New | project |
NodeId | project |
NodeName | project |
Old | project |
Origin | project |
TimeGenerated | project |
TI Map IP entity to Azure Key Vault logs
#Identifies a match in Azure Key Vault logs from any IP IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 7c8051a7-3d29-4c0d-a340-893423f7b0a5
name: TI Map IP entity to Azure Key Vault logs
description: |
Identifies a match in Azure Key Vault logs from any IP IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: AzureKeyVault
dataTypes:
- KeyVaultData
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Look back 1 hour for AzureDiagnostics logs
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
// Fetch threat intelligence indicators related to IP addresses
let IP_Indicators = ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
| extend NetworkSourceIP = toupper(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// Perform a join between IP indicators and AzureDiagnostics logs for Key Vault events
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
// Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
| join kind=innerunique (
AzureDiagnostics
| where ResourceType =~ "VAULTS"
| where TimeGenerated >= ago(dt_lookBack)
| extend KeyVaultEvents_TimeGenerated = TimeGenerated, ClientIP = CallerIPAddress
)
on $left.TI_ipEntity == $right.ClientIP
// Filter out logs that occurred after the expiration of the corresponding indicator
| where KeyVaultEvents_TimeGenerated < ValidUntil
// Group the results by IndicatorId and ClientIP, and keep the log entry with the latest timestamp
| summarize KeyVaultEvents_TimeGenerated = arg_max(KeyVaultEvents_TimeGenerated, *) by Id, ClientIP
// Select the desired output fields
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project KeyVaultEvents_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence,
TI_ipEntity, ClientIP, ResourceId, SubscriptionId, OperationName, ResultType, CorrelationId, id_s, clientInfo_s, httpStatusCode_d,
identity_claim_appid_g, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, Type
// Rename the timestamp field
| extend timestamp = KeyVaultEvents_TimeGenerated
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: ResourceId
version: 1.3.8
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Stages 1 to 11 define let IP_Indicators as the rule's main pipeline source. Stages 12 to 19 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
Stage 4: extend
| extend NetworkSourceIP = toupper(ObservableValue)
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: extend
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
TI_ipEntity =isnotempty(NetworkSourceIP)NetworkSourceIPNetworkSourceIPStage 8: extend
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
TI_ipEntity =isempty(TI_ipEntity) and isnotempty(NetworkSourceIP)NetworkSourceIPTI_ipEntityStage 9: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 10: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 11: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 12: project-reorder
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
Stage 13: join
| join kind=innerunique (
AzureDiagnostics
| where ResourceType =~ "VAULTS"
| where TimeGenerated >= ago(dt_lookBack)
| extend KeyVaultEvents_TimeGenerated = TimeGenerated, ClientIP = CallerIPAddress
)
on $left.TI_ipEntity == $right.ClientIP
Stage 14: where
where KeyVaultEvents_TimeGenerated < ValidUntil
Stage 15: summarize
summarize KeyVaultEvents_TimeGenerated by Id, ClientIP
Stage 16: extend
extend Description
Stage 17: extend
extend ActivityGroupNames
Stage 18: project
project ActivityGroupNames, ClientIP, Confidence, CorrelationId, Description, Id, KeyVaultEvents_TimeGenerated, OperationName, ResourceId, ResultType, SubscriptionId, TI_ipEntity, Type, ValidUntil, clientInfo_s, httpStatusCode_d, id_s, identity_claim_appid_g, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
Stage 19: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | in |
| field:"IndicatorType" kind:in |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"VAULTS" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ClientIP | project |
Confidence | project |
CorrelationId | project |
Description | project |
Id | project |
KeyVaultEvents_TimeGenerated | project |
OperationName | project |
ResourceId | project |
ResultType | project |
SubscriptionId | project |
TI_ipEntity | project |
Type | project |
ValidUntil | project |
clientInfo_s | project |
httpStatusCode_d | project |
id_s | project |
identity_claim_appid_g | project |
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g | project |
timestamp | extend |
UniFi Site Manager: ISP SLA Breach
#Identifies when ISP uptime falls below the SLA threshold. Useful for tracking SLA compliance and supporting ISP accountability conversations.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: fecd4ab1-b24e-8413-9164-e3621c8d7caa
name: "UniFi Site Manager: ISP SLA Breach"
description: |
Identifies when ISP uptime falls below the SLA threshold. Useful for tracking SLA compliance and supporting ISP accountability conversations.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_ISPMetrics_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1499
subTechniques:
- T1499.002
query: |
// UniFi ISP SLA Breach Detection
let SLAThreshold = 99.9;
Unifi_SiteManager_ISPMetrics_CL
| where TimeGenerated > ago(1h)
| mv-expand period = Periods
| extend
metricTime = todatetime(period.metricTime),
uptime = todouble(period.data.wan.uptime),
downtime = toint(period.data.wan.downtime),
ispName = tostring(period.data.wan.ispName),
ispAsn = tostring(period.data.wan.ispAsn),
siteIdStr = tostring(SiteId)
// De-duplicate Periods: each poll returns the same hour buckets, so collapse to latest value per metricTime
| summarize arg_max(TimeGenerated, uptime, downtime, ispAsn) by siteIdStr, ispName, metricTime
| where metricTime > ago(1h)
| summarize
AvgUptime = round(avg(uptime), 3),
MinUptime = round(min(uptime), 3),
TotalDowntimeSeconds = sum(downtime),
MeasurementCount = count()
by SiteId = siteIdStr, ispName, ispAsn
| where AvgUptime < SLAThreshold
| extend
TimeGenerated = now(),
SLATarget = SLAThreshold,
UptimeGap = round(SLAThreshold - AvgUptime, 3)
| project
TimeGenerated,
SiteId = SiteId,
ISPName = ispName,
ISPAsn = ispAsn,
AvgUptimePct = AvgUptime,
MinUptimePct = MinUptime,
SLATargetPct = SLATarget,
UptimeGapPct = UptimeGap,
TotalDowntimeSeconds,
MeasurementCount
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteId
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ISPName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Parameters
let SLAThreshold = 99.9;
Stage 1: source
Unifi_SiteManager_ISPMetrics_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: mv-expand
| mv-expand period = Periods
Stage 4: extend
| extend
metricTime = todatetime(period.metricTime),
uptime = todouble(period.data.wan.uptime),
downtime = toint(period.data.wan.downtime),
ispName = tostring(period.data.wan.ispName),
ispAsn = tostring(period.data.wan.ispAsn),
siteIdStr = tostring(SiteId)
Stage 5: summarize
| summarize arg_max(TimeGenerated, uptime, downtime, ispAsn) by siteIdStr, ispName, metricTime
Stage 6: where
| where metricTime > ago(1h)
Stage 7: summarize
| summarize
AvgUptime = round(avg(uptime), 3),
MinUptime = round(min(uptime), 3),
TotalDowntimeSeconds = sum(downtime),
MeasurementCount = count()
by SiteId = siteIdStr, ispName, ispAsn
Stage 8: where
| where AvgUptime < SLAThreshold
Stage 9: extend
| extend
TimeGenerated = now(),
SLATarget = SLAThreshold,
UptimeGap = round(SLAThreshold - AvgUptime, 3)
Stage 10: project
| project
TimeGenerated,
SiteId = SiteId,
ISPName = ispName,
ISPAsn = ispAsn,
AvgUptimePct = AvgUptime,
MinUptimePct = MinUptime,
SLATargetPct = SLATarget,
UptimeGapPct = UptimeGap,
TotalDowntimeSeconds,
MeasurementCount
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AvgUptime | lt |
| field:"AvgUptime" kind:lt value:"99.9" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AvgUptimePct | project |
ISPAsn | project |
ISPName | project |
MeasurementCount | project |
MinUptimePct | project |
SLATargetPct | project |
SiteId | project |
TimeGenerated | project |
TotalDowntimeSeconds | project |
UptimeGapPct | project |
Vectra Account's Behaviors
#'This analytic rule is looking for new attacker behaviors observed by the Vectra Platform. This rule is focused on account's detections.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: ce54b5d3-4c31-4eaf-a73e-31412270b6ab
name: Vectra Account's Behaviors
description: |
'This analytic rule is looking for new attacker behaviors observed by the Vectra Platform. This rule is focused on account's detections.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Discovery
- LateralMovement
- Collection
- CommandAndControl
- Exfiltration
- Impact
relevantTechniques:
- T1003
- T1087
- T1021
- T1119
- T1071
- T1041
- T1499
query: |
CommonSecurityLog
| where DeviceVendor == "Vectra Networks"
| where DeviceProduct == "X Series"
| where DeviceEventClassID != "campaigns"
and DeviceEventClassID != "hsc"
and DeviceEventClassID != "audit"
and DeviceEventClassID != "health"
and DeviceEventClassID != "asc"
| extend Category = coalesce(
column_ifexists("DeviceEventCategory", ""),
extract("cat=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
| project-rename threat_score = FlexNumber1
| project-rename certainty_score = FlexNumber2
| project-rename vectra_URL = DeviceCustomString4
| project-rename detection_name = DeviceEventClassID
| project-rename triaged = DeviceCustomString5
| where triaged != "True" and AdditionalExtensions contains "account"
| extend account = extract("account=(.+?);", 1, AdditionalExtensions)
| extend upn = iff(account matches regex ":", tostring(split(account, ":")[1]), tostring(split(account, ":")[0]))
| extend name = tostring(split(upn, "@")[0])
| extend upn_suffix = tostring(split(upn, "@")[1])
| extend source_entity = case(isnotempty(upn), upn,
isnotempty(SourceHostName), SourceHostName,
"UNKNWON")
| extend level = case(threat_score == 0 and certainty_score == 0, "Info",
threat_score < 50 and certainty_score < 50, "Low",
threat_score < 50 and certainty_score >= 50, "Medium",
threat_score >= 50 and certainty_score <= 50, "High",
threat_score >= 50 and certainty_score >= 50, "Critical",
"UNKNOWN")
| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
| summarize arg_max(threat_score, *) by source_entity, Activity
| sort by TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: name
- identifier: UPNSuffix
columnName: upn_suffix
alertDetailsOverride:
alertDisplayNameFormat: Vectra AI - {{Activity}} Detected
alertDescriptionFormat: |
Entity is an account. Category is {{Category}}. Threat score is {{threat_score}} and certainty score is {{certainty_score}}.
alertSeverityColumnName: Severity
alertDynamicProperties:
- alertProperty: AlertLink
value: vectra_URL
- alertProperty: ProductName
value: DeviceProduct
- alertProperty: ProviderName
value: DeviceVendor
- alertProperty: ConfidenceScore
value: certainty_score
customDetails:
AttackType: Activity
AttackCategory: Category
version: 1.0.5
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor == "Vectra Networks"
Stage 3: where
| where DeviceProduct == "X Series"
Stage 4: where
| where DeviceEventClassID != "campaigns"
and DeviceEventClassID != "hsc"
and DeviceEventClassID != "audit"
and DeviceEventClassID != "health"
and DeviceEventClassID != "asc"
Stage 5: extend
| extend Category = coalesce(
column_ifexists("DeviceEventCategory", ""),
extract("cat=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
Stage 6: project-rename
| project-rename threat_score = FlexNumber1
Stage 7: project-rename
| project-rename certainty_score = FlexNumber2
Stage 8: project-rename
| project-rename vectra_URL = DeviceCustomString4
Stage 9: project-rename
| project-rename detection_name = DeviceEventClassID
Stage 10: project-rename
| project-rename triaged = DeviceCustomString5
Stage 11: where
| where triaged != "True" and AdditionalExtensions contains "account"
Stage 12: extend (7 consecutive steps)
| extend account = extract("account=(.+?);", 1, AdditionalExtensions)
| extend upn = iff(account matches regex ":", tostring(split(account, ":")[1]), tostring(split(account, ":")[0]))
| extend name = tostring(split(upn, "@")[0])
| extend upn_suffix = tostring(split(upn, "@")[1])
| extend source_entity = case(isnotempty(upn), upn,
isnotempty(SourceHostName), SourceHostName,
"UNKNWON")
| extend level = case(threat_score == 0 and certainty_score == 0, "Info",
threat_score < 50 and certainty_score < 50, "Low",
threat_score < 50 and certainty_score >= 50, "Medium",
threat_score >= 50 and certainty_score <= 50, "High",
threat_score >= 50 and certainty_score >= 50, "Critical",
"UNKNOWN")
| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
Stage 13: summarize
| summarize arg_max(threat_score, *) by source_entity, Activity
Stage 14: sort
| sort by TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalExtensions | contains |
| field:"AdditionalExtensions" kind:contains value:"account" |
DeviceEventClassID | ne |
| field:"DeviceEventClassID" kind:ne |
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"X Series" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Vectra Networks" |
triaged | ne |
| field:"DeviceCustomString5" kind:ne value:"True" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | summarize |
source_entity | summarize |
Virtual Lab Deleted
#Detects when a virtual lab is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 384e92a7-103c-4a47-945d-381ae9653f91
name: Virtual Lab Deleted
description: Detects when a virtual lab is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
severity: Low
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 == 30800\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 30800
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"30800" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |