Detection rules › Kusto
Kusto rules: 0p
ADWS Connection from Process Injection Target
#The query first collects all network connections to the Active Directory Web Services (ADWS) service. It then searches for processes that inject into a process that makes a connection to ADWS. This can be used to detect process injection into a process that is used to query Active Directory.
Known False Positives
- None expected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Discovery | |
| Collection |
References
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let ADWSConnections=(
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 9389
| extend InitiatingProcessFileName=tolower(InitiatingProcessFileName)
);
let ADWSFileNames=materialize(
ADWSConnections
| distinct InitiatingProcessFileName
);
// Look for processes that inject into a process that makes an ADWS connection.
let InjectorProcesses=materialize(
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where not(isempty(FileName))
// Look for actions associated with process injection.
| where ActionType in~ ("CreateRemoteThreadApiCall", "MemoryRemoteProtect","NtAllocateVirtualMemoryApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "NtProtectVirtualMemoryApiCall", "SetThreadContextRemoteApiCall", "QueueUserApcRemoteApiCall")
| where FileName in~ (ADWSFileNames)
| where not(InitiatingProcessFolderPath startswith @"c:\program files\vmware\vmware tools" and InitiatingProcessFileName =~ "vmtoolsd.exe")
| where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32" and InitiatingProcessFileName =~ "csrss.exe")
| where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32\csrss.exe")
| where not(InitiatingProcessFolderPath startswith @"c:\program files\microsoft azure ad sync\" and InitiatingProcessFileName =~ "miiserver.exe")
| extend FileName=tolower(FileName)
| lookup kind=inner ADWSConnections on DeviceId, $left.ProcessId == $right.InitiatingProcessId, $left.FileName == $right.InitiatingProcessFileName
// Begin environment-specific filter.
// End environment-specific filter.
| project DeviceId, ProcessId, FileName, InjectorProcessId=InitiatingProcessId, InjectorFileName=InitiatingProcessFileName, InjectorActionType=ActionType
);
// Find ADWS connections from processes that were injected into by another process.
ADWSConnections
| lookup kind=inner InjectorProcesses on DeviceId, $left.InitiatingProcessId == $right.ProcessId, $left.InitiatingProcessFileName == $right.FileName
| summarize arg_min(Timestamp,*), InjectionMethods=make_set(InjectorActionType) by DeviceId, DeviceName, InitiatingProcessId, InitiatingProcessFileName
| extend HostName=tostring(split(DeviceName,".")[0]),DnsDomain=iif(DeviceName contains ".", substring(DeviceName, indexof(DeviceName, ".") + 1, strlen(DeviceName)),"")
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let ADWSConnections is inlined into the numbered stages below.
Let Binding: ADWSFileNames
let ADWSFileNames = materialize(
ADWSConnections
| distinct InitiatingProcessFileName
);
Let Binding: InjectorProcesses
let InjectorProcesses = materialize(
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where not(isempty(FileName))
| where ActionType in~ ("CreateRemoteThreadApiCall", "MemoryRemoteProtect","NtAllocateVirtualMemoryApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "NtProtectVirtualMemoryApiCall", "SetThreadContextRemoteApiCall", "QueueUserApcRemoteApiCall")
| where FileName in~ (ADWSFileNames)
| where not(InitiatingProcessFolderPath startswith @"c:\program files\vmware\vmware tools" and InitiatingProcessFileName =~ "vmtoolsd.exe")
| where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32" and InitiatingProcessFileName =~ "csrss.exe")
| where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32\csrss.exe")
| where not(InitiatingProcessFolderPath startswith @"c:\program files\microsoft azure ad sync\" and InitiatingProcessFileName =~ "miiserver.exe")
| extend FileName=tolower(FileName)
| lookup kind=inner ADWSConnections on DeviceId, $left.ProcessId == $right.InitiatingProcessId, $left.FileName == $right.InitiatingProcessFileName
| project DeviceId, ProcessId, FileName, InjectorProcessId=InitiatingProcessId, InjectorFileName=InitiatingProcessFileName, InjectorActionType=ActionType
);
Stage 1: source
let ADWSConnections
Stage 2: source
let ADWSFileNames
Stage 3: source
let InjectorProcesses
Stage 4: source
DeviceNetworkEvents
Stage 5: where
where ...
Stage 6: where
where ActionType == "ConnectionSuccess"
Stage 7: where
where RemotePort == 9389
Stage 8: extend
extend InitiatingProcessFileName
Stage 9: kusto:lookup
lookup kind=inner (InjectorProcesses) on DeviceId, $left.InitiatingProcessId, $right.ProcessId, $left.InitiatingProcessFileName, $right.FileName
Stage 10: summarize
summarize InjectionMethods by DeviceId, DeviceName, InitiatingProcessId, InitiatingProcessFileName
Stage 11: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ConnectionSuccess" |
ActionType | in |
| field:"ActionType" kind:in |
FileName | in |
| field:"FileName" kind:in value:"ADWSFileNames" |
RemotePort | eq |
| field:"RemotePort" kind:eq value:"9389" |
Exclusions
The rule actively suppresses these predicates.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
DeviceName | summarize |
InitiatingProcessFileName | summarize |
InitiatingProcessId | summarize |
InjectionMethods | summarize |
DnsDomain | extend |
HostName | extend |
Authentication Method Changed for Privileged Account
#Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
- Azure AD External Guest User Invited (Splunk)
Rule Body
id: feb0a2fb-ae75-4343-8cbc-ed545f1da289
name: Authentication Method Changed for Privileged Account
description: |
'Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 2h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
tags:
- AADSecOpsGuide
query: |
let VIPUsers = (IdentityInfo
| where AssignedRoles contains "Admin"
| summarize by tolower(AccountUPN));
AuditLogs
| where TimeGenerated > ago(2h)
| where Category =~ "UserManagement"
| where ActivityDisplayName =~ "User registered security info"
| where LoggedByService =~ "Authentication Methods"
| extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| where tolower(TargetUserPrincipalName) in (VIPUsers)
| extend TargetAadUserId = tostring(TargetResources[0].id)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetAccountName
- identifier: UPNSuffix
columnName: TargetAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: TargetAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Let Binding: VIPUsers
let VIPUsers = (IdentityInfo
| where AssignedRoles contains "Admin"
| summarize by tolower(AccountUPN));
Stage 1: source
let VIPUsers
Stage 2: source
AuditLogs
Stage 3: where
where TimeGenerated > ago(7200s)
Stage 4: where
where Category =~ "UserManagement"
Stage 5: where
where ActivityDisplayName =~ "User registered security info"
Stage 6: where
where LoggedByService =~ "Authentication Methods"
Stage 7: extend
extend TargetUserPrincipalName
Stage 8: where
where TargetUserPrincipalName == "VIPUsers"
Stage 9: extend (6 consecutive steps)
extend InitiatingAadUserId, InitiatingAccountName, InitiatingAccountUPNSuffix, InitiatingIPAddress, InitiatingUserPrincipalName, TargetAadUserId, TargetAccountName, TargetAccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityDisplayName | eq |
| field:"ActivityDisplayName" kind:eq value:"User registered security info" |
Category | eq |
| field:"Category" kind:eq value:"UserManagement" |
LoggedByService | eq |
| field:"LoggedByService" kind:eq value:"Authentication Methods" |
TargetUserPrincipalName | in |
| field:"TargetUserPrincipalName" kind:in value:"VIPUsers" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetUserPrincipalName | extend |
TargetAadUserId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIPAddress | extend |
TargetAccountName | extend |
TargetAccountUPNSuffix | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
Awake Security - High Match Counts By Device
#This query searches for devices with unexpectedly large number of activity match.
Rule Body
id: 90b7ac11-dd6c-4ba1-a99b-737061873859
name: Awake Security - High Match Counts By Device
description: This query searches for devices with unexpectedly large number of activity match.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
CommonSecurityLog
| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName
| where ModelMatchCount > 1000 and MaxSeverity > 2
| extend SeverityName=iff(MaxSeverity == 0, "Informational", iff(MaxSeverity < 5, "Low", iff(MaxSeverity < 8, "Medium", "High")))
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: Awake Security - High Model Match Counts On Device {{SourceHostName}}
alertDescriptionFormat: |-
The following Awake model(s):
{{Models}}
matched {{ModelMatchCount}} activities, an unexpectedly large number. The destination IPs associated with these matches were:
{{DestinationIPs}}
alertTacticsColumnName: null
alertSeverityColumnName: SeverityName
customDetails:
Matched_Models: Models
Matches_ASP_URLs: ASPMatchURLs
Device: SourceHostName
Matches_Count: ModelMatchCount
Matches_Max_Severity: MaxSeverity
Matches_Dest_IPs: DestinationIPs
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIPs
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: 3d
matchingMethod: Selected
groupByEntities:
- Host
groupByAlertDetails: []
groupByCustomDetails:
- Device
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"
Stage 3: summarize
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName
Stage 4: where
| where ModelMatchCount > 1000 and MaxSeverity > 2
Stage 5: extend
| extend SeverityName=iff(MaxSeverity == 0, "Informational", iff(MaxSeverity < 5, "Low", iff(MaxSeverity < 8, "Medium", "High")))
SeverityName =MaxSeverity == 0"Informational"iff((MaxSeverity < 5), "Low", iff((MaxSeverity < 8), "Medium", "High"))Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"Awake Security" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Arista Networks" |
MaxSeverity | gt |
| field:"MaxSeverity" kind:gt value:"2" |
ModelMatchCount | gt |
| field:"ModelMatchCount" kind:gt value:"1000" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ASPMatchURLs | summarize |
DestinationIPs | summarize |
EndTime | summarize |
MaxSeverity | summarize |
ModelMatchCount | summarize |
Models | summarize |
SourceHostName | summarize |
SourceIPs | summarize |
StartTime | summarize |
SeverityName | extend |
AWSCloudTrail - Creation of SSM policy and then privilege escalation
#Identifies creation of IAM policies that grant broad AWS Systems Manager permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to gain administration capabilities that support privilege escalation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- ASL AWS Create Policy Version to allow all resources (Splunk)
- AWS IAM CompromisedKeyQuarantine Policy Attached to User (Elastic)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - CloudFormation policy created then used for privilege escalation (Kusto)
- AWSCloudTrail - Created CRUD S3 policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD DynamoDB policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD Lambda policy and then privilege escalation (Kusto)
Rule Body
id: aaa2c05e-fdd4-4fa0-9072-6cffe3641b34
name: AWSCloudTrail - Creation of SSM policy and then privilege escalation
description: |
Identifies creation of IAM policies that grant broad AWS Systems Manager permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to gain administration capabilities that support privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1484
- T1098.003
query: |
let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;
// Creating Master table with all the events to use with materialize for better performance
let EventInfo = materialize(AWSCloudTrail
| where TimeGenerated >= ago(lookback)
| where EventName in (EventNameList) or EventName in (createPolicy)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), ""));
//Checking for Policy creation event with Full Admin Privileges since lookback period.
let FullAdminPolicyEvents = EventInfo
| where TimeGenerated >= ago(lookback)
| where EventName in (createPolicy)
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
| mvexpand Statement
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
| extend Action = tostring(Action)
| where Effect =~ "Allow" and (Action contains "ssm:*" or Action contains "ssm:Create*" or Action contains "ssm:CreateAssociation") and Resource == "*" and Condition == ""
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
| project-rename StartTime = TimeGenerated;
let PolicyAttach = EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName, UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType", UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
;
// Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
// These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
FullAdminPolicyEvents
| join kind=leftouter
(
PolicyAttach
)
on PolicyName
| project-away PolicyName1
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
PolicyName: PolicyName
EventName: EventName
RecipientAccountId: RecipientAccountId
UserIdentityArn: UserIdentityArn
alertDetailsOverride:
alertDisplayNameFormat: 'AWS Systems Manager privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;
let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.
Let Binding: PolicyAttach
let PolicyAttach = EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName, UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType", UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;
Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 use it.
Stage 1: source
AWSCloudTrail
Stage 2: where
| where TimeGenerated >= ago(lookback)
Stage 3: where
| where EventName in (EventNameList) or EventName in (createPolicy)
Stage 4: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 5: where
| where TimeGenerated >= ago(lookback)
Stage 6: where
| where EventName in (createPolicy)
Stage 7: extend
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
Stage 8: extend
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
Stage 9: mv-expand
| mvexpand Statement
Stage 10: extend
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
Stage 11: extend
| extend Action = tostring(Action)
Stage 12: where
| where Effect =~ "Allow" and (Action contains "ssm:*" or Action contains "ssm:Create*" or Action contains "ssm:CreateAssociation") and Resource == "*" and Condition == ""
Stage 13: distinct
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
Stage 14: project-rename
| project-rename StartTime = TimeGenerated
Stage 15: join
FullAdminPolicyEvents
| join kind=leftouter
(
PolicyAttach
)
on PolicyName
Stage 16: project-away
project-away PolicyName1
Stage 17: summarize aggregation inside the join branch
summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"ErrorCode" kind:is_null | |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
AccountUPNSuffix | summarize |
EventName | summarize |
EventSource | summarize |
PolicyName | summarize |
RecipientAccountId | summarize |
SourceIpAddress | summarize |
UserIdentityArn | summarize |
UserIdentityType | summarize |
Azure Security Benchmark Posture Changed
#This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative and triggers when policy compliance falls below 70% within a 7-day time window.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 0610e72f-ceaf-42d1-879e-952a1bd8d07a
name: Azure Security Benchmark Posture Changed
description: This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative
and triggers when policy compliance falls below 70% within a 7-day time window.
severity: Medium
requiredDataConnectors:
- connectorId: AzureSecurityCenter
dataTypes:
- SecurityRecommendation
- SecurityRegulatoryCompliance
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
let ComplianceDomainLookup = SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));
SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))))
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain
| extend PassedControlsPercentage = iff(Total > 0, (Passed / todouble(Total)) * 100.0, real(null))
| join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain
| project ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed, LastEvaluated = TimeGenerated
| summarize arg_max(LastEvaluated, *) by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
| where PassedControlsPercentage < 70
| sort by PassedControlsPercentage asc, Passed desc
| extend RemediationLink = strcat("https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22")
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RemediationLink
alertDetailsOverride:
alertDisplayNameFormat: Azure Security Benchmark posture below threshold for {{ComplianceDomain}}
customDetails:
ComplianceDomain: ComplianceDomain
TotalControls: Total
PassedControls: Passed
FailedControls: Failed
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: ComplianceDomainLookup
let ComplianceDomainLookup = SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));
Stage 1: source
SecurityRecommendation
Stage 2: join
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
Stage 3: summarize
summarize by AssessedResourceId, RecommendationName
Stage 4: extend
extend ComplianceDomain
ComplianceDomain =ComplianceControl contains "NS.""Network Security"iff((ComplianceControl contains "IM."), "Identity Management", iff((ComplianceControl contains "PA."), "Privileged Access", iff((ComplianceControl contains "DP."), "Data Protection", iff((ComplianceControl contains "AM."), "Asset Management", iff((ComplianceControl contains "LT."), "Logging & Threat Detection", iff((ComplianceControl contains "IR."), "Incident Response", iff((ComplianceControl contains "PV."), "Posture & Vulnerability Management", iff((ComplianceControl contains "ES."), "Endpoint Security", iff((ComplianceControl contains "BR."), "Backup & Recovery", iff((ComplianceControl startswith "DS."), "DevOps Security", iff((ComplianceControl contains "GS."), "Governance & Strategy", "Other")))))))))))Stage 5: summarize
summarize Failed, Passed, Total by ComplianceDomain
Stage 6: extend
extend PassedControlsPercentage
PassedControlsPercentage =Total > 0((Passed / todouble(Total)) * 100.0)real(null)Stage 7: join
join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain
Stage 8: project
project ComplianceDomain, Failed, LastEvaluated, Passed, PassedControlsPercentage, Total
Stage 9: summarize
summarize by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
Stage 10: where
where PassedControlsPercentage < 70
Stage 11: sort
sort by Passed, PassedControlsPercentage
Stage 12: extend
extend RemediationLink
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ComplianceStandard | eq |
| field:"ComplianceStandard" kind:eq value:"Azure-Security-Benchmark" |
PassedControlsPercentage | lt |
| field:"PassedControlsPercentage" kind:lt value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ComplianceDomain | summarize |
Failed | summarize |
Passed | summarize |
PassedControlsPercentage | summarize |
Total | summarize |
RemediationLink | extend |
Corelight - Possible Webshell
#'Detects post requests to unusual extensions.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: f3245aa1-1ca1-471c-a0b7-97ea6b791d5d
name: Corelight - Possible Webshell
description: |
'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Corelight
dataTypes:
- corelight_http
- corelight_http_red
- corelight_http2
- corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1505
query: |
union isfuzzy=true corelight_http, corelight_http_agg
| where method in~ ('POST', 'PUT')
| where toint(status_code) between (200 .. 299)
| where request_body_len != 0 or response_body_len != 0
| extend fe = extract(@'.*(\.\w+)$', 1, uri)
| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: id_orig_h
version: 2.2.0
kind: Scheduled
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: corelight_http, corelight_http_agg
Leg 1: corelight_http
Leg 2: corelight_http_agg
Applied to the Combined Result
| where method in~ ('POST', 'PUT') | where toint(status_code) between (200 .. 299) | where request_body_len != 0 or response_body_len != 0 | extend fe = extract(@'.*(\.\w+)$', 1, uri) | where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
fe | in |
| field:"fe" kind:in |
method | in |
| field:"method" kind:in |
request_body_len | ne |
| field:"request_body_len" kind:ne value:"0" |
response_body_len | ne |
| field:"response_body_len" kind:ne value:"0" |
status_code | ge |
| field:"status_code" kind:ge value:"200" |
status_code | le |
| field:"status_code" kind:le value:"299" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
fe | extend |
CYFIRMA - Medium severity Trojan Network Indicators - Monitor 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 analytics rule detects network-based indicators flagged by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These indicators may represent attacker-controlled endpoints used for persistence, data exfiltration, or command delivery to compromised systems."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Credential Access | |
| Command & Control | |
| Impact |
Rule Body
id: 104f4574-fc95-4f38-8aa2-02f0b78eba9b
name: CYFIRMA - Medium severity Trojan Network Indicators - Monitor Recommended Rule
description: |
"This analytics rule detects network-based indicators flagged by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
These indicators may represent attacker-controlled endpoints used for persistence, data exfiltration, or command delivery to compromised systems."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Trojan Network Indicators - Monitor 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 'Monitor' and Roles has 'Trojan'
| 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:
- Impact
- Persistence
- DefenseEvasion
- CredentialAccess
- CommandAndControl
- Execution
- InitialAccess
relevantTechniques:
- T1496
- T1053
- T1555
- T1090
- T1071
- T1204
- T1189
- T1053.005
- T1555.003
- T1071.001
- T1204.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Trojan Network Indicators - Monitor Recommended Rule - {{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 'Monitor' and Roles has 'Trojan'
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 |
Dataverse - Audit log data deletion
#Identifies audit log data deletion activity in Dataverse.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: f1634822-b7e9-44f5-95ac-fa4a04f14513
kind: Scheduled
name: Dataverse - Audit log data deletion
description: Identifies audit log data deletion activity in Dataverse.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1070
query: |
let query_frequency = 1h;
DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message =~ 'DeleteRecordChangeHistory' or Message =~ 'DeleteAuditData'
| extend CloudAppId = int(32780)
| extend AccountName = tostring(split(UserId, "@")[0])
| extend UPNSuffix = tostring(split(UserId, "@")[1])
| project
TimeGenerated,
UserId,
ClientIp,
UserAgent,
Message,
EntityName,
InstanceUrl,
AccountName,
UPNSuffix,
CloudAppId
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
alertDetailsOverride:
alertDisplayNameFormat: Dataverse - Audit logs deleted in {{InstanceUrl}}
alertDescriptionFormat: User {{UserId}} deleted audit log data in {{InstanceUrl}}.
The message type is {{Message}}.
version: 3.2.0
Stages and Predicates
Parameters
let query_frequency = 1h;
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where Message =~ 'DeleteRecordChangeHistory' or Message =~ 'DeleteAuditData'
Stage 4: extend (3 consecutive steps)
| extend CloudAppId = int(32780)
| extend AccountName = tostring(split(UserId, "@")[0])
| extend UPNSuffix = tostring(split(UserId, "@")[1])
Stage 5: project
| project
TimeGenerated,
UserId,
ClientIp,
UserAgent,
Message,
EntityName,
InstanceUrl,
AccountName,
UPNSuffix,
CloudAppId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | eq |
| field:"Message" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ClientIp | project |
CloudAppId | project |
EntityName | project |
InstanceUrl | project |
Message | project |
TimeGenerated | project |
UPNSuffix | project |
UserAgent | project |
UserId | project |
DNS events related to mining pools
#'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 0d76e9cf-788d-4a69-ac7d-f234826b5bed
name: DNS events related to mining pools
description: |
'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: DNS
dataTypes:
- DnsEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1496
query: |
DnsEvents
| where Name contains "."
| where Name has_any ("monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com",
"xmrget.com", "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club",
"supportxmr.com", "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com",
"gntl.co.uk", "semipool.com", "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io",
"coinpoolit.webhop.me", "nanopool.org", "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org",
"extrmepool.org", "webcoin.me", "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com",
"dwarfpool.com", "hash-to-coins.com", "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream",
"moneropool.ru", "cryptonotepool.org.uk", "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net",
"backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net", "shscrypto.net")
| extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
| extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
DnsEvents
Stage 2: where
| where Name contains "."
Stage 3: where
| where Name has_any ("monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com",
"xmrget.com", "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club",
"supportxmr.com", "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com",
"gntl.co.uk", "semipool.com", "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io",
"coinpoolit.webhop.me", "nanopool.org", "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org",
"extrmepool.org", "webcoin.me", "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com",
"dwarfpool.com", "hash-to-coins.com", "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream",
"moneropool.ru", "cryptonotepool.org.uk", "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net",
"backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net", "shscrypto.net")
Stage 4: extend
| extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
HostName =Computer has "."substring(Computer, 0, indexof(Computer, '.'))ComputerStage 5: extend
| extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
DnsDomain =Computer has "."substring(Computer, (indexof(Computer, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Name | contains |
| field:"Name" kind:contains value:"." |
Name | match |
| field:"Name" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
DnsDomain | extend |
License Expiring
#Detects when a Veeam license expires shortly.
Rule Body
id: 79adb08a-8218-492d-acf0-f8aa596f3444
name: License Expiring
description: Detects when a Veeam license expires shortly.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "let license_editions_lookup = union isfuzzy=true (datatable(Edition:string,\
\ EditionDescription:string)[]), (_GetWatchlist(\"license_editions_lookup\")); \n\
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]),\
\ (_GetWatchlist(\"license_types_lookup\")); \nVeeam_GetSecurityEvents\n| where\
\ instanceId == 24020\n| extend Edition = extract(\"Edition=\\\"([^\\\"]*)\\\"\"\
, 1, SyslogMessage)\n| lookup kind=leftouter (license_editions_lookup) \n on\
\ $left.Edition == $right.Edition\n| extend Type = extract(\"Type=\\\"([^\\\"]*)\\\
\"\", 1, SyslogMessage)\n| lookup kind=leftouter (license_types_lookup) \n on\
\ $left.Type == $right.Type\n| extend TenantID = extract(\"TenantID=\\\"([^\\\"\
]*)\\\"\", 1, SyslogMessage)\n| extend DaysLeft = extract(\"DaysLeft=\\\"([^\\\"\
]*)\\\"\", 1, SyslogMessage)\n| extend SupportLeft = extract(\"SupportLeft=\\\"\
([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"License Edition\"] = EditionDescription,\n [\"License Type\"] = TypeDescription,\n\
\ [\"Days Left\"] = DaysLeft,\n [\"Days of Support Left\"] = SupportLeft,\n\
\ MessageDetails = Description,\n Severity = SeverityDescription\n"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Parameters
let license_editions_lookup = union isfuzzy=true (datatable(Edition:string, EditionDescription:string)[]), (_GetWatchlist("license_editions_lookup"));
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]), (_GetWatchlist("license_types_lookup"));
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 24020
Stage 3: extend
| extend Edition = extract("Edition=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: kusto:lookup
lookup kind=leftouter (license_editions_lookup) on $left.Edition, $right.Edition
Stage 5: extend
extend Type
Stage 6: kusto:lookup
lookup kind=leftouter (license_types_lookup) on $left.Type, $right.Type
Stage 7: extend (3 consecutive steps)
extend DaysLeft, SupportLeft, TenantID
Stage 8: project
project DataSource, Date, Days Left, Days of Support Left, EventId, License Edition, License Type, MessageDetails, Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"24020" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
Days Left | project |
Days of Support Left | project |
EventId | project |
License Edition | project |
License Type | project |
MessageDetails | project |
Severity | project |
OCI - Instance metadata access
#'Detects instance metadata access.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: a55b4bbe-a014-4ae9-a50d-441ba5e98b65
name: OCI - Instance metadata access
description: |
'Detects instance metadata access.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: OracleCloudInfrastructureLogsConnector
dataTypes:
- OCILogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1069
query: |
OCILogs
| where EventType contains 'vcn.flowlogs'
| where data_action_s =~ 'ACCEPT'
| where DstIpAddr == '169.254.169.254'
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
OCILogs
Stage 2: where
| where EventType contains 'vcn.flowlogs'
Stage 3: where
| where data_action_s =~ 'ACCEPT'
Stage 4: where
| where DstIpAddr == '169.254.169.254'
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstIpAddr | eq |
| field:"dest_ip" kind:eq value:"169.254.169.254" |
EventType | contains |
| field:"EventType" kind:contains value:"vcn.flowlogs" |
data_action_s | eq |
| field:"data_action_s" kind:eq value:"ACCEPT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
PROD (TM004.1) - OBJECT - Enabled, Disabled, Unlocked, or Password Reset of a Tier Level Object
#A Tier Model object has been modified to Enable, Disable, or Unlock of a Tier Level Object.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: 1da0cf44-c927-4b6f-9b43-91871b2391b3
name: 'PROD (TM004.1) - OBJECT - Enabled, Disabled, Unlocked, or Password Reset of a Tier Level Object'
description: |
A Tier Model object has been modified to Enable, Disable, or Unlock of a Tier Level Object.
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Persistence
relevantTechniques:
- T1078.002
query: |
SecurityEvent
| where EventID == 5136
and (EventData matches regex @"(?i)OU=Domain Controllers"
or EventData matches regex @"(?i)OU=Tier 0 Accounts"
or EventData matches regex @"(?i)OU=Tier 1 Accounts"
or EventData matches regex @"(?i)OU=Tier 2 Accounts"
or EventData matches regex @"(?i)OU=Tier 0 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 1 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 0 PAW"
or EventData matches regex @"(?i)OU=Tier 1 PAW"
or EventData matches regex @"(?i)OU=Tier 2 PAW"
or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
or EventData matches regex @"(?i)OU=Tier 1 Member Servers")
and (EventData has 'AttributeValue">512'
or EventData has 'AttributeLDAPDisplayName">userAccountControl'
or EventData has 'AttributeLDAPDisplayName">lockoutTime'
or EventData has 'AttributeLDAPDisplayName">pwdLastSet')
and (EventData has 'AttributeValue">512'
or EventData has 'AttributeValue">514'
or EventData has 'AttributeValue">0'
or EventData has 'AttributeValue">4130'
or EventData has 'AttributeValue">4128')
and EventData has 'OperationType">%%14674'
| extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
| extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, ObjectTier_e)
| extend ObjectTier = iff(isempty(ObjectTier),
iff(EventData matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"),
ObjectTier)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = iff(EventData has 'AttributeValue">512' or EventData has 'AttributeValue">4128', "Enabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">514' or EventData has 'AttributeValue">4130', "Disabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "pwdLastSet", "Password Reset", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "lockoutTime", "Account Unlocked", tostring(AttributeValue))
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, AttributeValue
, ActivityType
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ObjectName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
customDetails:
ObjectName: ObjectName
ObjectTier: ObjectTier
ObjectClass: ObjectClass
AttributeValue: AttributeValue
ActivityType: ActivityType
Account: Account
Domain: Domain
Computer: Computer
Channel: Channel
EventID: EventID
EventData: EventData
eventGroupingSettings:
aggregationKind: AlertPerResult
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: '(TM004.1) A {{ObjectTier}} {{ObjectClass}} was {{AttributeValue}}'
alertDescriptionFormat: '{{ObjectTier}} {{ObjectName}} was {{AttributeValue}}.'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
and (EventData matches regex @"(?i)OU=Domain Controllers"
or EventData matches regex @"(?i)OU=Tier 0 Accounts"
or EventData matches regex @"(?i)OU=Tier 1 Accounts"
or EventData matches regex @"(?i)OU=Tier 2 Accounts"
or EventData matches regex @"(?i)OU=Tier 0 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 1 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
or EventData matches regex @"(?i)OU=Tier 0 PAW"
or EventData matches regex @"(?i)OU=Tier 1 PAW"
or EventData matches regex @"(?i)OU=Tier 2 PAW"
or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
or EventData matches regex @"(?i)OU=Tier 1 Member Servers")
and (EventData has 'AttributeValue">512'
or EventData has 'AttributeLDAPDisplayName">userAccountControl'
or EventData has 'AttributeLDAPDisplayName">lockoutTime'
or EventData has 'AttributeLDAPDisplayName">pwdLastSet')
and (EventData has 'AttributeValue">512'
or EventData has 'AttributeValue">514'
or EventData has 'AttributeValue">0'
or EventData has 'AttributeValue">4130'
or EventData has 'AttributeValue">4128')
and EventData has 'OperationType">%%14674'
Stage 3: extend (14 consecutive steps)
| extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
| extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, ObjectTier_e)
| extend ObjectTier = iff(isempty(ObjectTier),
iff(EventData matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"),
ObjectTier)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = iff(EventData has 'AttributeValue">512' or EventData has 'AttributeValue">4128', "Enabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">514' or EventData has 'AttributeValue">4130', "Disabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "pwdLastSet", "Password Reset", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "lockoutTime", "Account Unlocked", tostring(AttributeValue))
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
Stage 4: project
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, AttributeValue
, ActivityType
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match |
EventData | regex_match |
| field:"EventData" kind:regex_match |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
AttributeValue | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
ObjectClass | project |
ObjectName | project |
ObjectTier | project |
TimeGenerated | project |
PulseConnectSecure - Potential Brute Force Attempts
#'This query identifies evidence of potential brute force attack by looking at multiple failed attempts to log into the VPN server'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 34663177-8abf-4db1-b0a4-5683ab273f44
name: PulseConnectSecure - Potential Brute Force Attempts
description: |
'This query identifies evidence of potential brute force attack by looking at multiple failed attempts to log into the VPN server'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 20;
PulseConnectSecure
| where Messages contains "Login failed"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by User, Source_IP
| where count_ > threshold
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Source_IP
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 20;
Stage 1: source
PulseConnectSecure
Stage 2: where
| where Messages contains "Login failed"
Stage 3: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by User, Source_IP
Stage 4: where
| where count_ > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Messages | contains |
| field:"Messages" kind:contains value:"Login failed" |
count_ | gt |
| field:"count_" kind:gt value:"20" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
Source_IP | summarize |
StartTime | summarize |
User | summarize |
Suspicious link sharing pattern
#'Alerts in links that have been shared across multiple Zoom chat channels by the same user in a short space if time. Adjust the threshold figure to change the number of channels a message needs to be posted in before an alert is raised.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 1218175f-c534-421c-8070-5dcaabf28067
name: Suspicious link sharing pattern
description: |
'Alerts in links that have been shared across multiple Zoom chat channels by the same user in a short space if time.
Adjust the threshold figure to change the number of channels a message needs to be posted in before an alert is raised.'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Reconnaissance
relevantTechniques:
- T1598
query: |
let threshold = 3;
ZoomLogs
| where Event =~ "chat_message.sent"
| extend Channel = tostring(parse_json(ChatEvents).Channel)
| extend Message = tostring(parse_json(ChatEvents).Message)
| where Message matches regex "http(s?):\\/\\/"
| summarize Channels = makeset(Channel), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Message, User, UserId
| extend ChannelCount = arraylength(Channels)
| where ChannelCount > threshold
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let threshold = 3;
Stage 1: source
ZoomLogs
Stage 2: where
| where Event =~ "chat_message.sent"
Stage 3: extend
| extend Channel = tostring(parse_json(ChatEvents).Channel)
Stage 4: extend
| extend Message = tostring(parse_json(ChatEvents).Message)
Stage 5: where
| where Message matches regex "http(s?):\\/\\/"
Stage 6: summarize
| summarize Channels = makeset(Channel), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Message, User, UserId
Stage 7: extend
| extend ChannelCount = arraylength(Channels)
Stage 8: where
| where ChannelCount > threshold
Stage 9: extend
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ChannelCount | gt |
| field:"ChannelCount" kind:gt value:"3" |
Event | eq |
| field:"Event" kind:eq value:"chat_message.sent" |
Message | regex_match |
| field:"Message" kind:regex_match value:"http(s?):\/\/" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Channels | summarize |
EndTime | summarize |
Message | summarize |
StartTime | summarize |
User | summarize |
UserId | summarize |
ChannelCount | extend |
AccountName | extend |
AccountUPNSuffix | extend |
TI Map IP Entity to SigninLogs
#This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: f2eb15bd-8a88-4b24-9281-e133edfba315
name: TI Map IP Entity to SigninLogs
description: |
'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Signins = materialize(union isfuzzy=true
(SigninLogs
| where TimeGenerated >= ago(dt_lookBack)),
(AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)));
let SigninIPs = Signins | summarize make_list(IPAddress);
let TI = materialize(ThreatIntelligenceIndicator
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
| where TimeGenerated >= ago(ioc_lookBack)
| extend TI_ipEntity = coalesce(NetworkIP, EmailSourceIpAddress, NetworkDestinationIP, NetworkSourceIP)
| where TI_ipEntity in (SigninIPs)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;");
TI
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (Signins) on $left.TI_ipEntity == $right.IPAddress
| project-rename SigninLogs_TimeGenerated = TimeGenerated
| where SigninLogs_TimeGenerated < ExpirationDateTime
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails), StatusReason = tostring(Status.failureReason)
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, IPAddress
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, StatusReason, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
| extend timestamp = SigninLogs_TimeGenerated, Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.8
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let TI is inlined into the numbered stages below.
Let Binding: Signins
let Signins = materialize(union isfuzzy=true
(SigninLogs
| where TimeGenerated >= ago(dt_lookBack)),
(AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)));
Let Binding: SigninIPs
let SigninIPs = Signins | summarize make_list(IPAddress);
Stages 1 to 8 define let TI as the rule's main pipeline source. Stages 9 to 15 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: extend
| extend TI_ipEntity = coalesce(NetworkIP, EmailSourceIpAddress, NetworkDestinationIP, NetworkSourceIP)
Stage 5: where
| where TI_ipEntity in (SigninIPs)
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 9: join
join kind=innerunique (Signins) on $left.TI_ipEntity, $right.IPAddress
Stage 10: project-rename
project-rename
Stage 11: where
where SigninLogs_TimeGenerated < ExpirationDateTime
Stage 12: extend
extend StatusCode, StatusDetails, StatusReason
Stage 13: summarize
summarize SigninLogs_TimeGenerated by IndicatorId, IPAddress
Stage 14: project
project ActivityGroupNames, AppDisplayName, ConfidenceScore, Description, EmailSourceIpAddress, ExpirationDateTime, IPAddress, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SigninLogs_TimeGenerated, StatusCode, StatusDetails, StatusReason, TI_ipEntity, ThreatType, Type, Url, UserPrincipalName
Stage 15: extend
extend Name, UPNSuffix, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null | |
TI_ipEntity | in |
| field:"TI_ipEntity" kind:in value:"SigninIPs" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AppDisplayName | project |
ConfidenceScore | project |
Description | project |
EmailSourceIpAddress | project |
ExpirationDateTime | project |
IPAddress | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
SigninLogs_TimeGenerated | project |
StatusCode | project |
StatusDetails | project |
StatusReason | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
UserPrincipalName | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
Uniqkey - Data export activity
#Detects export of credential or vault data from the Uniqkey organization. Exports are legitimate for backup and migration scenarios, but they move secrets outside the platform's protection and are the primary exfiltration vector for a password manager, so each occurrence is raised as its own alert for review. If scheduled exports by known administrative or service accounts create noise, exclude those actors in the query.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: f3125212-6871-4adc-8335-98c9a920639b
name: Uniqkey - Data export activity
version: 1.0.0
kind: Scheduled
description: |-
Detects export of credential or vault data from the Uniqkey organization. Exports are legitimate for backup and migration scenarios, but they move secrets outside the platform's protection and are the primary exfiltration vector for a password manager, so each occurrence is raised as its own alert for review. If scheduled exports by known administrative or service accounts create noise, exclude those actors in the query.
severity: Medium
requiredDataConnectors:
- connectorId: UniqkeyEventsConnector
dataTypes:
- UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1555
query: |-
UniqkeyEvents_CL
| where Category == "data_export"
| project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorEmail
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Stage 1: source
UniqkeyEvents_CL
Stage 2: where
| where Category == "data_export"
Stage 3: project
| project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"data_export" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActionId | project |
ActorEmail | project |
ActorType | project |
ClientSystem | project |
SrcIpAddr | project |
TargetName | project |
TargetType | project |
TimeGenerated | project |
VMware ESXi - Low temp directory space
#'This rule is triggered when temp directory space is detected.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2ee727f7-b7c2-4034-b6c9-d245d5a29343
name: VMware ESXi - Low temp directory space
description: |
'This rule is triggered when temp directory space is detected.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
query: |
let threshold = 10;
VMwareESXi
| where SyslogMessage has_all ('Temp directory', 'free space')
| extend sp = toreal(extract(@'free space is:\s(\d+)', 1, SyslogMessage)) / 1000000000
| where sp < threshold
| extend h = 'Hypervisor'
| extend HostCustomEntity = h
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
VMwareESXi
Stage 2: where
| where SyslogMessage has_all ('Temp directory', 'free space')
Stage 3: extend
| extend sp = toreal(extract(@'free space is:\s(\d+)', 1, SyslogMessage)) / 1000000000
Stage 4: where
| where sp < threshold
Stage 5: extend
| extend h = 'Hypervisor'
Stage 6: extend
| extend HostCustomEntity = h
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
sp | lt |
| field:"sp" kind:lt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
sp | extend |
h | extend |
HostCustomEntity | extend |