Detection rules › Kusto
Kusto rules: 0h
Account added and removed from privileged groups
#Identifies accounts that are added to a privileged group and then quickly removed, which could be a sign of compromise.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: 7efc75ce-e2a4-400f-a8b1-283d3b0f2c60
name: Account added and removed from privileged groups
description: |
'Identifies accounts that are added to a privileged group and then quickly removed, which could be a sign of compromise.'
severity: Low
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
query: |
let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";
let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";
let AC_Add =
(union isfuzzy=true
(SecurityEvent
// Event ID related to member addition.
| where EventID in (4728, 4732,4756)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData with * '"MemberName">' * '=' AccountAdded ",OU" *
| where isnotempty(AccountAdded)
| extend GroupAddedTo = TargetUserName, AddingAccount = Account
| extend AccountAdded_GroupAddedTo_AddingAccount = strcat(AccountAdded, "||", GroupAddedTo, "||", AddingAccount )
| project AccountAdded_GroupAddedTo_AddingAccount, AccountAddedTime = TimeGenerated
),
(WindowsEvent
// Event ID related to member addition.
| where EventID in (4728, 4732,4756)
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData.MemberName with * '"MemberName">' * '=' AccountAdded ",OU" *
| where isnotempty(AccountAdded)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend AddingAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend GroupAddedTo = TargetUserName
| extend AccountAdded_GroupAddedTo_AddingAccount = strcat(AccountAdded, "||", GroupAddedTo, "||", AddingAccount )
| project AccountAdded_GroupAddedTo_AddingAccount, AccountAddedTime = TimeGenerated
)
);
let AC_Remove =
( union isfuzzy=true
(SecurityEvent
// Event IDs related to member removal.
| where EventID in (4729,4733,4757)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData with * '"MemberName">' * '=' AccountRemoved ",OU" *
| where isnotempty(AccountRemoved)
| extend GroupRemovedFrom = TargetUserName, RemovingAccount = Account
| extend AccountRemoved_GroupRemovedFrom_RemovingAccount = strcat(AccountRemoved, "||", GroupRemovedFrom, "||", RemovingAccount)
| project AccountRemoved_GroupRemovedFrom_RemovingAccount, AccountRemovedTime = TimeGenerated, Computer, AccountRemoved = tolower(AccountRemoved),
RemovingAccount, RemovingAccountLogonId = SubjectLogonId, GroupRemovedFrom = TargetUserName, TargetDomainName
),
(WindowsEvent
// Event IDs related to member removal.
| where EventID in (4729,4733,4757)
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData.MemberName with * '"MemberName">' * '=' AccountRemoved ",OU" *
| where isnotempty(AccountRemoved)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend RemovingAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend GroupRemovedFrom = TargetUserName
| extend AccountRemoved_GroupRemovedFrom_RemovingAccount = strcat(AccountRemoved, "||", GroupRemovedFrom, "||", RemovingAccount)
| extend RemovedAccountLogonId= tostring(EventData.SubjectLogonId)
| extend TargetDomainName = tostring(EventData.TargetDomainName)
| project AccountRemoved_GroupRemovedFrom_RemovingAccount, AccountRemovedTime = TimeGenerated, Computer, AccountRemoved = tolower(AccountRemoved),
RemovingAccount, RemovedAccountLogonId, GroupRemovedFrom = TargetUserName, TargetDomainName
));
AC_Add
| join kind = inner AC_Remove
on $left.AccountAdded_GroupAddedTo_AddingAccount == $right.AccountRemoved_GroupRemovedFrom_RemovingAccount
| extend DurationinSecondAfter_Removed = datetime_diff ('second', AccountRemovedTime, AccountAddedTime)
| where DurationinSecondAfter_Removed > 0
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend RemovedAccountName = tostring(split(AccountRemoved, @"\")[1]), RemovedAccountNTDomain = tostring(split(AccountRemoved, @"\")[0])
| extend RemovingAccountName = tostring(split(RemovingAccount, @"\")[1]), RemovingAccountNTDomain = tostring(split(RemovingAccount, @"\")[0])
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountRemoved
- identifier: Name
columnName: RemovedAccountName
- identifier: NTDomain
columnName: RemovedAccountNTDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: RemovingAccount
- identifier: Name
columnName: RemovingAccountName
- identifier: NTDomain
columnName: RemovingAccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.1.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";
Let Binding: WellKnownGroupSID
let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";
Let Binding: AC_Remove
let AC_Remove = ( union isfuzzy=true
(SecurityEvent
| where EventID in (4729,4733,4757)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData with * '"MemberName">' * '=' AccountRemoved ",OU" *
| where isnotempty(AccountRemoved)
| extend GroupRemovedFrom = TargetUserName, RemovingAccount = Account
| extend AccountRemoved_GroupRemovedFrom_RemovingAccount = strcat(AccountRemoved, "||", GroupRemovedFrom, "||", RemovingAccount)
| project AccountRemoved_GroupRemovedFrom_RemovingAccount, AccountRemovedTime = TimeGenerated, Computer, AccountRemoved = tolower(AccountRemoved),
RemovingAccount, RemovingAccountLogonId = SubjectLogonId, GroupRemovedFrom = TargetUserName, TargetDomainName
),
(WindowsEvent
| where EventID in (4729,4733,4757)
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData.MemberName with * '"MemberName">' * '=' AccountRemoved ",OU" *
| where isnotempty(AccountRemoved)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend RemovingAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend GroupRemovedFrom = TargetUserName
| extend AccountRemoved_GroupRemovedFrom_RemovingAccount = strcat(AccountRemoved, "||", GroupRemovedFrom, "||", RemovingAccount)
| extend RemovedAccountLogonId= tostring(EventData.SubjectLogonId)
| extend TargetDomainName = tostring(EventData.TargetDomainName)
| project AccountRemoved_GroupRemovedFrom_RemovingAccount, AccountRemovedTime = TimeGenerated, Computer, AccountRemoved = tolower(AccountRemoved),
RemovingAccount, RemovedAccountLogonId, GroupRemovedFrom = TargetUserName, TargetDomainName
));
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where EventID in (4728, 4732,4756)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData with * '"MemberName">' * '=' AccountAdded ",OU" *
| where isnotempty(AccountAdded)
| extend GroupAddedTo = TargetUserName, AddingAccount = Account
| extend AccountAdded_GroupAddedTo_AddingAccount = strcat(AccountAdded, "||", GroupAddedTo, "||", AddingAccount )
| project AccountAdded_GroupAddedTo_AddingAccount, AccountAddedTime = TimeGenerated
Leg 2: WindowsEvent
WindowsEvent
| where EventID in (4728, 4732,4756)
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| parse EventData.MemberName with * '"MemberName">' * '=' AccountAdded ",OU" *
| where isnotempty(AccountAdded)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend AddingAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend GroupAddedTo = TargetUserName
| extend AccountAdded_GroupAddedTo_AddingAccount = strcat(AccountAdded, "||", GroupAddedTo, "||", AddingAccount )
| project AccountAdded_GroupAddedTo_AddingAccount, AccountAddedTime = TimeGenerated
Applied to the Combined Result
| join kind = inner AC_Remove
on $left.AccountAdded_GroupAddedTo_AddingAccount == $right.AccountRemoved_GroupRemovedFrom_RemovingAccount | extend DurationinSecondAfter_Removed = datetime_diff ('second', AccountRemovedTime, AccountAddedTime) | where DurationinSecondAfter_Removed > 0 | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.')) | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer) | extend RemovedAccountName = tostring(split(AccountRemoved, @"\")[1]), RemovedAccountNTDomain = tostring(split(AccountRemoved, @"\")[0]) | extend RemovingAccountName = tostring(split(RemovingAccount, @"\")[1]), RemovingAccountNTDomain = tostring(split(RemovingAccount, @"\")[0]) | project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountAdded | is_not_null | field:"AccountAdded" kind:is_not_null | |
AccountRemoved | is_not_null | field:"AccountRemoved" kind:is_not_null | |
DurationinSecondAfter_Removed | gt |
| field:"DurationinSecondAfter_Removed" kind:gt value:"0" |
EventID | in |
| field:"EventID" kind:in |
TargetSid | regex_match |
| field:"TargetSid" kind:regex_match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountAddedTime | project |
AccountAdded_GroupAddedTo_AddingAccount | project |
DurationinSecondAfter_Removed | extend |
HostName | extend |
HostNameDomain | extend |
RemovedAccountNTDomain | extend |
RemovedAccountName | extend |
RemovingAccountNTDomain | extend |
RemovingAccountName | extend |
Alsid user accounts issues
#'Searches for triggered Indicators of Exposures related to user accounts issues'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: fb9e0b51-8867-48d7-86f4-6e76f2176bf8
name: Alsid user accounts issues
description: |
'Searches for triggered Indicators of Exposures related to user accounts issues'
severity: Low
requiredDataConnectors:
- connectorId: AlsidForAD
dataTypes:
- AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-ACCOUNTS-DANG-SID-HISTORY", "C-PRE-WIN2000-ACCESS-MEMBERS", "C-PASSWORD-DONT-EXPIRE", "C-SLEEPING-ACCOUNTS", "C-DANG-PRIMGROUPID", "C-PASSWORD-NOT-REQUIRED", "C-USER-PASSWORD"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-ACCOUNTS-DANG-SID-HISTORY", "C-PRE-WIN2000-ACCESS-MEMBERS", "C-PASSWORD-DONT-EXPIRE", "C-SLEEPING-ACCOUNTS", "C-DANG-PRIMGROUPID", "C-PASSWORD-NOT-REQUIRED", "C-USER-PASSWORD"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
AWSCloudTrail - RDS instance publicly exposed
#Detects RDS instance creation or modification events where the database is configured as publicly accessible. Public exposure of database instances can increase the risk of unauthorized access and data leakage.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 8f1630c2-2e45-4df2-be43-50fba90f601d
name: AWSCloudTrail - RDS instance publicly exposed
description: |
Detects RDS instance creation or modification events where the database is configured as publicly accessible.
Public exposure of database instances can increase the risk of unauthorized access and data leakage.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1537
query: |
AWSCloudTrail
| where EventName in ("CreateDBInstance", "ModifyDBInstance") and isempty(ErrorCode) and isempty(ErrorMessage)
| where tostring(parse_json(RequestParameters).publiclyAccessible) == "true"
| 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:
EventName: EventName
AWSRegion: AWSRegion
UserIdentityArn: UserIdentityArn
RecipientAccountId: RecipientAccountId
alertDetailsOverride:
alertDisplayNameFormat: 'AWS RDS instance publicly exposed by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} event from {{SourceIpAddress}} setting RDS publicly accessible in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in ("CreateDBInstance", "ModifyDBInstance") and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: where
| where tostring(parse_json(RequestParameters).publiclyAccessible) == "true"
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]), "")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
RequestParameters.publiclyAccessible | eq |
| field:"RequestParameters.publiclyAccessible" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Azure secure score PW age policy new
#'This query searches for having found that when periodic password resets are enforced, passwords become less secure. Users tend to pick a weaker password and vary it slightly for each reset. If a user creates a strong password (long, complex and without any pragmatic words present) it should remain just as strong in the future as it is today. It is Microsoft's official security position to not expire passwords periodically without a specific reason, and recommends that cloud-only tenants set the password policy to never expire.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 88C9A5E0-31EC-490B-82E5-A286D9B99A67
name: Azure secure score PW age policy new
description: |
'This query searches for having found that when periodic password resets are enforced, passwords become less secure. Users tend to pick a weaker password and vary it slightly for each reset.
If a user creates a strong password (long, complex and without any pragmatic words present) it should remain just as strong in the future as it is today. It is Microsoft's official security position to not expire passwords periodically without a specific reason, and recommends that cloud-only tenants set the password policy to never expire.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1555
- T1606
- T1040
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScorePWAgePolicyNew'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScorePWAgePolicyNew'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScorePWAgePolicyNew" |
Beacon Traffic Based on Common User Agents Visiting Limited Number of Domains
#This query searches web proxy logs for a specific type of beaconing behavior by joining a number of sources together: - Traffic by actual web browsers - by looking at traffic generated by a UserAgent that looks like a browser and is used by multiple users to visit a large number of domains. - Users that make requests using one of these actual browsers, but only to a small set of domains, none of which are common domains. - The traffic is beacon-like; meaning that it occurs during many different hours of the day (i.e. periodic).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 6345c923-99eb-4a83-b11d-7af0ffa75577
name: Beacon Traffic Based on Common User Agents Visiting Limited Number of Domains
description: |
This query searches web proxy logs for a specific type of beaconing behavior by joining a number of sources together:
- Traffic by actual web browsers - by looking at traffic generated by a UserAgent that looks like a browser and is used by multiple users to visit a large number of domains.
- Users that make requests using one of these actual browsers, but only to a small set of domains, none of which are common domains.
- The traffic is beacon-like; meaning that it occurs during many different hours of the day (i.e. periodic).
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071.001
query: |
let timeframe = 1d; // Timeframe during which to search for beaconing behavior.
let lookback = 7d; // Look back period to find if browser was used for other domains by user.
let min_requests=50; // Minimum number of requests to consider it beacon traffic.
let min_hours=8; // Minimum number of different hours during which connections were made to consider it beacon traffic.
let trusted_user_count=10; // If visited by this many users a domain is considered 'trusted'.
let max_sites=3; // Maximum number of different sites visited using this user-agent.
// Client-specific query to obtain 'browser-like' traffic from proxy logs.
let BrowserTraffic = (p:timespan) {
CommonSecurityLog
| where DeviceVendor == "Zscaler" and DeviceProduct == "NSSWeblog"
| where TimeGenerated >ago(p)
| project TimeGenerated, SourceUserName, DestinationHostName, RequestClientApplication
| where (RequestClientApplication startswith "Mozilla/" and RequestClientApplication contains "Gecko")
};
let CommonDomains = BrowserTraffic(timeframe)
| summarize source_count=dcount(SourceUserName) by DestinationHostName
| where source_count>trusted_user_count
| project DestinationHostName;
let CommonUA = BrowserTraffic(timeframe)
| summarize source_count=dcount(SourceUserName), host_count=dcount(DestinationHostName) by RequestClientApplication
| where source_count>trusted_user_count and host_count > 100 // Normal browsers are browsers used by many people and visiting many different sites.
| project RequestClientApplication;
// Find browsers that are common, i.e. many users use them and they use them to visit many different sites,
// but some users only use the browser to visit a very limited set of sites.
// These are considered suspicious, since they might be an attacker masquerading a beacon as a legitimate browser.
let SuspiciousBrowers = BrowserTraffic(timeframe)
| where RequestClientApplication in(CommonUA)
| summarize BrowserHosts=make_set(DestinationHostName),request_count=count() by RequestClientApplication, SourceUserName
| where array_length(BrowserHosts) <= max_sites and request_count >= min_requests
| project RequestClientApplication, SourceUserName,BrowserHosts;
// Just reporting on suspicious browsers gives too many false positives.
// For example, users that have the browser open on the login screen of 1 specific application.
// In the suspicious browsers we can search for 'beacon-like' behavior.
// Get all browser traffic by the suspicious browsers.
let PotentialAlerts=SuspiciousBrowers
| join BrowserTraffic(timeframe) on RequestClientApplication, SourceUserName
// Find beaconing-like traffic - i.e. contacting the same host in many different hours.
| summarize hour_count=dcount(bin(TimeGenerated,1h)), BrowserHosts=any(BrowserHosts), request_count=count() by RequestClientApplication, SourceUserName, DestinationHostName
| where hour_count >= min_hours and request_count >= min_requests
// Remove common domains like login.microsoft.com.
| join kind=leftanti CommonDomains on DestinationHostName
| summarize RareHosts=make_set(DestinationHostName), TotalRequestCount=sum(request_count), BrowserHosts=any(BrowserHosts) by RequestClientApplication, SourceUserName
// Remove browsers that visit any common domains.
| where array_length(RareHosts) == array_length(BrowserHosts);
// Look back for X days to see if the browser was not used to visit more hosts.
// This is to get rid of someone that started up the browser a long time ago, and left only a single tab open.
PotentialAlerts
| join BrowserTraffic(lookback) on SourceUserName, RequestClientApplication
| summarize RareHosts=any(RareHosts),BrowserHosts1d=any(BrowserHosts),BrowserHostsLookback=make_set(DestinationHostName) by SourceUserName, RequestClientApplication
| where array_length(RareHosts) == array_length(BrowserHostsLookback)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SourceUserName
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
let lookback = 7d;
let min_requests = 50;
let min_hours = 8;
let trusted_user_count = 10;
let max_sites = 3;
let BrowserTraffic, let SuspiciousBrowers and let PotentialAlerts are inlined into the numbered stages below.
Let Binding: CommonDomains
let CommonDomains = BrowserTraffic(timeframe)
| summarize source_count=dcount(SourceUserName) by DestinationHostName
| where source_count>trusted_user_count
| project DestinationHostName;
Let Binding: CommonUA
let CommonUA = BrowserTraffic(timeframe)
| summarize source_count=dcount(SourceUserName), host_count=dcount(DestinationHostName) by RequestClientApplication
| where source_count>trusted_user_count and host_count > 100
| project RequestClientApplication;
Stages 1 to 15 define let PotentialAlerts as the rule's main pipeline source. Stages 16 to 18 use it.
Stage 1: source
CommonSecurityLog
Stage 2: where
where DeviceProduct == "NSSWeblog" and DeviceVendor == "Zscaler"
Stage 3: where
where ...
Stage 4: project
project DestinationHostName, RequestClientApplication, SourceUserName, TimeGenerated
Stage 5: where
where RequestClientApplication contains "Gecko" and RequestClientApplication startswith "Mozilla/"
Stage 6: where
where RequestClientApplication == "CommonUA"
Stage 7: summarize
summarize BrowserHosts, request_count by RequestClientApplication, SourceUserName
Stage 8: where
where BrowserHosts <= 3 and request_count >= 50
Stage 9: project
project BrowserHosts, RequestClientApplication, SourceUserName
Stage 10: join
| join BrowserTraffic(timeframe) on RequestClientApplication, SourceUserName
Stage 11: summarize
summarize BrowserHosts, hour_count, request_count by RequestClientApplication, SourceUserName, DestinationHostName
Stage 12: where
where hour_count >= 8 and request_count >= 50
Stage 13: join (negated)
join kind=leftanti (CommonDomains) on DestinationHostName
Stage 14: summarize
summarize BrowserHosts, RareHosts, TotalRequestCount by RequestClientApplication, SourceUserName
Stage 15: where
where <unknown:array_length(, RareHosts)> =~ <unknown:array_length(, BrowserHosts)>
Stage 16: join
PotentialAlerts
| join BrowserTraffic(lookback) on SourceUserName, RequestClientApplication
Stage 17: summarize
summarize BrowserHosts1d, BrowserHostsLookback, RareHosts by SourceUserName, RequestClientApplication
Stage 18: where
where <unknown:array_length(, RareHosts)> =~ <unknown:array_length(, BrowserHostsLookback)>
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DeviceProduct | eq | NSSWeblog | excludes:DeviceProduct field:"DeviceProduct" value:"NSSWeblog" |
DeviceVendor | eq | Zscaler | excludes:DeviceVendor field:"DeviceVendor" value:"Zscaler" |
RequestClientApplication | contains | Gecko | excludes:RequestClientApplication field:"RequestClientApplication" value:"Gecko" |
RequestClientApplication | starts_with | Mozilla/ | excludes:RequestClientApplication field:"RequestClientApplication" value:"Mozilla/" |
source_count | gt | 10 | excludes:source_count field:"source_count" value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
BrowserHosts1d | summarize |
BrowserHostsLookback | summarize |
RareHosts | summarize |
RequestClientApplication | summarize |
SourceUserName | summarize |
BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC3 Privileges
#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.
BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC3 Privileges
Rule Body
id: e6c6fb5d-c2a4-4ff4-8c75-a66f37e1ecc0
name: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC3 Privileges
description: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC3
Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0ADCSESC3"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0ADCSESC3"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0ADCSESC3" |
Check Point Exposure Management - Alert Ingestion Anomaly
#Detects when no Check Point Exposure Management alerts have been ingested into the argsentdc_CL table for an extended period. This may indicate a failure in the CCP data connector or the Importer playbook, a misconfigured API token, or network connectivity issues.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 8d3b9c7e-5a2f-4e1d-b6c8-3f9a7e2d1b4c
name: Check Point Exposure Management - Alert Ingestion Anomaly
description: |
Detects when no Check Point Exposure Management alerts have been ingested into the argsentdc_CL table
for an extended period. This may indicate a failure in the CCP data connector or the
Importer playbook, a misconfigured API token, or network connectivity issues.
severity: Medium
queryFrequency: 1h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
requiredDataConnectors:
- connectorId: CheckPointCyberintAlerts
dataTypes:
- argsentdc_CL
query: |
let lookback = 24h;
let expected_interval = 1h;
let last_record = toscalar(
argsentdc_CL
| where TimeGenerated > ago(lookback)
| summarize max(TimeGenerated)
);
let gap_minutes = datetime_diff('minute', now(), last_record);
// Alert if no records in the last expected_interval, but there WERE records in the lookback period
// (avoids alerting on brand-new deployments with no data yet)
argsentdc_CL
| where TimeGenerated > ago(lookback)
| summarize
LastIngestionTime = max(TimeGenerated),
TotalRecords = count(),
DistinctAlerts = dcount(ref_id)
| where LastIngestionTime < ago(expected_interval)
| extend GapMinutes = datetime_diff('minute', now(), LastIngestionTime)
| project
LastIngestionTime,
GapMinutes,
TotalRecords,
DistinctAlerts,
Message = strcat("No Check Point Exposure Management alerts ingested for ", GapMinutes, " minutes. Last record at ", LastIngestionTime)
suppressionEnabled: false
suppressionDuration: 5h
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 24h
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDescriptionFormat: '{{Message}}'
alertDisplayNameFormat: 'Check Point Exposure Management - Ingestion gap detected ({{GapMinutes}} minutes)'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 24h;
let expected_interval = 1h;
let gap_minutes = datetime_diff('minute', now(), last_record);
Let Binding: last_record
let last_record = toscalar(
argsentdc_CL
| where TimeGenerated > ago(lookback)
| summarize max(TimeGenerated)
);
Stage 1: source
argsentdc_CL
Stage 2: where
| where TimeGenerated > ago(lookback)
Stage 3: summarize
| summarize
LastIngestionTime = max(TimeGenerated),
TotalRecords = count(),
DistinctAlerts = dcount(ref_id)
Stage 4: where
| where LastIngestionTime < ago(expected_interval)
Stage 5: extend
| extend GapMinutes = datetime_diff('minute', now(), LastIngestionTime)
Stage 6: project
| project
LastIngestionTime,
GapMinutes,
TotalRecords,
DistinctAlerts,
Message = strcat("No Check Point Exposure Management alerts ingested for ", GapMinutes, " minutes. Last record at ", LastIngestionTime)
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DistinctAlerts | project |
GapMinutes | project |
LastIngestionTime | project |
Message | project |
TotalRecords | project |
Cognni Incidents for Low Sensitivity HR Information
#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.
'Display incidents in which low sensitive HR information was placed at risk by user sharing.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: ef8654b1-b2cf-4f6c-ae5c-eca635a764e8
name: Cognni Incidents for Low Sensitivity HR Information
description: |
'Display incidents in which low sensitive HR information was placed at risk by user sharing.'
severity: Low
requiredDataConnectors:
- connectorId: CognniSentinelDataConnector
dataTypes:
- CognniIncidents_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Collection
relevantTechniques:
- T1530
query: |
let lowRisk = 1;
let hr = 'HR Information';
CognniIncidents_CL
| where Severity == lowRisk
| where informationType_s == hr
| where TimeGenerated >= ago(5h)
| extend AccountCustomEntity = userId_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lowRisk = 1;
let hr = 'HR Information';
Stage 1: source
CognniIncidents_CL
Stage 2: where
| where Severity == lowRisk
Stage 3: where
| where informationType_s == hr
Stage 4: where
| where TimeGenerated >= ago(5h)
Stage 5: extend
| extend AccountCustomEntity = userId_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | eq |
| field:"Severity" kind:eq value:"1" |
informationType_s | eq |
| field:"informationType_s" kind:eq value:"HR Information" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
CYFIRMA - Data Breach and Web Monitoring - Ransomware Exposure Detected Rule
#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.
"This analytics rule detects high-severity ransomware threats targeting the organization, as reported by CYFIRMA's Dark Web and Data Breach Intelligence feeds. The alert is generated when threat actors post, claim, or associate ransomware activity with corporate domains, brands, or subsidiaries, indicating a potential data breach, extortion attempt, or unauthorized access."
MITRE ATT&CK Coverage
Rule Body
id: ed1aabc1-e1c1-42f4-abac-fd5637730f13
name: CYFIRMA - Data Breach and Web Monitoring - Ransomware Exposure Detected Rule
description: |
"This analytics rule detects high-severity ransomware threats targeting the organization, as reported by CYFIRMA's Dark Web and Data Breach Intelligence feeds.
The alert is generated when threat actors post, claim, or associate ransomware activity with corporate domains, brands, or subsidiaries, indicating a potential data breach, extortion attempt, or unauthorized access."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaDBWMRansomwareAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Exfiltration
relevantTechniques:
- T1566.001
- T1566.002
- T1566.003
query: |
// High severity - Data Breach and Web Monitoring - Ransomware Exposure Detected
let timeFrame = 5m;
CyfirmaDBWMRansomwareAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation='',
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title,
ThreatActors= threat_actors
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle,
ThreatActors
customDetails:
AssetType: AssetType
AssetValue: AssetValue
ThreatActors: ThreatActors
RiskScore: RiskScore
Source: Source
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity Alert: Ransomware Exposure Detected - {{AlertTitle}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaDBWMRansomwareAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation='',
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title,
ThreatActors= threat_actors
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle,
ThreatActors
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
Source | project |
ThreatActors | project |
TimeGenerated | project |
UID | project |
CYFIRMA - Medium severity TOR Node Network Indicators - Block Recommended Rule
#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.
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'. These indicators may include IP addresses, domains, and URLs related to Tor network activity. Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Persistence | |
| Command & Control | |
| Exfiltration |
Rule Body
id: aba36dc3-af43-4ab6-9349-3d1e37f1d4f3
name: CYFIRMA - Medium severity TOR Node Network Indicators - Block Recommended Rule
description: |
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'.
These indicators may include IP addresses, domains, and URLs related to Tor network activity.
Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//TOR Node Network Indicators - Block Recommended
let timeFrame= 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'TOR'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- CommandAndControl
- Exfiltration
- InitialAccess
- Persistence
- Reconnaissance
relevantTechniques:
- T1090
- T1572
- T1048
- T1071
- T1189
- T1505
- T1595
- T1090.003
- T1048.002
- T1071.001
- T1505.003
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence TOR Node Network Indicators - Block Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'TOR'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
DDoS Attack IP Addresses - Percent Threshold
#Identifies IP addresses that generate over 5% of traffic during DDoS attack mitigation
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 402a42ad-f31c-48d1-8f80-0200846b7f25
kind: Scheduled
name: DDoS Attack IP Addresses - Percent Threshold
description: Identifies IP addresses that generate over 5% of traffic during DDoS attack mitigation
severity: Medium
status : Available
requiredDataConnectors:
- connectorId: DDOS
dataTypes:
- AzureDiagnostics
queryPeriod: 2h
queryFrequency: 2h
triggerOperator: gt
triggerThreshold: 0
query: |
let T1 = AzureDiagnostics
| where ResourceType == "PUBLICIPADDRESSES" and Category == "DDoSMitigationFlowLogs"
| summarize rows_count = count() by destPublicIpAddress_s, sourcePublicIpAddress_s;
let T2 = AzureDiagnostics
| where ResourceType == "PUBLICIPADDRESSES" and Category == "DDoSMitigationFlowLogs"
| summarize rows_total = count() by destPublicIpAddress_s;
T2
| join kind=leftouter T1 on destPublicIpAddress_s
| project destPublicIpAddress_s, sourcePublicIpAddress_s, percent_of_traffic = 100*rows_count/rows_total
| order by percent_of_traffic desc
| where percent_of_traffic > 5
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: sourcePublicIpAddress_s
tactics:
- Impact
relevantTechniques:
- T1498
version: 1.0.1
Stages and Predicates
let T2 is inlined into the numbered stages below.
Let Binding: T1
let T1 = AzureDiagnostics
| where ResourceType == "PUBLICIPADDRESSES" and Category == "DDoSMitigationFlowLogs"
| summarize rows_count = count() by destPublicIpAddress_s, sourcePublicIpAddress_s;
Stages 1 to 3 define let T2 as the rule's main pipeline source. Stages 4 to 7 use it.
Stage 1: source
AzureDiagnostics
Stage 2: where
| where ResourceType == "PUBLICIPADDRESSES" and Category == "DDoSMitigationFlowLogs"
Stage 3: summarize
| summarize rows_total = count() by destPublicIpAddress_s
Stage 4: join
T2
| join kind=leftouter T1 on destPublicIpAddress_s
Stage 5: project
project destPublicIpAddress_s, percent_of_traffic, sourcePublicIpAddress_s
Stage 6: sort
sort by percent_of_traffic
Stage 7: where
where percent_of_traffic > 5
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"DDoSMitigationFlowLogs" |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"PUBLICIPADDRESSES" |
percent_of_traffic | gt |
| field:"percent_of_traffic" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
destPublicIpAddress_s | project |
percent_of_traffic | project |
sourcePublicIpAddress_s | project |
Detect suspicious foci token logins V2
#FOCI tokens (Family of Client IDs tokens) are special refresh tokens that allow multiple applications within the same "family" to share authentication tokens. This means that once a user authenticates with one application, they can access other applications in the same family without needing to re-authenticate. For adversaries, these are very interesting tokens to abuse since they can access a normal application (Microsoft Teams for example), and reuse that refresh token to access another application (like Azure CLI). To detect a suspicious foci token combination, we look for all the logins using foci tokens and group them by Session ID (since these belong to the same session). Then we take the first login where no refresh token was provided, and look at the logins that used refresh tokens as incomming token types within that same session. If the second login application is one that is typically abused by adversaries and the application for the first login is a 'normal' application, we flag the event. This version is the V2 version for this query in this repo, which also flags when an adversary is using the same application to get new access tokens but with another scope (compared to V1 which does not do this). The v2 version focusses more on RoadTool detection tho, while the v1 detection is more broad. Some organizations have a high BP hit count on Microsoft Azure CLI. To limit those hits, you have three finetune options to enable in the query: - Only alert when first and second login has X time between each other (default 90 minutes if enabled) - Only alert on Microsoft Azure CLI when Global Administrator scope is used in token - Only alert on Microsoft Azure CLI when Global Administrator scope is used in token and request came from a non-compliant device
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Credential Access |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Target application: Sign-in naming a target application |
Rule Body
// TimeDiff threshold in minutes. Needed for some environments with a lot of BP hits on long time frames. Used in scenario where you expect adversary to quickly request new tokens after first token request.
let maxTimeDiff = 90;
// External lookup to get list of FOCI applications
let FociClientApplications = toscalar(externaldata(client_id: string)
[@"https://raw.githubusercontent.com/secureworks/family-of-client-ids-research/refs/heads/main/known-foci-clients.csv"] with (format="csv", ignoreFirstRecord=true)
//| project-rename FociClientId = client_id
| summarize FociClientId = make_list(client_id)
);
// Get all token requests for Foci clients
let FociTokenRequest = materialize (
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(6h)
// Filter for sign-ins to home tenant only
| where HomeTenantId == ResourceTenantId
// Lookup for FOCI client
| where AppId in (FociClientApplications)
);
FociTokenRequest
// First get all initial logins without refresh tokens as incomming token type
| where IncomingTokenType == "none"
// Then get logins with refresh tokens for same session
| join kind=inner (
FociTokenRequest
| where IncomingTokenType != "none"
| project-rename
SecondAppDisplayName = AppDisplayName,
SecondRequestTimeGenerated = TimeGenerated,
SecondAppId = AppId
)
on SessionId, UserPrincipalName
| extend FirstOauthScopeInfo = extract("{\"key\":\"Oauth Scope Info\",\"value\":\"\\[(.*)\\]\"}", 1, AuthenticationProcessingDetails),
SecondOauthScopeInfo = extract("{\"key\":\"Oauth Scope Info\",\"value\":\"\\[(.*)\\]\"}", 1, AuthenticationProcessingDetails1)
// Only get requests where refresh token was used after first sign-in
| extend TimeDiff = datetime_diff('minute', SecondRequestTimeGenerated, TimeGenerated)
| where TimeDiff >= 1 and TimeDiff <= maxTimeDiff
// Only project needed columns
| project
FirstRequestTimeGenerated = TimeGenerated,
FirstResult = ResultType,
FirstResultDescription = ResultDescription,
Identity,
Location,
FirstAppDisplayName = AppDisplayName,
FirstAppId = AppId,
ClientAppUsed,
DeviceDetail,
SecondDeviceDetail = DeviceDetail1,
IPAddress,
LocationDetails,
UserAgent,
SecondRequestTimeGenerated,
SecondResult = ResultType,
SecondResultDescription = ResultDescription1,
SecondAppDisplayName,
SecondAppId,
SeconIncomingTokenType = IncomingTokenType1,
SessionId,
TimeDiff,
FirstOauthScopeInfo,
SecondOauthScopeInfo,
FirstResourceIdentity = ResourceIdentity,
SecondResourceIdentity = ResourceIdentity1
// Flag logins to the following applications as second login, since these are the most popular used for RoadTools (fico apps, localhost redirect URI, and Azure AD resource identity)
| where SecondAppDisplayName in ("Microsoft Azure CLI", "Copilot App", "Microsoft Azure PowerShell", "Visual Studio - Legacy", "Microsoft Edge Enterprise New Tab Page") and SecondResourceIdentity == "00000002-0000-0000-c000-000000000000"
| where SecondResult == 0
// ENVIRONMENT SPECIFIC FINETUNING - BEGIN
// Most BP triggers are mainly on Microsoft Azure CLI, so we provide two ways of handling these BP detections (strongly depends on environment)
// OPTION 1 - Flag login to Azure CLI using 'Global Administrator' ID in token scope
//| where (SecondAppDisplayName in ("Microsoft Azure PowerShell", "Office 365 Management") or (SecondAppDisplayName == "Microsoft Azure CLI" and SecondAuthenticationProcessingDetails contains "62e90394-69f5-4237-9190-012177145e10"))
// OPTION 2 - Flag login to Azure CLI using 'Global Administrator' ID in token scope from non compliant device
//| where (SecondAppDisplayName in ("Microsoft Azure PowerShell", "Office 365 Management") or (SecondAppDisplayName == "Microsoft Azure CLI" and SecondAuthenticationProcessingDetails contains "62e90394-69f5-4237-9190-012177145e10" and todynamic(SecondDeviceDetail).isCompliant != "true"))
// ENVIRONMENT SPECIFIC FINETUNING - END
Stages and Predicates
Parameters
let maxTimeDiff = 90;
let FociTokenRequest is inlined into the numbered stages below.
Let Binding: FociClientApplications
let FociClientApplications = toscalar(externaldata(client_id: string)
[@"https://raw.githubusercontent.com/secureworks/family-of-client-ids-research/refs/heads/main/known-foci-clients.csv"] with (format="csv", ignoreFirstRecord=true)
| summarize FociClientId = make_list(client_id)
);
Stages 1 to 4 define let FociTokenRequest as the rule's main pipeline source. Stages 5 to 12 use it.
Stage 1: source
AADNonInteractiveUserSignInLogs
Stage 2: where
| where TimeGenerated > ago(6h)
Stage 3: where
| where HomeTenantId == ResourceTenantId
Stage 4: where
| where AppId in (FociClientApplications)
Stage 5: where
FociTokenRequest
| where IncomingTokenType == "none"
Stage 6: join
| join kind=inner (
FociTokenRequest
| where IncomingTokenType != "none"
| project-rename
SecondAppDisplayName = AppDisplayName,
SecondRequestTimeGenerated = TimeGenerated,
SecondAppId = AppId
)
on SessionId, UserPrincipalName
Stage 7: extend
extend FirstOauthScopeInfo, SecondOauthScopeInfo
Stage 8: extend
extend TimeDiff
Stage 9: where
where TimeDiff >= 1 and TimeDiff <= 90
Stage 10: project
project ClientAppUsed, DeviceDetail, FirstAppDisplayName, FirstAppId, FirstOauthScopeInfo, FirstRequestTimeGenerated, FirstResourceIdentity, FirstResult, FirstResultDescription, IPAddress, Identity, Location, LocationDetails, SeconIncomingTokenType, SecondAppDisplayName, SecondAppId, SecondDeviceDetail, SecondOauthScopeInfo, SecondRequestTimeGenerated, SecondResourceIdentity, SecondResult, SecondResultDescription, SessionId, TimeDiff, UserAgent
Stage 11: where
where SecondAppDisplayName in ("Copilot App", "Microsoft Azure CLI", "Microsoft Azure PowerShell", "Microsoft Edge Enterprise New Tab Page", "Visual Studio - Legacy") and SecondResourceIdentity == "00000002-0000-0000-c000-000000000000"
Stage 12: where
where SecondResult == 0
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppId | in |
| field:"AppId" kind:in value:"FociClientApplications" |
IncomingTokenType | eq |
| field:"IncomingTokenType" kind:eq value:"none" |
IncomingTokenType | ne |
| field:"IncomingTokenType" kind:ne value:"none" |
SecondAppDisplayName | in |
| field:"AppDisplayName" kind:in |
SecondResourceIdentity | eq |
| field:"SecondResourceIdentity" kind:eq value:"00000002-0000-0000-c000-000000000000" |
SecondResult | eq |
| field:"SecondResult" kind:eq value:"0" |
TimeDiff | ge |
| field:"TimeDiff" kind:ge value:"1" |
TimeDiff | le |
| field:"TimeDiff" kind:le value:"90" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientAppUsed | project |
DeviceDetail | project |
FirstAppDisplayName | project |
FirstAppId | project |
FirstOauthScopeInfo | project |
FirstRequestTimeGenerated | project |
FirstResourceIdentity | project |
FirstResult | project |
FirstResultDescription | project |
IPAddress | project |
Identity | project |
Location | project |
LocationDetails | project |
SeconIncomingTokenType | project |
SecondAppDisplayName | project |
SecondAppId | project |
SecondDeviceDetail | project |
SecondOauthScopeInfo | project |
SecondRequestTimeGenerated | project |
SecondResourceIdentity | project |
SecondResult | project |
SecondResultDescription | project |
SessionId | project |
TimeDiff | project |
UserAgent | project |
Detect Windows Allow Firewall Rule Addition/Modification
#This analytic rule detects any registry value creation or modification of Windows firewall registry keys to allow network traffic. This could be an indication of defense evasion by an adversary to allow network traffic to/from a compromised host.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
Rule Body
id: 056593d4-ca3b-47a7-be9d-d1d0884a1d36
name: Detect Windows Allow Firewall Rule Addition/Modification
description: |
This analytic rule detects any registry value creation or modification of Windows firewall registry keys to allow network traffic. This could be an indication of defense evasion by an adversary to allow network traffic to/from a compromised host.
severity: Medium
status: Available
tags:
- Schema: _ASim_RegistryEvent
SchemaVersion: 0.1.2
requiredDataConnectors:
- connectorId: CrowdStrikeFalconEndpointProtection
dataTypes:
- CommonSecurityLog
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- connectorId: SentinelOne
dataTypes:
- SentinelOne_CL
- connectorId: VMwareCarbonBlack
dataTypes:
- CarbonBlackEvents_CL
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint_CL
- connectorId: TrendMicroApexOne
dataTypes:
- TMApexOneEvent
- connectorId: TrendMicroApexOneAma
dataTypes:
- TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
// List of Windows Firewall registry keys to monitor
let firewallRegistryList = dynamic([
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\RestrictedServices\\Static\\System',
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\RestrictedServices\\Configurable\\System',
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Defaults\\FirewallPolicy\\FirewallRules',
'HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall'
]);
_ASim_RegistryEvent
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
| where RegistryKey has_any (firewallRegistryList) and RegistryValueData has_all ('Action=Allow', 'Active=TRUE')
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- identifier: NTDomain
columnName: NTDomain
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Username
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: NTDomain
columnName: NTDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ActingProcessId
- identifier: CommandLine
columnName: ActingProcessCommandLine
- entityType: RegistryKey
fieldMappings:
- identifier: Hive
columnName: RegHive
- identifier: Key
columnName: RegKey
- entityType: RegistryValue
fieldMappings:
- identifier: Name
columnName: RegistryValue
- identifier: Value
columnName: RegistryValueData
- identifier: ValueType
columnName: RegistryValueType
eventGroupingSettings:
aggregationKind: singleAlert
alertDetailsOverride:
alertDisplayNameFormat: "Allow Firewall Rule {{RegistryValueData}} added at registry key {{RegKey}} on {{HostName}}"
alertDescriptionFormat: "An allow Firewall Rule {{RegistryValueData}} added at registry key {{RegKey}} by {{Username}}."
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: firewallRegistryList
let firewallRegistryList = dynamic([
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\RestrictedServices\\Static\\System',
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\RestrictedServices\\Configurable\\System',
'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Defaults\\FirewallPolicy\\FirewallRules',
'HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall'
]);
Stage 1: source
_ASim_RegistryEvent
Stage 2: where
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
Stage 3: where
| where RegistryKey has_any (firewallRegistryList) and RegistryValueData has_all ('Action=Allow', 'Active=TRUE')
Stage 4: project
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
Stage 5: extend (7 consecutive steps)
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
RegistryKey | match |
| field:"TargetObject" kind:match |
RegistryValueData | match |
| field:"Details" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessCommandLine | project |
ActingProcessId | project |
ActingProcessName | project |
ActorUsername | project |
ActorUsernameType | project |
DvcHostname | project |
RegistryKey | project |
RegistryValue | project |
RegistryValueData | project |
RegistryValueType | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Username | extend |
NTDomain | extend |
UPNSuffix | extend |
RegHive | extend |
RegKey | extend |
Detection Opportunities for Certighost (CVE-2026-54121)
#Telemetry Coverage
Rule Body
DeviceNetworkEvents
| where ActionType == "ListeningConnectionCreated"
| where LocalPort == 389
| where DeviceName !in~ ("list of domain controllers")
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://academy.bluraven.io)
//
let domain_controllers = pack_array("list of domain controllers");
let domain_controller_ips = pack_array("IP addresses of domain controllers");
DeviceLogonEvents
| where AccountName has_any (domain_controllers)
| where isnotempty(RemoteIP)
| where RemoteIP !in ("::1", "127.0.0.1", "-")
| where RemoteIP !startswith "fe80::"
| where RemoteIP !in (domain_controller_ips)
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://academy.bluraven.io)
//
let domain_controllers = pack_array("list of domain controllers");
let domain_controller_ips = pack_array("IP addresses of domain controllers");
SecurityEvent
| where EventID == 4624
| where TargetUserName has_any (domain_controllers)
| where isnotempty(IpAddress)
| where IpAddress !in ("::1", "127.0.0.1", "-")
| where IpAddress !startswith "fe80::"
| where IpAddress !in (domain_controller_ips)
| project-reorder TimeGenerated, Computer, TargetUserName, IpAddress
let domain_controller_fqdns = pack_array("list of domain controller fqdns");
let domain_controller_ips = pack_array("IP addresses of domain controllers");
SecurityEvent
| where EventID in (4886, 4887, 4888)
| extend cdc = extract(@'cdc:(\S+)', 1, Attributes), rmd = extract(@'rmd:(\S+)', 1, Attributes)
| where not (cdc in~ (domain_controller_ips) or cdc in~ (domain_controller_fqdns))
Stages and Predicates
Stage 1: source
DeviceNetworkEvents
Stage 2: where
where ActionType == "ListeningConnectionCreated"
Stage 3: where
where LocalPort == 389
Stage 4: where
where not (DeviceName =~ "list of domain controllers")
Stage 5: source
DeviceLogonEvents
Stage 6: where
where AccountName has "domain_controllers"
Stage 7: where
where isnotempty(RemoteIP)
Stage 8: where
where not (RemoteIP in ("-", "127.0.0.1", "::1"))
Stage 9: where
where not (RemoteIP startswith "fe80::")
Stage 10: where
where not (RemoteIP == "domain_controller_ips")
Stage 11: source
SecurityEvent
Stage 12: where
where EventID == 4624
Stage 13: where
where TargetUserName has "domain_controllers"
Stage 14: where
where isnotempty(IpAddress)
Stage 15: where
where not (IpAddress in ("-", "127.0.0.1", "::1"))
Stage 16: where
where not (IpAddress startswith "fe80::")
Stage 17: where
where not (IpAddress == "domain_controller_ips")
Stage 18: project-reorder
project-reorder
Stage 19: source
SecurityEvent
Stage 20: where
where EventID in (4886, 4887, 4888)
Stage 21: extend
extend cdc, rmd
Stage 22: where
where not ((cdc =~ "domain_controller_fqdns" or cdc =~ "domain_controller_ips"))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountName | match |
| field:"AccountName" kind:match value:"domain_controllers" |
ActionType | eq |
| field:"ActionType" kind:eq value:"ListeningConnectionCreated" |
EventID | eq |
| field:"EventID" kind:eq value:"4624" |
EventID | in |
| field:"EventID" kind:in |
IpAddress | is_not_null | field:"IpAddress" kind:is_not_null | |
LocalPort | eq |
| field:"LocalPort" kind:eq value:"389" |
RemoteIP | is_not_null | field:"RemoteIP" kind:is_not_null | |
TargetUserName | match |
| field:"TargetUserName" kind:match value:"domain_controllers" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DeviceName | eq | list of domain controllers | excludes:DeviceName field:"DeviceName" value:"list of domain controllers" |
RemoteIP | in | -, 127.0.0.1, ::1 | excludes:RemoteIP field:"RemoteIP" value:"-" field:"RemoteIP" value:"127.0.0.1" field:"RemoteIP" value:"::1" |
RemoteIP | starts_with | fe80:: | excludes:RemoteIP field:"RemoteIP" value:"fe80::" |
RemoteIP | eq | domain_controller_ips | excludes:RemoteIP field:"RemoteIP" value:"domain_controller_ips" |
IpAddress | in | -, 127.0.0.1, ::1 | excludes:IpAddress field:"IpAddress" value:"-" field:"IpAddress" value:"127.0.0.1" field:"IpAddress" value:"::1" |
IpAddress | starts_with | fe80:: | excludes:IpAddress field:"IpAddress" value:"fe80::" |
IpAddress | eq | domain_controller_ips | excludes:IpAddress field:"IpAddress" value:"domain_controller_ips" |
cdc | eq | domain_controller_fqdns | excludes:cdc field:"cdc" value:"domain_controller_fqdns" |
cdc | eq | domain_controller_ips | excludes:cdc field:"cdc" value:"domain_controller_ips" |
KnowBe4 Defend - Dangerous Attachment Detected
#'Defend has detected a user has a suspicious file type from a suspicious sender in their mailbox.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Execution |
Rule Body
id: b75a4a7a-dedb-47be-b7e6-344f4d50af19
name: KnowBe4 Defend - Dangerous Attachment Detected
description: |
'Defend has detected a user has a suspicious file type from a suspicious sender in their mailbox.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: KnowBe4Defend
datatypes:
- KnowBe4Defend_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- InitialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1204
- T0853
- T0863
- T1566
- T1546
- T1546
tags:
- Defend
query: |
DefendAuditData
| where ThreatLevel == "suspicious" or ThreatLevel == "dangerous"
| mv-expand todynamic(Attachments)
| where Attachments.name matches regex @"(?i)^.*\.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)"
| summarize attachmentCount=count() by TimeGenerated, tostring(Attachments.name), Subject, From, Account_0_FullName = trim(@"[^@.\w]+",Recipients), timesClicked = LinksClicked, SenderIP
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account_0_FullName
- entityType: File
fieldMappings:
- identifier: Name
columnName: Attachments_name
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: Account_0_FullName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SenderIP
alertDetailsOverride:
alertDisplayNameFormat: Alert - {{Account_0_FullName}} has suspicious attachment.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DefendAuditData
Stage 2: where
| where ThreatLevel == "suspicious" or ThreatLevel == "dangerous"
Stage 3: mv-expand
| mv-expand todynamic(Attachments)
Stage 4: where
| where Attachments.name matches regex @"(?i)^.*\.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)"
Stage 5: summarize
| summarize attachmentCount=count() by TimeGenerated, tostring(Attachments.name), Subject, From, Account_0_FullName = trim(@"[^@.\w]+",Recipients), timesClicked = LinksClicked, SenderIP
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Attachments.name | regex_match |
| field:"Attachments.name" kind:regex_match value:"(?i)^.*.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)" |
ThreatLevel | eq |
| field:"ThreatLevel" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account_0_FullName | summarize |
From | summarize |
SenderIP | summarize |
Subject | summarize |
TimeGenerated | summarize |
attachmentCount | summarize |
timesClicked | summarize |
Netskope - WebTransaction Error Detection
#'Rule helps to track error occurred in Netskope WebTransaction Data Connector.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: "66c4cd4c-d391-47e8-b4e6-93e55d86ca9f"
name: "Netskope - WebTransaction Error Detection"
description: |
'Rule helps to track error occurred in Netskope WebTransaction Data Connector.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: NetskopeDataConnector
dataTypes:
- NetskopeWebtxErrors_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
NetskopeWebtxErrors_CL
|where error_s has_any ("Invalid Netskope Hostname", "Webtx Authentication", "Webtx Token Empty", "Webtx Exponential Backoff", "Webtx Idle Time")
incidentConfiguration:
createIncident: true
groupingConfiguration :
enabled: true
reopenClosedIncident: false
lookbackDuration : 5m
matchingMethod : AnyAlert
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Netskope Error at {{TimeGenerated}}'
alertDescriptionFormat: 'Error Message: {{error_s}}'
customDetails:
ErrorMessage: error_s
Time: TimeGenerated
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
NetskopeWebtxErrors_CL
Stage 2: where
| where error_s has_any ("Invalid Netskope Hostname", "Webtx Authentication", "Webtx Token Empty", "Webtx Exponential Backoff", "Webtx Idle Time")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
error_s | match |
| field:"error_s" kind:match |
Objects for Protection Group Deleted
#Detects when objects are deleted from a protection group. This might indicate unauthorized removal of critical components.
Rule Body
id: 88a61215-b3e6-4c78-8acd-9078d9bcfdc3
name: Objects for Protection Group Deleted
description: Detects when objects are deleted from a protection group. This might
indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 29150\n| extend ProtectionGroupName\
\ = extract(\"ProtectionGroupName=\\\"([^\\\"]*)\\\"\", 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 \
\ [\"Protection Group Name\"] = ProtectionGroupName,\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 == 29150
Stage 3: extend
| extend ProtectionGroupName = extract("ProtectionGroupName=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Protection Group Name"] = ProtectionGroupName,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"29150" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Protection Group Name | project |
Severity | project |
User Name | project |
Oracle - Malicious user agent
#'Detects known malicious user agent strings'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 51d050ee-5740-11ec-bf63-0242ac130002
name: Oracle - Malicious user agent
description: |
'Detects known malicious user agent strings'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- OracleWebLogicServer_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
OracleWebLogicServerEvent
| where isnotempty(HttpUserAgentOriginal)
| where HttpUserAgentOriginal has_any ('Nikto', '(hydra)', '.nasl', 'absinthe', 'advanced email extractor', 'arachni/', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project/1.0', 'crimscanner/', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core 1.', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'user-agent:', 'vega/', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc/', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', '(BOT for JCE)')
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
OracleWebLogicServerEvent
Stage 2: where
| where isnotempty(HttpUserAgentOriginal)
Stage 3: where
| where HttpUserAgentOriginal has_any ('Nikto', '(hydra)', '.nasl', 'absinthe', 'advanced email extractor', 'arachni/', 'autogetcontent', 'bilbo', 'BFAC', 'brutus', 'brutus/aet', 'bsqlbf', 'cgichk', 'cisco-torch', 'commix', 'core-project/1.0', 'crimscanner/', 'datacha0s', 'dirbuster', 'domino hunter', 'dotdotpwn', 'email extractor', 'fhscan core 1.', 'floodgate', 'get-minimal', 'gootkit auto-rooter scanner', 'grabber', 'grendel-scan', 'havij', 'inspath', 'internet ninja', 'jaascois', 'zmeu', 'masscan', 'metis', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'paros', 'pmafind', 'prog.customcrawler', 'qualys was', 's.t.a.l.k.e.r.', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'teh forest lobster', 'this is an exploit', 'toata dragostea', 'toata dragostea mea pentru diavola', 'uil2pn', 'user-agent:', 'vega/', 'voideye', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webshag', 'webtrends security analyzer', 'webvulnscan', 'whatweb', 'whcc/', 'wordpress hash grabber', 'xmlrpc exploit', 'WPScan', 'XSpider', 'SF/', 'FooBar/42', 'ScanAlert', 'Webscanner', 'Webster', 'fantomCrew', 'fantomBrowser', 'visvo', 'magereport', 'ltx71', 'websiteprotection', 'BigCliqueBOT', '(BOT for JCE)')
Stage 4: 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 | |
HttpUserAgentOriginal | match |
| field:"HttpUserAgentOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Regsvr32 Rundll32 with Anomalous Parent Process
#This analytical rule looks for rundll32.exe or regsvr32.exe being spawned by abnormal processes: wscript.exe, powershell.exe, cmd.exe, pwsh.exe, cscript.exe. Blog: https://threathunt.blog/running-live-malware-for-threat-hunting-purposes/
MITRE ATT&CK Coverage
Telemetry Coverage
Rule Body
id: 2624fc55-0998-4897-bb48-1c6422befce4
name: Regsvr32 Rundll32 with Anomalous Parent Process
description: |
This analytical rule looks for rundll32.exe or regsvr32.exe being spawned by abnormal processes: wscript.exe, powershell.exe, cmd.exe, pwsh.exe, cscript.exe.
Blog: https://threathunt.blog/running-live-malware-for-threat-hunting-purposes/
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
- DeviceNetworkEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1218.010
- T1218.011
tags:
- Defense Evasion
- Image Load
- Regsvr32 Abuse
- Rundll32 Abuse
query: |
DeviceProcessEvents
| where FileName has_any ("rundll32.exe","regsvr32.exe")
| where InitiatingProcessFileName has_any ("wscript.exe","powershell.exe","cmd.exe","pwsh.exe","cscript.exe")
| project TimeGenerated, DeviceName, InvestigatedProcessName=FileName, InvestigatedProcessCommandLine = ProcessCommandLine,InvestigatedProcessStartTime = ProcessCreationTime, InvestigatedProcessId = ProcessId, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName
| join (
DeviceNetworkEvents
| where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")
| where RemoteIPType == "Public"
| project DeviceName, InvestigatedProcessName=InitiatingProcessFileName, InvestigatedProcessCommandLine = InitiatingProcessCommandLine,InvestigatedProcessStartTime = InitiatingProcessCreationTime, InvestigatedProcessId = InitiatingProcessId, LocalIP, RemoteIP, RemoteUrl
) on DeviceName, InvestigatedProcessCommandLine, InvestigatedProcessId, InvestigatedProcessName, InvestigatedProcessStartTime
| project-away DeviceName1, InvestigatedProcessCommandLine1, InvestigatedProcessId1, InvestigatedProcessName1, InvestigatedProcessStartTime1
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: LocalIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RemoteUrl
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where FileName has_any ("rundll32.exe","regsvr32.exe")
Stage 3: where
| where InitiatingProcessFileName has_any ("wscript.exe","powershell.exe","cmd.exe","pwsh.exe","cscript.exe")
Stage 4: project
| project TimeGenerated, DeviceName, InvestigatedProcessName=FileName, InvestigatedProcessCommandLine = ProcessCommandLine,InvestigatedProcessStartTime = ProcessCreationTime, InvestigatedProcessId = ProcessId, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName
Stage 5: join
| join (
DeviceNetworkEvents
| where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")
| where RemoteIPType == "Public"
| project DeviceName, InvestigatedProcessName=InitiatingProcessFileName, InvestigatedProcessCommandLine = InitiatingProcessCommandLine,InvestigatedProcessStartTime = InitiatingProcessCreationTime, InvestigatedProcessId = InitiatingProcessId, LocalIP, RemoteIP, RemoteUrl
) on DeviceName, InvestigatedProcessCommandLine, InvestigatedProcessId, InvestigatedProcessName, InvestigatedProcessStartTime
Stage 6: project-away
project-away DeviceName1, InvestigatedProcessCommandLine1, InvestigatedProcessId1, InvestigatedProcessName1, InvestigatedProcessStartTime1
Stage 7: extend
extend HostName
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 8: extend
extend DnsDomain
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FileName | match |
| field:"process_basename" kind:match |
InitiatingProcessFileName | match |
| field:"parent_process_basename" kind:match |
RemoteIPType | eq |
| field:"RemoteIPType" kind:eq value:"Public" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | project |
InitiatingProcessCommandLine | project |
InitiatingProcessFileName | project |
InitiatingProcessParentFileName | project |
InvestigatedProcessCommandLine | project |
InvestigatedProcessId | project |
InvestigatedProcessName | project |
InvestigatedProcessStartTime | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Sentinel One - Exclusion added
#'Detects when new exclusion added.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule Body
id: 4224409f-a7bf-45eb-a931-922d79575a05
name: Sentinel One - Exclusion added
description: |
'Detects when new exclusion added.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SentinelOne
dataTypes:
- SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1070
query: |
SentinelOne
| where EventOriginalMessage has_all ('added', 'exclusion')
| project EventCreationTime, SrcUserName, EventOriginalMessage
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SentinelOne
Stage 2: where
| where EventOriginalMessage has_all ('added', 'exclusion')
Stage 3: project
| project EventCreationTime, SrcUserName, EventOriginalMessage
Stage 4: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalMessage | match |
| field:"EventOriginalMessage" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCreationTime | project |
EventOriginalMessage | project |
SrcUserName | project |
AccountCustomEntity | extend |
Sonrai Ticket Snoozed
#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 been snoozed. It uses the action type to check if a ticket has been snoozed'
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: 10e6c454-5cad-4f86-81ce-800235cb050a
name: Sonrai Ticket Snoozed
description: |
'Checks if Sonrai tickets have been snoozed.
It uses the action type to check if a ticket has been snoozed'
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 == 6
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: Snoozed - {{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 == 6
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action_d | eq |
| field:"action_d" kind:eq value:"6" |
Tailscale: Tailnet lock validation failed
#Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Suspicious - likely an unsigned node attempting to join.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Stealth | |
| Defense Impairment | |
| Credential Access |
Rule Body
id: e9f0a1b2-3456-7890-12cd-ef1234560040
name: "Tailscale: Tailnet lock validation failed"
description: Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Suspicious - likely an unsigned node attempting to join.
description-detailed: |
Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Tailnet lock requires new node-keys be co-signed by trusted signers; errors here are the only direct signal of a node-key injection attempt.
severity: High
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Devices_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- InitialAccess
relevantTechniques:
- T1556
- T1078
query: |
Tailscale_Devices_CL
| where TimeGenerated > ago(1h)
| summarize arg_max(TimeGenerated, *) by DeviceId
| where isnotempty(TailnetLockError)
| project TimeGenerated, DeviceName, Hostname, User, Os, ClientVersion, TailnetLockError, TailnetLockKey, LastSeen, Authorized
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Tailscale_Devices_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by DeviceId
Stage 4: where
| where isnotempty(TailnetLockError)
Stage 5: project
| project TimeGenerated, DeviceName, Hostname, User, Os, ClientVersion, TailnetLockError, TailnetLockKey, LastSeen, Authorized
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
TailnetLockError | is_not_null | field:"TailnetLockError" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Authorized | project |
ClientVersion | project |
DeviceName | project |
Hostname | project |
LastSeen | project |
Os | project |
TailnetLockError | project |
TailnetLockKey | project |
TimeGenerated | project |
User | project |
Ubiquiti - Unusual traffic
#'Detects unusual traffic masking as HTTP(S).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 31e868c0-91d3-40eb-accc-3fa73aa96f8e
name: Ubiquiti - Unusual traffic
description: |
'Detects unusual traffic masking as HTTP(S).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1573
query: |
UbiquitiAuditEvent
| where EventCategory =~ 'firewall'
| where DstPortNumber in ('80', '443')
| where NetworkProtocol !~ 'TCP'
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
UbiquitiAuditEvent
Stage 2: where
| where EventCategory =~ 'firewall'
Stage 3: where
| where DstPortNumber in ('80', '443')
Stage 4: where
| where NetworkProtocol !~ 'TCP'
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstPortNumber | in |
| field:"DstPortNumber" kind:in |
EventCategory | eq |
| field:"EventCategory" kind:eq value:"firewall" |
NetworkProtocol | ne |
| field:"NetworkProtocol" kind:ne value:"TCP" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Vaikora - Agent policy violation
#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 AI agent actions explicitly blocked by a Vaikora policy. Repeated violations from the same agent may indicate prompt injection, policy circumvention, or a compromised workflow.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Rule Body
id: 54262ad1-f346-4246-a13f-9557595ff7bd
name: Vaikora - Agent policy violation
description: |
Identifies AI agent actions explicitly blocked by a Vaikora policy. Repeated violations from the same agent may indicate prompt injection, policy circumvention, or a compromised workflow.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VaikoraSentinel
dataTypes:
- Vaikora_AgentSignals_CL
queryFrequency: 15m
queryPeriod: 1h
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1078
- T1562
query: |
Vaikora_AgentSignals_CL
| where TimeGenerated > ago(1h)
| where policy_decision_s == "block"
| summarize
ViolationCount = count(),
PolicyIds = make_set(policy_id_s),
ActionTypes = make_set(action_type_s),
ResourceTypes = make_set(resource_type_s),
MaxAnomalyScore = max(anomaly_score_d),
Severities = make_set(severity_s),
LogHashes = make_set(log_hash_s)
by AgentId = agent_id_s
| extend
PolicyList = strcat_array(PolicyIds, ", "),
ActionList = strcat_array(ActionTypes, ", "),
ResourceList = strcat_array(ResourceTypes, ", ")
| where ViolationCount >= 1
suppressionDuration: 15m
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: Selected
groupByEntities:
- Account
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
ViolationCount: ViolationCount
PolicyIds: PolicyList
ActionTypes: ActionList
MaxAnomalyScore: MaxAnomalyScore
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AgentId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Vaikora_AgentSignals_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where policy_decision_s == "block"
Stage 4: summarize
| summarize
ViolationCount = count(),
PolicyIds = make_set(policy_id_s),
ActionTypes = make_set(action_type_s),
ResourceTypes = make_set(resource_type_s),
MaxAnomalyScore = max(anomaly_score_d),
Severities = make_set(severity_s),
LogHashes = make_set(log_hash_s)
by AgentId = agent_id_s
Stage 5: extend
| extend
PolicyList = strcat_array(PolicyIds, ", "),
ActionList = strcat_array(ActionTypes, ", "),
ResourceList = strcat_array(ResourceTypes, ", ")
Stage 6: where
| where ViolationCount >= 1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ViolationCount | ge |
| field:"ViolationCount" kind:ge value:"1" |
policy_decision_s | eq |
| field:"policy_decision_s" kind:eq value:"block" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActionTypes | summarize |
AgentId | summarize |
LogHashes | summarize |
MaxAnomalyScore | summarize |
PolicyIds | summarize |
ResourceTypes | summarize |
Severities | summarize |
ViolationCount | summarize |
ActionList | extend |
PolicyList | extend |
ResourceList | extend |
Whisper Security - Domain Registrar Change Anomaly
#Detects domains whose registrar has changed across WHOIS history snapshots within the last 30 days. Registrar changes may indicate domain hijacking or adversary infrastructure acquisition.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Resource Development |
Rule Body
id: b6e26c67-f596-5c0f-8614-c88d715508c5
name: Whisper Security - Domain Registrar Change Anomaly
description: |
Detects domains whose registrar has changed across WHOIS history snapshots within the last 30 days. Registrar changes may indicate domain hijacking or adversary infrastructure acquisition.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
dataTypes:
- WhisperHistory_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
relevantTechniques:
- T1584
query: |
// MITRE ATT&CK: T1584 - Compromise Infrastructure
// Tactic: Resource Development
// Detects registrar changes across WHOIS history snapshots
let lookbackPeriod = 14d;
WhisperHistory_CL
| where TimeGenerated > ago(lookbackPeriod)
| where indicatorType == "domain"
| where isnotempty(registrar)
| summarize Registrars = make_set(registrar), SnapshotCount = count(), EarliestSnapshot = min(snapshotDate), LatestSnapshot = max(snapshotDate) by indicator
| where array_length(Registrars) > 1
| mv-expand CurrentRegistrar = Registrars
| extend CurrentRegistrar = tostring(CurrentRegistrar)
| join kind=inner (
WhisperHistory_CL
| where TimeGenerated > ago(lookbackPeriod)
| where indicatorType == "domain"
| where isnotempty(registrar)
| serialize
| extend PreviousRegistrar = prev(registrar), PreviousIndicator = prev(indicator)
| where indicator == PreviousIndicator
| where registrar != PreviousRegistrar
| where isnotempty(PreviousRegistrar)
| project indicator, OldRegistrar = PreviousRegistrar, NewRegistrar = registrar, ChangeDate = snapshotDate
) on indicator
| project TimeGenerated = ChangeDate, DnsDomain = indicator, OldRegistrar, NewRegistrar, ChangeDate, SnapshotCount
| distinct TimeGenerated, DnsDomain, OldRegistrar, NewRegistrar, ChangeDate, SnapshotCount
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DnsDomain
customDetails:
OldRegistrar: OldRegistrar
NewRegistrar: NewRegistrar
ChangeDate: ChangeDate
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookbackPeriod = 14d;
Stage 1: source
WhisperHistory_CL
Stage 2: where
| where TimeGenerated > ago(lookbackPeriod)
Stage 3: where
| where indicatorType == "domain"
Stage 4: where
| where isnotempty(registrar)
Stage 5: summarize
| summarize Registrars = make_set(registrar), SnapshotCount = count(), EarliestSnapshot = min(snapshotDate), LatestSnapshot = max(snapshotDate) by indicator
Stage 6: where
| where array_length(Registrars) > 1
Stage 7: mv-expand
| mv-expand CurrentRegistrar = Registrars
Stage 8: extend
| extend CurrentRegistrar = tostring(CurrentRegistrar)
Stage 9: join
| join kind=inner (
WhisperHistory_CL
| where TimeGenerated > ago(lookbackPeriod)
| where indicatorType == "domain"
| where isnotempty(registrar)
| serialize
| extend PreviousRegistrar = prev(registrar), PreviousIndicator = prev(indicator)
| where indicator == PreviousIndicator
| where registrar != PreviousRegistrar
| where isnotempty(PreviousRegistrar)
| project indicator, OldRegistrar = PreviousRegistrar, NewRegistrar = registrar, ChangeDate = snapshotDate
) on indicator
Stage 10: project
project ChangeDate, DnsDomain, NewRegistrar, OldRegistrar, SnapshotCount, TimeGenerated
Stage 11: distinct
distinct ChangeDate, DnsDomain, NewRegistrar, OldRegistrar, SnapshotCount, TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PreviousRegistrar | is_not_null | field:"PreviousRegistrar" kind:is_not_null | |
indicatorType | eq |
| field:"indicatorType" kind:eq value:"domain" |
registrar | is_not_null | field:"registrar" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ChangeDate | project |
DnsDomain | project |
NewRegistrar | project |
OldRegistrar | project |
SnapshotCount | project |
TimeGenerated | project |