Detection rules › Kusto
Kusto rules: 2q
Apache - Multiple client errors from single IP
#'Detects multiple client errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 15f5a956-3af9-11ec-8d3d-0242ac130003
name: Apache - Multiple client errors from single IP
description: |
'Detects multiple client errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let threshold = 100;
ApacheHTTPServer
| where HttpStatusCode >= 400 and HttpStatusCode <= 499
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleClientErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
ApacheHTTPServer
Stage 2: where
| where HttpStatusCode >= 400 and HttpStatusCode <= 499
Stage 3: summarize
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleClientErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleClientErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
Azure DevOps Administrator Group Monitoring
#'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 89e6adbd-612c-4fbe-bc3d-32f81baf3b6c
name: Azure DevOps Administrator Group Monitoring
description: |
'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 4h
queryPeriod: 4h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
// Change to true to monitor for Project Administrator adds to *any* project
let MonitorAllProjects = false;
// If MonitorAllProjects is false, trigger only on Project Administrator add for the following projects
let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);
ADOAuditLogs
| where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"
| where Details has 'Administrators'
| where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')
| parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName
| extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId
| extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)
| where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'
| project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Adder
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ActorIpAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let MonitorAllProjects = false;
let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);
Stage 1: source
ADOAuditLogs
Stage 2: where
| where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"
Stage 3: where
| where Details has 'Administrators'
Stage 4: where
| where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')
Stage 5: parse
| parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName
Stage 6: extend
| extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId
Stage 7: extend
| extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)
Stage 8: where
| where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'
Stage 9: project
| project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details
Stage 10: extend
| extend timestamp = TimeGenerated
Stage 11: extend
| extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Area | eq |
| field:"Area" kind:eq value:"Group" |
Details | ends_with |
| field:"Details" kind:ends_with |
Details | match |
| field:"Details" kind:match |
EntityName | in |
| field:"EntityName" kind:in |
Level | eq |
| field:"Level" kind:eq value:"Organization" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Group.UpdateGroupMembership.Add" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorAuthType | project |
ActorIpAddress | project |
ActorUserAgent | project |
AddedIdentity | project |
AddedIdentityId | project |
Adder | project |
AlertDetails | project |
EntityName | project |
GroupName | project |
Level | project |
RawDetails | project |
Severity | project |
TimeGenerated | project |
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |
BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System
#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 - VM Admin Login Role on Tier Zero System
Rule Body
id: f1334bfc-7cdd-4655-b8de-e1398158f4b5
name: BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System
description: BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System
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 == "AzureT0VMAdminLogin"
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 == "AzureT0VMAdminLogin"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0VMAdminLogin" |
Cisco Duo - Unexpected authentication factor
#'Detects when unexpected authentication factor used.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 16c91a2c-17ad-4985-a9ad-4a4f1cb11830
name: Cisco Duo - Unexpected authentication factor
description: |
'Detects when unexpected authentication factor used.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoDuoAuthConnectorDefinition
dataTypes:
- DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let allowed_auth_f = dynamic(['duo_push', 'duo_mobile_passcode']);
DuoAuthentication_CL
| where Result =~ 'success'
| where Factor !in~ (allowed_auth_f)
| extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let allowed_auth_f = dynamic(['duo_push', 'duo_mobile_passcode']);
Stage 1: source
DuoAuthentication_CL
Stage 2: where
| where Result =~ 'success'
Stage 3: where
| where Factor !in~ (allowed_auth_f)
Stage 4: extend
| extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Result | eq |
| field:"Result" kind:eq value:"success" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Factor | in | duo_mobile_passcode, duo_push | excludes:Factor field:"Factor" value:"duo_mobile_passcode" field:"Factor" value:"duo_push" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
Cross-tenant Access Settings Organization Outbound Direct Settings Changed
#Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when Organization Outbound Direct Settings are changed for "Users & Groups" and for "Applications".
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- Cross-tenant Access Settings Organization Added (Kusto)
- Cross-tenant Access Settings Organization Deleted (Kusto)
- Cross-tenant Access Settings Organization Inbound Collaboration Settings Changed (Kusto)
- Cross-tenant Access Settings Organization Inbound Direct Settings Changed (Kusto)
- Cross-tenant Access Settings Organization Outbound Collaboration Settings Changed (Kusto)
Rule Body
id: 0101e08d-99cd-4a97-a9e0-27649c4369ad
name: Cross-tenant Access Settings Organization Outbound Direct Settings Changed
description: |
'Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when Organization Outbound Direct Settings are changed for "Users & Groups" and for "Applications".'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Persistence
- Discovery
relevantTechniques:
- T1078.004
- T1136.003
- T1087.004
query: |
// In User & Groups and in Applications, the following "AccessType" values in columns PremodifiedOutboundSettings and ModifiedOutboundSettings are interpreted accordingly
// When Access Type in premodified outbound settings value was 1 that means that the initial access was allowed. When Access Type in premodified outbound settings value was 2 that means that the initial access was blocked.
// When Access Type in modified outbound settings value is 1 that means that now access is allowed. When Access Type in modified outbound settings value is 2 that means that now access is blocked.
AuditLogs
| where OperationName has "Update a partner cross-tenant access setting"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Policy"
| extend Properties = TargetResource.modifiedProperties
)
| mv-apply Property = Properties on
(
where Property.displayName =~ "b2bDirectConnectOutbound"
| extend PremodifiedOutboundSettings = trim('"',tostring(Property.oldValue)),
ModifiedOutboundSettings = trim(@'"',tostring(Property.newValue))
)
| where PremodifiedOutboundSettings != ModifiedOutboundSettings
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- 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.1.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName has "Update a partner cross-tenant access setting"
Stage 3: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Policy"
| extend Properties = TargetResource.modifiedProperties
)
Stage 4: kusto:mv-apply
| mv-apply Property = Properties on
(
where Property.displayName =~ "b2bDirectConnectOutbound"
| extend PremodifiedOutboundSettings = trim('"',tostring(Property.oldValue)),
ModifiedOutboundSettings = trim(@'"',tostring(Property.newValue))
)
Stage 5: where
| where PremodifiedOutboundSettings != ModifiedOutboundSettings
Stage 6: extend (6 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | match |
| field:"OperationName" kind:match value:"Update a partner cross-tenant access setting" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"b2bDirectConnectOutbound" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"Policy" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
Cyble Vision Alerts Assets
#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.
'Scheduled rule that creates incidents for asset alerts using saved parser Alerts_assets. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 0012714c-c595-4dcd-8949-4a5c1d49aaa8
name: Cyble Vision Alerts Assets
description: |
'Scheduled rule that creates incidents for asset alerts using saved parser Alerts_assets. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1592
query: |
Alerts_assets
| where Service == "assets"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
AA_KeywordID: AA_KeywordID
AA_LastDetected: AA_LastDetected
AA_Value: AA_Value
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: AA_Value
- entityType: Url
fieldMappings:
- identifier: Url
columnName: AA_Value
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_assets
Stage 2: where
| where Service == "assets"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"assets" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - Social and Public Exposure - Social Media Threats Activity 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 rule detects medium-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees. These threats can result in reputational damage, phishing, or social engineering attacks. Immediate investigation and takedown are recommended to minimize risk."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access | |
| Impact | |
| Impact |
Rule Body
id: b8149f2f-54da-4f7b-98e1-c01ca47e1e55
name: CYFIRMA - Social and Public Exposure - Social Media Threats Activity Detected Rule
description: |
"This rule detects medium-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees.
These threats can result in reputational damage, phishing, or social engineering attacks.
Immediate investigation and takedown are recommended to minimize risk."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaSPESocialThreatAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- ResourceDevelopment
- Reconnaissance
- InitialAccess
- Impact
relevantTechniques:
- T1585.001
- T1593
- T1566
- T1582
- T1491
query: |
// Medium severity - Social and Public Exposure - Social Media Threats Activity Detected
let timeFrame = 5m;
CyfirmaSPESocialThreatAlerts_CL
| where severity == 'High' 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=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
customDetails:
AssetType: AssetType
AssetValue: AssetValue
RiskScore: RiskScore
Source: Source
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Alert: Social Media Threat Activity 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
CyfirmaSPESocialThreatAlerts_CL
Stage 2: where
| where severity == 'High' 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=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
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 |
TimeGenerated | project |
UID | project |
full_access_as_app Granted To Application
#This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent. This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data by being added to a compromised application. The application granted this permission should be reviewed to ensure that it is absolutely necessary for the applications function. Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Consent to application |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 54e22fed-0ec6-4fb2-8312-2a3809a93f63
name: full_access_as_app Granted To Application
description: |
'This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent.
This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data by being added to a compromised application. The application granted this permission should be reviewed to ensure that it is absolutely necessary for the applications function.
Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1550.001
query: |
AuditLogs
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Consent to application"
| where TargetResources has "full_access_as_app"
| mv-expand TargetResources
| extend OAuthAppName = TargetResources.displayName
| extend ModifiedProperties = TargetResources.modifiedProperties
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "ConsentContext.isAdminConsent"
| extend AdminConsent = tostring(Property.newValue)
)
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "ConsentAction.Permissions"
| extend Permissions = tostring(Property.newValue)
)
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend AppId = tostring(Property.newValue)
)
| mv-apply Property = AdditionalDetails on
(
where Property.key =~ "User-Agent"
| extend InitiatingUserAgent = replace('"', '', tostring(Property.value))
)
| project-away Property
| parse Permissions with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "," *
| where GrantScope1 =~ "full_access_as_app"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| project-reorder TimeGenerated, OAuthAppName, AppId, AdminConsent, Permissions, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserAgent, GrantScope1, GrantConsentType
| extend GrantInitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName))
| extend Name = split(InitiatingUserPrincipalName, "@")[0], UPNSuffix = split(InitiatingUserPrincipalName, "@")[1]
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
customDetails:
OAuthApplication: OAuthAppName
OAuthAppId: AppId
UserAgent: InitiatingUserAgent
alertDetailsOverride:
alertDisplayNameFormat: User or App {{GrantInitiatedBy}} granted full_access_as_app to {{OAuthAppName}}
alertDescriptionFormat: |
This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent.
This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data
by being added to a compromised application. The application granted this permission should be reviewed to ensure that it
is absolutely necessary for the applications function.
In this case {{GrantInitiatedBy}} granted full_access_as_app to {{OAuthAppName}} from {{InitiatingIpAddress}}
Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where LoggedByService =~ "Core Directory"
Stage 3: where
| where Category =~ "ApplicationManagement"
Stage 4: where
| where OperationName =~ "Consent to application"
Stage 5: where
| where TargetResources has "full_access_as_app"
Stage 6: mv-expand
| mv-expand TargetResources
Stage 7: extend
| extend OAuthAppName = TargetResources.displayName
Stage 8: extend
| extend ModifiedProperties = TargetResources.modifiedProperties
Stage 9: kusto:mv-apply
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "ConsentContext.isAdminConsent"
| extend AdminConsent = tostring(Property.newValue)
)
Stage 10: kusto:mv-apply
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "ConsentAction.Permissions"
| extend Permissions = tostring(Property.newValue)
)
Stage 11: kusto:mv-apply
| mv-apply Property = ModifiedProperties on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend AppId = tostring(Property.newValue)
)
Stage 12: kusto:mv-apply
| mv-apply Property = AdditionalDetails on
(
where Property.key =~ "User-Agent"
| extend InitiatingUserAgent = replace('"', '', tostring(Property.value))
)
Stage 13: project-away
| project-away Property
Stage 14: parse
| parse Permissions with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "," *
Stage 15: where
| where GrantScope1 =~ "full_access_as_app"
Stage 16: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 17: project-reorder
| project-reorder TimeGenerated, OAuthAppName, AppId, AdminConsent, Permissions, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserAgent, GrantScope1, GrantConsentType
Stage 18: extend
| extend GrantInitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName))
Stage 19: extend
| extend Name = split(InitiatingUserPrincipalName, "@")[0], UPNSuffix = split(InitiatingUserPrincipalName, "@")[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"ApplicationManagement" |
GrantScope1 | eq |
| field:"GrantScope1" kind:eq value:"full_access_as_app" |
LoggedByService | eq |
| field:"azure_ad::logged_by_service" kind:eq value:"Core Directory" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Consent to application" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq |
Property.key | eq |
| field:"Property.key" kind:eq value:"User-Agent" |
TargetResources | match |
| field:"azure_ad::target_resources" kind:match value:"full_access_as_app" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
OAuthAppName | extend |
ModifiedProperties | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
GrantInitiatedBy | extend |
Name | extend |
UPNSuffix | extend |
GTI - Data Leak Alert Detected
#Triggers an incident when a GTI Relevance System Alert of type data_leak is ingested. Data Leak alerts indicate that sensitive organisational data (credentials, PII, intellectual property, source code, databases, etc.) has been found exposed on the dark web, paste sites, or underground forums and matches your organisation profile. Each unique Alert ID is grouped into a single incident.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Collection | |
| Exfiltration | |
| Impact |
Rule Body
id: d4e5f6a7-b8c9-0123-defa-234567890124
name: GTI - Data Leak Alert Detected
description: |
Triggers an incident when a GTI Relevance System Alert of type data_leak is ingested.
Data Leak alerts indicate that sensitive organisational data (credentials, PII, intellectual property,
source code, databases, etc.) has been found exposed on the dark web, paste sites, or underground
forums and matches your organisation profile.
Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
dataTypes:
- RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- Impact
- CredentialAccess
- Collection
relevantTechniques:
- T1567
- T1530
- T1552
- T1485
query: |
GTIRelevanceSystemAlerts
| where DetailType == "data_leak"
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
LeakSeverity = DataLeakSeverity,
DiscoveryDocuments = DataLeakDiscoveryDocumentIds,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
AuditCreateTime,
AuditUpdateTime
alertDetailsOverride:
alertDisplayNameFormat: 'GTI Data Leak: {{AlertName}}'
alertDescriptionFormat: |
Leak Severity: {{LeakSeverity}} | Priority: {{PriorityLevel}}
Summary: {{AiSummary}}
customDetails:
AlertId: AlertId
AlertState: AlertState
LeakSeverity: LeakSeverity
PriorityLevel: PriorityLevel
SeverityLevel: SeverityLevel
RelevanceLevel: RelevanceLevel
FindingCount: FindingCount
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P5D
matchingMethod: Selected
groupByCustomDetails:
- AlertId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIRelevanceSystemAlerts
Stage 2: where
| where DetailType == "data_leak"
Stage 3: project
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
LeakSeverity = DataLeakSeverity,
DiscoveryDocuments = DataLeakDiscoveryDocumentIds,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
AuditCreateTime,
AuditUpdateTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DetailType | eq |
| field:"DetailType" kind:eq value:"data_leak" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AiSummary | project |
AlertId | project |
AlertName | project |
AlertState | project |
AuditCreateTime | project |
AuditUpdateTime | project |
CommonThemes | project |
DiscoveryDocuments | project |
DistinctThemes | project |
FindingCount | project |
LeakSeverity | project |
PriorityLevel | project |
PriorityReasoning | project |
RelevanceConfidence | project |
RelevanceLevel | project |
RelevanceReasoning | project |
SeverityConfidence | project |
SeverityLevel | project |
SeverityReasoning | project |
TimeGenerated | project |
Hunt for devices doing first RDP session
#This hunting query can help you find devices doing an RDP connection for the first time in 30 days. While this can be normal behavior, it might be interesting to look at why this device is suddenly doing an RDP connection.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceNetworkEvents action ConnectionSuccess: Connection succeeded |
Rule Body
let historic_rdp_devices = toscalar(
DeviceNetworkEvents
| where Timestamp > ago (30d)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| summarize make_set(DeviceId)
);
DeviceNetworkEvents
| where Timestamp > ago(1h)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| where DeviceId !in (historic_rdp_devices)
let historic_rdp_devices = toscalar(
DeviceNetworkEvents
| where TimeGenerated > ago (30d)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| summarize make_set(DeviceId)
);
DeviceNetworkEvents
| where TimeGenerated > ago(1h)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| where DeviceId !in (historic_rdp_devices)
Stages and Predicates
Let Binding: historic_rdp_devices
let historic_rdp_devices = toscalar(
DeviceNetworkEvents
| where Timestamp > ago (30d)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| summarize make_set(DeviceId)
);
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where Timestamp > ago(1h)
Stage 3: where
| where ActionType == "ConnectionSuccess"
Stage 4: where
| where RemotePort == 3389
Stage 5: where
| where DeviceId !in (historic_rdp_devices)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ConnectionSuccess" |
RemotePort | eq |
| field:"DestinationPort" kind:eq value:"3389" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DeviceId | eq | historic_rdp_devices | excludes:DeviceId field:"DeviceId" value:"historic_rdp_devices" |
Lumen TI IPAddress in IdentityLogonEvents
#This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in Azure AD sign-in logs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: a7cd18cd-1503-47ec-8dca-65d750540637
name: Lumen TI IPAddress in IdentityLogonEvents
displayName: Lumen TI IPAddress in IdentityLogonEvents
description: |
This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in Azure AD sign-in logs.
severity: Medium
requiredDataConnectors:
- connectorId: LumenThreatFeedConnector
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceUploadIndicatorsAPI
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- AADServicePrincipalSignInLogs
- AADManagedIdentitySignInLogs
- ADFSSignInLogs
queryFrequency: 4h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: true
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1d; // Data lookback for AAD sign-in logs
let ioc_lookBack = 14d; // TI lookback
let IP_Indicators =
ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| where IsActive == true and ValidUntil > now()
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where SourceSystem == 'Lumen'
| where ObservableKey == 'ipv4-addr:value'
| extend TI_ipEntity = ObservableValue
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.';
let SignInUnion =
union isfuzzy=true
(
AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
LogonType = column_ifexists('ClientAppUsed',''),
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
AADServicePrincipalSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
LogonType = "ServicePrincipal",
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
AADManagedIdentitySignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
Application = ResourceDisplayName,
LogonType = "ManagedIdentity",
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
ADFSSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
Application = coalesce(AppDisplayName, ResourceDisplayName),
LogonType = coalesce(Requirement, AuthenticationRequirement),
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
);
IP_Indicators
| join kind=innerunique (SignInUnion) on $left.TI_ipEntity == $right.ILE_ipEntity
| where IdentityLogonEvents_TimeGenerated < ValidUntil
| summarize arg_max(IdentityLogonEvents_TimeGenerated, *), StartTime = min(IdentityLogonEvents_TimeGenerated), EndTime = max(IdentityLogonEvents_TimeGenerated) by Id, ILE_ipEntity
| project timestamp = EndTime, StartTime, EndTime, AccountDisplayName, IPAddress, Application, LogonType, Id, Tags, ValidUntil, Confidence, TI_ipEntity, ILE_ipEntity, Type
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ILE_ipEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1d;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Let Binding: SignInUnion
let SignInUnion = union isfuzzy=true
(
AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
LogonType = column_ifexists('ClientAppUsed',''),
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
AADServicePrincipalSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
LogonType = "ServicePrincipal",
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
AADManagedIdentitySignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
Application = ResourceDisplayName,
LogonType = "ManagedIdentity",
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
),
(
ADFSSignInLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend
ILE_ipEntity = IPAddress,
AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
Application = coalesce(AppDisplayName, ResourceDisplayName),
LogonType = coalesce(Requirement, AuthenticationRequirement),
IdentityLogonEvents_TimeGenerated = TimeGenerated
| project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
);
Stages 1 to 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 to 12 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: where
| where IsActive == true and ValidUntil > now()
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
Stage 5: where
| where SourceSystem == 'Lumen'
Stage 6: where
| where ObservableKey == 'ipv4-addr:value'
Stage 7: extend
| extend TI_ipEntity = ObservableValue
Stage 8: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.'
Stage 9: join
join kind=innerunique (SignInUnion) on $left.TI_ipEntity, $right.ILE_ipEntity
Stage 10: where
where IdentityLogonEvents_TimeGenerated < ValidUntil
Stage 11: summarize
summarize EndTime, StartTime by Id, ILE_ipEntity
Stage 12: project
project AccountDisplayName, Application, Confidence, EndTime, ILE_ipEntity, IPAddress, Id, LogonType, StartTime, TI_ipEntity, Tags, Type, ValidUntil, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"ipv4-addr:value" |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"Lumen" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDisplayName | project |
Application | project |
Confidence | project |
EndTime | project |
ILE_ipEntity | project |
IPAddress | project |
Id | project |
LogonType | project |
StartTime | project |
TI_ipEntity | project |
Tags | project |
Type | project |
ValidUntil | project |
timestamp | project |
McAfee ePO - Error sending alert
#Detects when error sending alert occurs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| McAfee-ePolicy-Orchestrator | Event ID 1062: Error sending alert |
Rule Body
id: 1e3bcd0f-10b2-4fbd-854f-1c6f33acc36a
name: McAfee ePO - Error sending alert
description: |
'Detects when error sending alert occurs.'
severity: Medium
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
- T1070
query: |
McAfeeEPOEvent
| where EventId == '1062'
| extend IPCustomEntity = DvcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
McAfeeEPOEvent
Stage 2: where
| where EventId == '1062'
Stage 3: extend
| extend IPCustomEntity = DvcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventId | eq |
| field:"EventId" kind:eq value:"1062" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Mimecast Secure Email Gateway - Internal Email Protect
#'Detects threats from internal email threat protection.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Lateral Movement |
Rule Body
id: d3bd7640-3600-49f9-8d10-6fe312e68b4f
name: Mimecast Secure Email Gateway - Internal Email Protect
description: |
'Detects threats from internal email threat protection.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MimecastSEGAPI
dataTypes:
- MimecastCG
enabled: true
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- Persistence
- Exfiltration
relevantTechniques:
- T1534
- T1546
query: |
MimecastCG
| where Type == "email_iep"
| extend SenderEnvelope = ['Sender Envelope'] , MessageId = ['Message ID']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: SenderEnvelope
- identifier: Recipient
columnName: Recipients
- identifier: InternetMessageId
columnName: MessageId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastCG
Stage 2: where
| where Type == "email_iep"
Stage 3: extend
| extend SenderEnvelope = ['Sender Envelope'] , MessageId = ['Message ID']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Type | eq |
| field:"Type" kind:eq value:"email_iep" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MessageId | extend |
SenderEnvelope | extend |
Mimecast Secure Email Gateway - Internal Email Protect
#Detects threats from internal email threat protection
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Lateral Movement |
Rule Body
id: 5b66d176-e344-4abf-b915-e5f09a6430ef
name: Mimecast Secure Email Gateway - Internal Email Protect
description: Detects threats from internal email threat protection
severity: High
requiredDataConnectors:
- connectorId: MimecastSIEMAPI
dataTypes:
- MimecastSIEM_CL
enabled: true
query: MimecastSIEM_CL| where mimecastEventId_s=="mail_ttp_iep"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- LateralMovement
- Persistence
- Exfiltration
relevantTechniques:
- T1534
- T1546
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
customDetails:
Subject: Subject_s
Route: Route_s
UrlCategory: UrlCategory_s
ScanResultInfo: ScanResultInfo_s
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: Sender_s
- identifier: Recipient
columnName: Recipient_s
- identifier: InternetMessageId
columnName: MsgId_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL_s
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastSIEM_CL
Stage 2: where
| where mimecastEventId_s=="mail_ttp_iep"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
mimecastEventId_s | eq |
| field:"mimecastEventId_s" kind:eq value:"mail_ttp_iep" |
PROD (TM003.1) - OBJECT - Moved or Recovered a Tier Level Account
#Moving or Recovering of a Tier Model object.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 1d858e12-777d-4156-b0af-b5ef410739e6
name: 'PROD (TM003.1) - OBJECT - Moved or Recovered a Tier Level Account'
description: |
Moving or Recovering of a Tier Model object.
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Persistence
relevantTechniques:
- T1078.002
query: |
SecurityEvent
| where (EventID == 5138
or EventID == 5139)
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 Groups"
or EventData matches regex @"(?i)OU=Tier 1 Groups"
or EventData matches regex @"(?i)OU=Tier 2 Groups"
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")
| extend OldObject_e = extract("<Data Name=\"OldObjectDN\">([^<]+)</Data>", 1, EventData)
| extend OldObject = extract("(?i)CN=([^,]+)", 1, OldObject_e)
| extend OldObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, OldObject_e)
| extend OldObjectTier = iff(isempty(OldObjectTier), "Outside Tier Model", OldObjectTier)
| extend NewObject_e = extract("<Data Name=\"NewObjectDN\">([^<]+)</Data>", 1, EventData)
| extend NewObject = extract("(?i)CN=([^,]+)", 1, NewObject_e)
| extend NewObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, NewObject_e)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
| extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
| extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| extend NewObjectTier = iff(isempty(NewObjectTier),
iff(NewObject_e matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"),
NewObjectTier)
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| where OldObject != NewObjectTier
| project TimeGenerated
, ObjectName = OldObject
, NewObjectTier
, NewObjectOU = NewObject_e
, ObjectClass
, PreviousTier = OldObjectTier
, PreviousOU = OldObject_e
, 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
NewObjectTier: NewObjectTier
NewObjectOU: NewObjectOU
ObjectClass: ObjectClass
PreviousTier: PreviousTier
PreviousOU: PreviousOU
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: '(TM003.1) A {{NewObjectTier}} {{ObjectClass}} was {{ActivityType}}'
alertDescriptionFormat: '{{ObjectName}} was moved from {{PreviousOU}} to the following OU {{NewObjectOU}}.'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where (EventID == 5138
or EventID == 5139)
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 Groups"
or EventData matches regex @"(?i)OU=Tier 1 Groups"
or EventData matches regex @"(?i)OU=Tier 2 Groups"
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")
Stage 3: extend (14 consecutive steps)
| extend OldObject_e = extract("<Data Name=\"OldObjectDN\">([^<]+)</Data>", 1, EventData)
| extend OldObject = extract("(?i)CN=([^,]+)", 1, OldObject_e)
| extend OldObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, OldObject_e)
| extend OldObjectTier = iff(isempty(OldObjectTier), "Outside Tier Model", OldObjectTier)
| extend NewObject_e = extract("<Data Name=\"NewObjectDN\">([^<]+)</Data>", 1, EventData)
| extend NewObject = extract("(?i)CN=([^,]+)", 1, NewObject_e)
| extend NewObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, NewObject_e)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
| extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
| extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| extend NewObjectTier = iff(isempty(NewObjectTier),
iff(NewObject_e matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"),
NewObjectTier)
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
Stage 4: where
| where OldObject != NewObjectTier
Stage 5: project
| project TimeGenerated
, ObjectName = OldObject
, NewObjectTier
, NewObjectOU = NewObject_e
, ObjectClass
, PreviousTier = OldObjectTier
, PreviousOU = OldObject_e
, ActivityType
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | regex_match |
| field:"EventData" kind:regex_match |
EventID | eq |
| field:"EventID" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
NewObjectOU | project |
NewObjectTier | project |
ObjectClass | project |
ObjectName | project |
PreviousOU | project |
PreviousTier | project |
TimeGenerated | project |
Snowflake - Multiple login failures by user
#'Detects multiple login failures by user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: e05cc333-d499-430f-907c-7f28a9e4d1b5
name: Snowflake - Multiple login failures by user
description: |
'Detects multiple login failures by user.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let threshold = 10;
Snowflake
| where EventType =~ 'LOGIN'
| where LoginIsSuccess =~ 'No'
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
| where count_ > threshold
| extend AccountCustomEntity = TargetUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
Snowflake
Stage 2: where
| where EventType =~ 'LOGIN'
Stage 3: where
| where LoginIsSuccess =~ 'No'
Stage 4: summarize
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
Stage 5: where
| where count_ > threshold
Stage 6: extend
| extend AccountCustomEntity = TargetUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"LOGIN" |
LoginIsSuccess | eq |
| field:"LoginIsSuccess" kind:eq value:"No" |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetUsername | summarize |
AccountCustomEntity | extend |
TI Map IP Entity to Duo Security
#'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DuoSecurity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 4988c238-a118-442c-80bd-6c689a1b2e97
name: TI Map IP Entity to Duo Security
description: |
'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DuoSecurity.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: CiscoDuoSecurity
dataTypes:
- CiscoDuo
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
| extend NetworkSourceIP = toupper(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
// Picking up only IOC's that contain the entities we want
| where TimeGenerated >= ago(ioc_lookBack)
// As there is potentially more than 1 indicator type for matching IP, taking NetworkIP first, then others if that is empty.
// Taking the first non-empty value based on potential IOC match availability
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| join (
CiscoDuo
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(AccessDvcIpAddr)
| extend Duo_TimeGenerated = IsoTimestamp
)
on $left.TI_ipEntity == $right.AccessDvcIpAddr
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project LatestIndicatorTime, Description, ActivityGroupNames, Id, ValidUntil, Confidence, Duo_TimeGenerated,
TI_ipEntity, DstUserName, AuthFactor, EventResult, SrcAppName, EventType, TransactionId, SrcUserId, AccessDvcIpAddr, SrcGeoCity, AccessDvcLocationState, SrcGeoCountry
| extend timestamp = Duo_TimeGenerated, Name = tostring(split(DstUserName, '@', 0)[0]), UPNSuffix = tostring(split(DstUserName, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: DstUserName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: AccessDvcIpAddr
version: 1.0.10
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
Stage 4: extend
| extend NetworkSourceIP = toupper(ObservableValue)
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: extend
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
TI_ipEntity =isnotempty(NetworkSourceIP)NetworkSourceIPNetworkSourceIPStage 8: extend
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
TI_ipEntity =isempty(TI_ipEntity) and isnotempty(NetworkSourceIP)NetworkSourceIPTI_ipEntityStage 9: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 10: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 11: join
| join (
CiscoDuo
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(AccessDvcIpAddr)
| extend Duo_TimeGenerated = IsoTimestamp
)
on $left.TI_ipEntity == $right.AccessDvcIpAddr
Stage 12: where
where TimeGenerated >= ago(1209600s)
Stage 13: summarize
summarize LatestIndicatorTime by Id, ObservableValue
Stage 14: where
where (isempty(ValidUntil) or ValidUntil > now()) and IsActive == true
Stage 15: extend
extend Description
Stage 16: extend
extend ActivityGroupNames
Stage 17: project
project AccessDvcIpAddr, AccessDvcLocationState, ActivityGroupNames, AuthFactor, Confidence, Description, DstUserName, Duo_TimeGenerated, EventResult, EventType, Id, LatestIndicatorTime, SrcAppName, SrcGeoCity, SrcGeoCountry, SrcUserId, TI_ipEntity, TransactionId, ValidUntil
Stage 18: extend
extend Name, UPNSuffix, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccessDvcIpAddr | is_not_null | field:"AccessDvcIpAddr" kind:is_not_null | |
IndicatorType | in |
| field:"IndicatorType" kind:in |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccessDvcIpAddr | project |
AccessDvcLocationState | project |
ActivityGroupNames | project |
AuthFactor | project |
Confidence | project |
Description | project |
DstUserName | project |
Duo_TimeGenerated | project |
EventResult | project |
EventType | project |
Id | project |
LatestIndicatorTime | project |
SrcAppName | project |
SrcGeoCity | project |
SrcGeoCountry | project |
SrcUserId | project |
TI_ipEntity | project |
TransactionId | project |
ValidUntil | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
Unusual identity creation using exchange powershell
#The query below identifies creation of unusual identity by the Europium actor to mimic Microsoft Exchange Health Manager Service account using Exchange PowerShell commands Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 0a3f4f4f-46ad-4562-acd6-f17730a5aef4
name: Unusual identity creation using exchange powershell
description: |
' The query below identifies creation of unusual identity by the Europium actor to mimic Microsoft Exchange Health Manager Service account using Exchange PowerShell commands
Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government/'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1136
tags:
- Europium
query: |
(union isfuzzy=true
(SecurityEvent
| where EventID==4688
| where CommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and CommandLine has "HealthMailbox55x2yq"
| project TimeGenerated, DeviceName = Computer, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
),
(DeviceProcessEvents
| where ProcessCommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and ProcessCommandLine has "HealthMailbox55x2yq"
| extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
)
)
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingProcessAccount
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.1.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection", "Identity" ]
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, DeviceProcessEvents
Leg 1: SecurityEvent
SecurityEvent
| where EventID==4688
| where CommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and CommandLine has "HealthMailbox55x2yq"
| project TimeGenerated, DeviceName = Computer, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
Leg 2: DeviceProcessEvents
DeviceProcessEvents
| where ProcessCommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and ProcessCommandLine has "HealthMailbox55x2yq"
| extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
Applied to the Combined Result
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDomain | extend |
AccountName | extend |
Activity | project |
CommandLine | project |
DeviceName | project |
EventID | project |
EventSourceName | project |
ProcessName | project |
ProcessNameFullPath | project |
TimeGenerated | project |
Type | project |
InitiatingProcessAccount | extend |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |