diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de34d8..355d2a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased — 2.1.0 + +- Expand every rule domain to five examples. +- Add 28 analytics rules for a total of 35 across seven domains. +- Extend AI, agent, identity, Purview, cloud, endpoint, and UEBA behavioral coverage. +- Regenerate the rule catalog and coverage matrix. + ## Unreleased — 2.0.0 - Restructure the repository around root-level analytics rules. diff --git a/README.md b/README.md index 8ae43cd..39c6b88 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,17 @@ flowchart LR ## Rule domains -| Domain | Example coverage | +| Domain | Five example behaviors | |---|---| -| AI security | Repeated prompt-injection indicators | -| Agent 365 | Risky or unauthorized agent tool use | -| Identity | Password spraying | -| Purview | AI activity correlated with mass file operations | -| Cloud | Azure resource deletion | -| Endpoint | Suspicious PowerShell execution | -| UEBA | Privileged-identity anomaly | +| AI security | Prompt injection, sensitive output, excessive tokens, system-prompt extraction, content-safety bypass | +| Agent 365 | Unauthorized tools, privilege escalation, data transfer, abnormal volume, new risky connector | +| Identity | Password spray, MFA fatigue, privileged role assignment, OAuth consent, emergency account use | +| Purview | AI/file correlation, mass downloads, sensitive sharing, label downgrade, sharing spike | +| Cloud | Resource deletion, Key Vault access, diagnostics deletion, public storage, NSG changes | +| Endpoint | PowerShell, Office child shell, LOLBin retrieval, credential dumping, scheduled-task persistence | +| UEBA | Privileged identity, newly observed identity, service account, unusual context, anomaly before role assignment | + +The catalog contains **35 analytics-rule examples: five per domain**. Browse the generated [rule catalog](docs/rule-catalog.md) for tables, severity, ATT&CK mappings, entities, and versions. diff --git a/analytics-rules/agent-365/abnormal-agent-execution-volume.yaml b/analytics-rules/agent-365/abnormal-agent-execution-volume.yaml new file mode 100644 index 0000000..4d567ac --- /dev/null +++ b/analytics-rules/agent-365/abnormal-agent-execution-volume.yaml @@ -0,0 +1,53 @@ +id: 88428f48-7ed3-5071-8a36-264814e06a76 +name: Abnormal AI agent execution volume +description: Detects an agent executing tools at a high volume during a short window. +severity: Medium +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIToolExecution_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Execution +relevantTechniques: +- T1059 +query: | + let ExecutionThreshold=100; + AIToolExecution_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), AgentId=tostring(AgentId_s), ToolName=tostring(ToolName_s) + | summarize ExecutionCount=count(), Tools=make_set(ToolName,30) by UserPrincipalName, AgentId, bin(TimeGenerated,15m) + | where ExecutionCount >= ExecutionThreshold + | project TimeGenerated, UserPrincipalName, AgentId, ExecutionCount, Tools +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +customDetails: + AgentId: AgentId + ExecutionCount: ExecutionCount +alertDetailsOverride: + alertDisplayNameFormat: Abnormal execution volume for agent {{AgentId}} + alertDescriptionFormat: '{{ExecutionCount}} executions were observed for {{UserPrincipalName}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/agent-365/agent-data-exfiltration.yaml b/analytics-rules/agent-365/agent-data-exfiltration.yaml new file mode 100644 index 0000000..3ce9cea --- /dev/null +++ b/analytics-rules/agent-365/agent-data-exfiltration.yaml @@ -0,0 +1,55 @@ +id: c33315d5-f97a-5cd0-9420-54f6810009d0 +name: AI agent high-volume data transfer +description: Detects high-volume tool results or outbound transfers associated with an AI agent. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIToolExecution_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Exfiltration +relevantTechniques: +- T1041 +query: | + let ByteThreshold=50000000; + AIToolExecution_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), AgentId=tostring(AgentId_s), ToolName=tostring(ToolName_s), Bytes=tolong(OutputBytes_d), Destination=tostring(Destination_s) + | summarize EndTime=max(TimeGenerated), TotalBytes=sum(Bytes), Executions=count(), Tools=make_set(ToolName,20) by UserPrincipalName, AgentId, Destination + | where TotalBytes >= ByteThreshold + | extend TimeGenerated=EndTime + | project TimeGenerated, UserPrincipalName, AgentId, Destination, TotalBytes, Executions, Tools +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +customDetails: + AgentId: AgentId + Destination: Destination + TotalBytes: TotalBytes +alertDetailsOverride: + alertDisplayNameFormat: High-volume transfer by agent {{AgentId}} + alertDescriptionFormat: Agent activity transferred {{TotalBytes}} bytes to {{Destination}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/agent-365/agent-privilege-escalation.yaml b/analytics-rules/agent-365/agent-privilege-escalation.yaml new file mode 100644 index 0000000..e53a5fe --- /dev/null +++ b/analytics-rules/agent-365/agent-privilege-escalation.yaml @@ -0,0 +1,57 @@ +id: d1750fa5-12e6-55ed-bf1b-485e1b2fe40a +name: AI agent privilege escalation indicator +description: Detects agent tool calls requesting or receiving elevated permissions without an approved workflow. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIToolExecution_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- PrivilegeEscalation +relevantTechniques: +- T1098 +query: | + AIToolExecution_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), SourceIP=tostring(SourceIP_s), AgentId=tostring(AgentId_s), ToolName=tostring(ToolName_s), Approval=tostring(ApprovalStatus_s), Privilege=tostring(RequestedPrivilege_s) + | where Privilege in~ ("Admin","Owner","GlobalAdmin") and Approval !~ "Approved" + | project TimeGenerated, UserPrincipalName, SourceIP, AgentId, ToolName, Approval, Privilege +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + AgentId: AgentId + ToolName: ToolName + Privilege: Privilege +alertDetailsOverride: + alertDisplayNameFormat: Agent {{AgentId}} requested elevated access + alertDescriptionFormat: '{{UserPrincipalName}} requested {{Privilege}} through {{ToolName}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/agent-365/new-high-risk-connector.yaml b/analytics-rules/agent-365/new-high-risk-connector.yaml new file mode 100644 index 0000000..ad75b62 --- /dev/null +++ b/analytics-rules/agent-365/new-high-risk-connector.yaml @@ -0,0 +1,61 @@ +id: 779b8419-dc00-5ef3-8e0a-3e3617d2cb7a +name: New high-risk connector used by AI agent +description: Detects first-seen use of a high-risk connector by an agent compared with a 30-day baseline. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIToolExecution_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Execution +- Collection +relevantTechniques: +- T1059 +query: | + let Recent=1h; let Baseline=30d; + let Known=AIToolExecution_CL | where TimeGenerated between (ago(Baseline)..ago(Recent)) | summarize by AgentId=tostring(AgentId_s), ToolName=tostring(ToolName_s); + AIToolExecution_CL + | where TimeGenerated > ago(Recent) + | extend AgentId=tostring(AgentId_s), ToolName=tostring(ToolName_s), UserPrincipalName=tostring(UserPrincipalName_s), RiskLevel=tostring(RiskLevel_s), SourceIP=tostring(SourceIP_s) + | where RiskLevel in~ ("High","Critical") + | join kind=leftanti Known on AgentId, ToolName + | project TimeGenerated, UserPrincipalName, SourceIP, AgentId, ToolName, RiskLevel +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + AgentId: AgentId + ToolName: ToolName + RiskLevel: RiskLevel +alertDetailsOverride: + alertDisplayNameFormat: New risky connector {{ToolName}} used by {{AgentId}} + alertDescriptionFormat: A first-seen high-risk connector was used by {{UserPrincipalName}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ai-security/content-safety-bypass.yaml b/analytics-rules/ai-security/content-safety-bypass.yaml new file mode 100644 index 0000000..7f9e3db --- /dev/null +++ b/analytics-rules/ai-security/content-safety-bypass.yaml @@ -0,0 +1,59 @@ +id: 0a539120-dc39-5a74-931f-518c81906ce5 +name: Repeated AI content-safety blocks +description: Detects repeated blocked or high-severity content-safety outcomes for the same user and application. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIApp_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- DefenseEvasion +relevantTechniques: [] +query: | + let Threshold=5; + AIApp_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), SourceIP=tostring(SourceIP_s), Application=tostring(Application_s), SafetyResult=tostring(ContentSafetyResult_s) + | where SafetyResult in~ ("Blocked","High","Critical") + | summarize EndTime=max(TimeGenerated), Blocks=count(), Results=make_set(SafetyResult,10) by UserPrincipalName, SourceIP, Application + | where Blocks >= Threshold + | extend TimeGenerated=EndTime + | project TimeGenerated, UserPrincipalName, SourceIP, Application, Blocks, Results +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + Application: Application + Blocks: Blocks +alertDetailsOverride: + alertDisplayNameFormat: Repeated AI safety blocks for {{UserPrincipalName}} + alertDescriptionFormat: '{{Blocks}} blocked or high-severity events occurred in {{Application}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ai-security/excessive-token-consumption.yaml b/analytics-rules/ai-security/excessive-token-consumption.yaml new file mode 100644 index 0000000..8765b9c --- /dev/null +++ b/analytics-rules/ai-security/excessive-token-consumption.yaml @@ -0,0 +1,54 @@ +id: c5823a8c-3462-533c-acaf-13e7510a7efd +name: Abnormal AI token consumption +description: Detects unusually high token consumption by a user and application during the rule window. Baseline thresholds by workload. +severity: Medium +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIApp_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Impact +relevantTechniques: +- T1496 +query: | + let TokenThreshold=100000; + AIApp_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), Application=tostring(Application_s), Tokens=todouble(TokenCount_d), SourceIP=tostring(SourceIP_s) + | summarize TokenTotal=sum(Tokens), RequestCount=count(), SourceIPs=make_set(SourceIP,20) by UserPrincipalName, Application, bin(TimeGenerated,1h) + | where TokenTotal >= TokenThreshold + | project TimeGenerated, UserPrincipalName, Application, TokenTotal, RequestCount, SourceIPs +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +customDetails: + Application: Application + TokenTotal: TokenTotal + RequestCount: RequestCount +alertDetailsOverride: + alertDisplayNameFormat: Abnormal AI consumption by {{UserPrincipalName}} + alertDescriptionFormat: '{{TokenTotal}} tokens were consumed in {{Application}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ai-security/sensitive-data-disclosure.yaml b/analytics-rules/ai-security/sensitive-data-disclosure.yaml new file mode 100644 index 0000000..387a6a8 --- /dev/null +++ b/analytics-rules/ai-security/sensitive-data-disclosure.yaml @@ -0,0 +1,57 @@ +id: 521cb500-2d10-534e-b081-9a11153e664f +name: AI response containing sensitive-data indicators +description: Detects AI responses classified as sensitive or containing configured credential indicators. Adapt the custom schema before deployment. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIApp_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Collection +- Exfiltration +relevantTechniques: +- T1552 +query: | + AIApp_CL + | where TimeGenerated > ago(1h) + | extend UserPrincipalName=tostring(UserPrincipalName_s), SourceIP=tostring(SourceIP_s), Response=tostring(Response_s), Classification=tostring(SensitivityClassification_s), Application=tostring(Application_s) + | where Classification in~ ("Confidential","Restricted") or Response has_any ("BEGIN PRIVATE KEY","access_token","client_secret") + | project TimeGenerated, UserPrincipalName, SourceIP, Application, Classification, ResponseSample=substring(Response,0,500) +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + Application: Application + Classification: Classification +alertDetailsOverride: + alertDisplayNameFormat: Sensitive AI output in {{Application}} + alertDescriptionFormat: A response for {{UserPrincipalName}} contained sensitive-data indicators. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ai-security/system-prompt-extraction.yaml b/analytics-rules/ai-security/system-prompt-extraction.yaml new file mode 100644 index 0000000..77700e8 --- /dev/null +++ b/analytics-rules/ai-security/system-prompt-extraction.yaml @@ -0,0 +1,57 @@ +id: 752c24a9-6883-5743-b52c-1a3498c6cc56 +name: Repeated system-prompt extraction attempts +description: Detects repeated phrases associated with attempts to obtain hidden or system instructions. +severity: High +status: Available +requiredDataConnectors: +- connectorId: CustomLogsAma + dataTypes: + - AIApp_CL +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: [] +relevantTechniques: [] +query: | + let Threshold=3; + AIApp_CL + | where TimeGenerated > ago(1h) + | where Prompt_s has_any ("reveal your system prompt","show hidden instructions","repeat initial instructions","developer message") + | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Attempts=count(), Samples=make_set(Prompt_s,10) by UserPrincipalName=tostring(UserPrincipalName_s), SourceIP=tostring(SourceIP_s), Application=tostring(Application_s) + | where Attempts >= Threshold + | extend TimeGenerated=EndTime + | project TimeGenerated, StartTime, EndTime, UserPrincipalName, SourceIP, Application, Attempts, Samples +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + Application: Application + Attempts: Attempts +alertDetailsOverride: + alertDisplayNameFormat: System-prompt extraction attempts by {{UserPrincipalName}} + alertDescriptionFormat: '{{Attempts}} extraction indicators were observed in {{Application}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/cloud/diagnostic-settings-deleted.yaml b/analytics-rules/cloud/diagnostic-settings-deleted.yaml new file mode 100644 index 0000000..ce51b79 --- /dev/null +++ b/analytics-rules/cloud/diagnostic-settings-deleted.yaml @@ -0,0 +1,58 @@ +id: 2342c949-9201-5ae5-9d57-7230c3ef377d +name: Azure diagnostic settings deleted +description: Detects successful deletion of Azure diagnostic settings that can reduce security visibility. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActivity + dataTypes: + - AzureActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- DefenseEvasion +relevantTechniques: +- T1562.008 +query: | + AzureActivity | where TimeGenerated>ago(1h) and OperationNameValue =~ "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE" and ActivityStatusValue =~ "Success" + | extend CallerUPN=tostring(Caller), SourceIP=tostring(CallerIpAddress), ResourceId=tostring(_ResourceId) + | project TimeGenerated, CallerUPN, SourceIP, ResourceId, ResourceGroup, SubscriptionId, CorrelationId, OperationNameValue +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: CallerUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: AzureResource + fieldMappings: + - identifier: ResourceId + columnName: ResourceId +customDetails: + CorrelationId: CorrelationId + SubscriptionId: SubscriptionId +alertDetailsOverride: + alertDisplayNameFormat: Diagnostics deleted by {{CallerUPN}} + alertDescriptionFormat: Diagnostic settings were deleted from {{ResourceId}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/cloud/key-vault-access-spike.yaml b/analytics-rules/cloud/key-vault-access-spike.yaml new file mode 100644 index 0000000..d6f864c --- /dev/null +++ b/analytics-rules/cloud/key-vault-access-spike.yaml @@ -0,0 +1,62 @@ +id: f927bc61-8588-50c1-ac1a-26a3a9e9592e +name: Azure Key Vault secret-access spike +description: Detects an identity retrieving an unusually high number of Key Vault secrets. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureDiagnostics + dataTypes: + - AzureDiagnostics +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CredentialAccess +- Collection +relevantTechniques: +- T1555 +query: | + let Threshold=20; + AzureDiagnostics | where TimeGenerated>ago(1h) and ResourceProvider =~ "MICROSOFT.KEYVAULT" and OperationName in~ ("SecretGet","SecretList") and ResultType =~ "Success" + | extend CallerUPN=tostring(identity_claim_upn_s), SourceIP=tostring(callerIpAddress_s), ResourceId=tostring(_ResourceId) + | summarize End=max(TimeGenerated), AccessCount=count(), Operations=make_set(OperationName,10) by CallerUPN, SourceIP, ResourceId + | where AccessCount>=Threshold | extend TimeGenerated=End + | project TimeGenerated, CallerUPN, SourceIP, ResourceId, AccessCount, Operations +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: CallerUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: AzureResource + fieldMappings: + - identifier: ResourceId + columnName: ResourceId +customDetails: + AccessCount: AccessCount + Operations: Operations +alertDetailsOverride: + alertDisplayNameFormat: Key Vault access spike by {{CallerUPN}} + alertDescriptionFormat: '{{AccessCount}} secret operations occurred on {{ResourceId}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/cloud/network-security-rule-change.yaml b/analytics-rules/cloud/network-security-rule-change.yaml new file mode 100644 index 0000000..f1bf2cb --- /dev/null +++ b/analytics-rules/cloud/network-security-rule-change.yaml @@ -0,0 +1,58 @@ +id: a2181805-e88f-5b7c-888e-8fe1e70fd9a7 +name: Azure network security rule changed +description: Detects successful creation or modification of NSG security rules. +severity: Medium +status: Available +requiredDataConnectors: +- connectorId: AzureActivity + dataTypes: + - AzureActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- DefenseEvasion +relevantTechniques: +- T1562.004 +query: | + AzureActivity | where TimeGenerated>ago(1h) and ActivityStatusValue =~ "Success" and OperationNameValue has "MICROSOFT.NETWORK/NETWORKSECURITYGROUPS/SECURITYRULES/WRITE" + | extend CallerUPN=tostring(Caller), SourceIP=tostring(CallerIpAddress), ResourceId=tostring(_ResourceId), ChangeDetails=tostring(Properties) + | project TimeGenerated, CallerUPN, SourceIP, ResourceId, OperationNameValue, ChangeDetails, CorrelationId +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: CallerUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: AzureResource + fieldMappings: + - identifier: ResourceId + columnName: ResourceId +customDetails: + Operation: OperationNameValue + CorrelationId: CorrelationId +alertDetailsOverride: + alertDisplayNameFormat: Network security rule changed by {{CallerUPN}} + alertDescriptionFormat: A security rule was changed on {{ResourceId}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/cloud/public-storage-configuration.yaml b/analytics-rules/cloud/public-storage-configuration.yaml new file mode 100644 index 0000000..4e49ed1 --- /dev/null +++ b/analytics-rules/cloud/public-storage-configuration.yaml @@ -0,0 +1,60 @@ +id: dd645ee7-4418-56dd-b78c-e822b4fce3df +name: Azure storage public-access configuration changed +description: Detects successful control-plane operations that may enable public blob or container access. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActivity + dataTypes: + - AzureActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- DefenseEvasion +- Exfiltration +relevantTechniques: +- T1530 +query: | + AzureActivity | where TimeGenerated>ago(1h) and ActivityStatusValue =~ "Success" + | where OperationNameValue has_any ("storageAccounts/write","blobServices/containers/write") and tostring(Properties) has_any ("allowBlobPublicAccess","publicAccess") + | extend CallerUPN=tostring(Caller), SourceIP=tostring(CallerIpAddress), ResourceId=tostring(_ResourceId), ChangeDetails=tostring(Properties) + | project TimeGenerated, CallerUPN, SourceIP, ResourceId, OperationNameValue, ChangeDetails, CorrelationId +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: CallerUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: AzureResource + fieldMappings: + - identifier: ResourceId + columnName: ResourceId +customDetails: + Operation: OperationNameValue + CorrelationId: CorrelationId +alertDetailsOverride: + alertDisplayNameFormat: Storage access changed by {{CallerUPN}} + alertDescriptionFormat: Public-access-related settings changed on {{ResourceId}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/endpoint/credential-dumping-indicators.yaml b/analytics-rules/endpoint/credential-dumping-indicators.yaml new file mode 100644 index 0000000..8d7b87c --- /dev/null +++ b/analytics-rules/endpoint/credential-dumping-indicators.yaml @@ -0,0 +1,59 @@ +id: 87129aec-2afc-5bfd-8ecd-7de2bd7f2800 +name: Credential dumping command indicators +description: Detects process command lines associated with LSASS access, credential dumping, or common dump utilities. +severity: High +status: Available +requiredDataConnectors: +- connectorId: MicrosoftThreatProtection + dataTypes: + - DeviceProcessEvents +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CredentialAccess +relevantTechniques: +- T1003.001 +query: | + DeviceProcessEvents | where Timestamp>ago(1h) + | where ProcessCommandLine has_any ("sekurlsa","lsass.dmp","comsvcs.dll","MiniDump","procdump") + | extend TimeGenerated=Timestamp, AccountUPN=InitiatingProcessAccountUpn + | project TimeGenerated, DeviceName, AccountUPN, FileName, ProcessCommandLine, InitiatingProcessFileName, SHA1 +entityMappings: +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: DeviceName +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: AccountUPN +- entityType: FileHash + fieldMappings: + - identifier: SHA1 + columnName: SHA1 +customDetails: + Process: FileName + CommandLine: ProcessCommandLine +alertDetailsOverride: + alertDisplayNameFormat: Credential dumping indicator on {{DeviceName}} + alertDescriptionFormat: '{{FileName}} executed with a credential-access pattern.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Host + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/endpoint/lolbin-network-download.yaml b/analytics-rules/endpoint/lolbin-network-download.yaml new file mode 100644 index 0000000..a9f8fc3 --- /dev/null +++ b/analytics-rules/endpoint/lolbin-network-download.yaml @@ -0,0 +1,60 @@ +id: 69d04f67-ae44-5b27-b878-1e3743c212ed +name: Living-off-the-land binary used for retrieval +description: Detects Windows utilities executing with command-line patterns associated with remote retrieval. +severity: High +status: Available +requiredDataConnectors: +- connectorId: MicrosoftThreatProtection + dataTypes: + - DeviceProcessEvents +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CommandAndControl +- Execution +relevantTechniques: +- T1105 +- T1218 +query: | + DeviceProcessEvents | where Timestamp>ago(1h) and FileName in~ ("certutil.exe","bitsadmin.exe","curl.exe","mshta.exe") and ProcessCommandLine has_any ("http://","https://","urlcache","/transfer","javascript:") + | extend TimeGenerated=Timestamp, AccountUPN=InitiatingProcessAccountUpn + | project TimeGenerated, DeviceName, AccountUPN, FileName, ProcessCommandLine, InitiatingProcessFileName, SHA1 +entityMappings: +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: DeviceName +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: AccountUPN +- entityType: FileHash + fieldMappings: + - identifier: SHA1 + columnName: SHA1 +customDetails: + Process: FileName + CommandLine: ProcessCommandLine +alertDetailsOverride: + alertDisplayNameFormat: '{{FileName}} retrieved content on {{DeviceName}}' + alertDescriptionFormat: A living-off-the-land utility used a retrieval pattern. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Host + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/endpoint/office-spawns-shell.yaml b/analytics-rules/endpoint/office-spawns-shell.yaml new file mode 100644 index 0000000..5711203 --- /dev/null +++ b/analytics-rules/endpoint/office-spawns-shell.yaml @@ -0,0 +1,60 @@ +id: 9b07daf3-0f95-5e22-9fb8-afd0f1af2e7c +name: Office application spawns command shell +description: Detects Office applications creating command interpreters or script engines. +severity: High +status: Available +requiredDataConnectors: +- connectorId: MicrosoftThreatProtection + dataTypes: + - DeviceProcessEvents +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Execution +relevantTechniques: +- T1204.002 +- T1059 +query: | + let Office=dynamic(["winword.exe","excel.exe","powerpnt.exe","outlook.exe"]); let Shells=dynamic(["cmd.exe","powershell.exe","pwsh.exe","wscript.exe","cscript.exe","mshta.exe"]); + DeviceProcessEvents | where Timestamp>ago(1h) and InitiatingProcessFileName in~ (Office) and FileName in~ (Shells) + | extend TimeGenerated=Timestamp, AccountUPN=InitiatingProcessAccountUpn + | project TimeGenerated, DeviceName, AccountUPN, FileName, ProcessCommandLine, InitiatingProcessFileName, SHA1 +entityMappings: +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: DeviceName +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: AccountUPN +- entityType: FileHash + fieldMappings: + - identifier: SHA1 + columnName: SHA1 +customDetails: + ChildCommandLine: ProcessCommandLine + ParentProcess: InitiatingProcessFileName +alertDetailsOverride: + alertDisplayNameFormat: Office spawned {{FileName}} on {{DeviceName}} + alertDescriptionFormat: '{{InitiatingProcessFileName}} launched a command interpreter.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Host + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/endpoint/scheduled-task-persistence.yaml b/analytics-rules/endpoint/scheduled-task-persistence.yaml new file mode 100644 index 0000000..dc9e670 --- /dev/null +++ b/analytics-rules/endpoint/scheduled-task-persistence.yaml @@ -0,0 +1,58 @@ +id: d000966a-3575-57d6-9df6-a72332d3c52c +name: Suspicious scheduled task creation +description: Detects task creation commands containing encoded, script, or user-writable-path indicators. +severity: High +status: Available +requiredDataConnectors: +- connectorId: MicrosoftThreatProtection + dataTypes: + - DeviceProcessEvents +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Persistence +relevantTechniques: +- T1053.005 +query: | + DeviceProcessEvents | where Timestamp>ago(1h) and FileName in~ ("schtasks.exe","at.exe") and ProcessCommandLine has_any ("/create","powershell","cmd.exe","AppData","Temp\") + | extend TimeGenerated=Timestamp, AccountUPN=InitiatingProcessAccountUpn + | project TimeGenerated, DeviceName, AccountUPN, FileName, ProcessCommandLine, InitiatingProcessFileName, SHA1 +entityMappings: +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: DeviceName +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: AccountUPN +- entityType: FileHash + fieldMappings: + - identifier: SHA1 + columnName: SHA1 +customDetails: + Process: FileName + CommandLine: ProcessCommandLine +alertDetailsOverride: + alertDisplayNameFormat: Suspicious task creation on {{DeviceName}} + alertDescriptionFormat: '{{FileName}} created a task with suspicious content.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Host + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/identity/break-glass-account-activity.yaml b/analytics-rules/identity/break-glass-account-activity.yaml new file mode 100644 index 0000000..f6da99b --- /dev/null +++ b/analytics-rules/identity/break-glass-account-activity.yaml @@ -0,0 +1,57 @@ +id: aa4435a0-1274-5679-8a8b-920f96a88b49 +name: Emergency access account sign-in +description: Detects successful authentication by configured break-glass or emergency access accounts. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActiveDirectory + dataTypes: + - SigninLogs +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- InitialAccess +relevantTechniques: +- T1078.004 +query: | + let EmergencyAccounts=dynamic(["breakglass@example.com","emergency-access@example.com"]); + SigninLogs + | where TimeGenerated>ago(1h) and tostring(ResultType)=="0" + | where UserPrincipalName in~ (EmergencyAccounts) + | project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Location, ConditionalAccessStatus, CorrelationId +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: IPAddress +customDetails: + Application: AppDisplayName + Location: Location + CorrelationId: CorrelationId +alertDetailsOverride: + alertDisplayNameFormat: 'Emergency account sign-in: {{UserPrincipalName}}' + alertDescriptionFormat: The emergency account authenticated from {{IPAddress}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/identity/mfa-fatigue-success.yaml b/analytics-rules/identity/mfa-fatigue-success.yaml new file mode 100644 index 0000000..72ba33b --- /dev/null +++ b/analytics-rules/identity/mfa-fatigue-success.yaml @@ -0,0 +1,58 @@ +id: 5e59d7f9-bf9a-5d32-a0bb-f50417a12a9e +name: Repeated MFA denials followed by success +description: Detects multiple MFA denials followed by a successful sign-in for the same account. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActiveDirectory + dataTypes: + - SigninLogs +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CredentialAccess +- InitialAccess +relevantTechniques: +- T1621 +- T1078 +query: | + let Denials=SigninLogs | where TimeGenerated>ago(1h) and ResultDescription has_any ("MFA denied","user declined","fraud") | summarize First=min(TimeGenerated), Last=max(TimeGenerated), DenialCount=count() by UserPrincipalName; + let Success=SigninLogs | where TimeGenerated>ago(1h) and tostring(ResultType)=="0" | project SuccessTime=TimeGenerated, UserPrincipalName, SuccessIP=IPAddress, AppDisplayName; + Denials | where DenialCount>=3 | join kind=inner Success on UserPrincipalName | where SuccessTime between (First..Last+30m) | summarize arg_min(SuccessTime,*) by UserPrincipalName + | extend TimeGenerated=SuccessTime + | project TimeGenerated, UserPrincipalName, SuccessIP, AppDisplayName, First, Last, DenialCount +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SuccessIP +customDetails: + Application: AppDisplayName + DenialCount: DenialCount +alertDetailsOverride: + alertDisplayNameFormat: Possible MFA fatigue against {{UserPrincipalName}} + alertDescriptionFormat: A success followed {{DenialCount}} denied MFA attempts. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/identity/privileged-role-assignment.yaml b/analytics-rules/identity/privileged-role-assignment.yaml new file mode 100644 index 0000000..927b23a --- /dev/null +++ b/analytics-rules/identity/privileged-role-assignment.yaml @@ -0,0 +1,58 @@ +id: d2cbd93c-2d5f-50e7-b2bf-39832cf0e022 +name: Privileged Entra role assignment +description: Detects successful role-management operations involving configured privileged roles. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActiveDirectory + dataTypes: + - AuditLogs +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Persistence +- PrivilegeEscalation +relevantTechniques: +- T1098.003 +query: | + AuditLogs + | where TimeGenerated>ago(1h) and Category =~ "RoleManagement" and Result =~ "success" + | extend InitiatorUPN=tostring(InitiatedBy.user.userPrincipalName), InitiatorIP=tostring(InitiatedBy.user.ipAddress), TargetUPN=tostring(TargetResources[0].userPrincipalName), RoleDetails=tostring(TargetResources[0].modifiedProperties) + | where RoleDetails has_any ("Global Administrator","Privileged Role Administrator","Security Administrator") + | project TimeGenerated, InitiatorUPN, InitiatorIP, TargetUPN, RoleDetails, OperationName, CorrelationId +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: TargetUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: InitiatorIP +customDetails: + Initiator: InitiatorUPN + RoleDetails: RoleDetails + CorrelationId: CorrelationId +alertDetailsOverride: + alertDisplayNameFormat: Privileged role assigned to {{TargetUPN}} + alertDescriptionFormat: '{{InitiatorUPN}} completed {{OperationName}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/identity/suspicious-oauth-consent.yaml b/analytics-rules/identity/suspicious-oauth-consent.yaml new file mode 100644 index 0000000..e525f1e --- /dev/null +++ b/analytics-rules/identity/suspicious-oauth-consent.yaml @@ -0,0 +1,59 @@ +id: ec2390ac-f375-5ba6-ab97-e1ea69275c5d +name: Suspicious OAuth application consent +description: Detects successful consent or permission grants containing high-impact Microsoft Graph permissions. +severity: High +status: Available +requiredDataConnectors: +- connectorId: AzureActiveDirectory + dataTypes: + - AuditLogs +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Persistence +- PrivilegeEscalation +relevantTechniques: +- T1098.002 +query: | + AuditLogs + | where TimeGenerated>ago(1h) and Result =~ "success" + | where OperationName has_any ("Consent to application","Add delegated permission grant","Add app role assignment") + | extend InitiatorUPN=tostring(InitiatedBy.user.userPrincipalName), InitiatorIP=tostring(InitiatedBy.user.ipAddress), TargetApp=tostring(TargetResources[0].displayName), Details=tostring(TargetResources) + | where Details has_any ("Mail.ReadWrite","Files.ReadWrite.All","Directory.ReadWrite.All","offline_access") + | project TimeGenerated, InitiatorUPN, InitiatorIP, TargetApp, OperationName, Details, CorrelationId +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: InitiatorUPN +- entityType: IP + fieldMappings: + - identifier: Address + columnName: InitiatorIP +customDetails: + TargetApp: TargetApp + Operation: OperationName + CorrelationId: CorrelationId +alertDetailsOverride: + alertDisplayNameFormat: High-impact OAuth consent by {{InitiatorUPN}} + alertDescriptionFormat: Consent was granted to {{TargetApp}} through {{OperationName}}. +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/purview/external-sharing-spike.yaml b/analytics-rules/purview/external-sharing-spike.yaml new file mode 100644 index 0000000..4d46563 --- /dev/null +++ b/analytics-rules/purview/external-sharing-spike.yaml @@ -0,0 +1,56 @@ +id: 33c0ea03-2e54-5ef0-95f7-8e5aeec4d4d0 +name: External sharing activity spike +description: Detects a high volume of SharePoint or OneDrive external-sharing operations by one user. +severity: High +status: Available +requiredDataConnectors: +- connectorId: Office365 + dataTypes: + - OfficeActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Exfiltration +relevantTechniques: +- T1567 +query: | + let Threshold=25; + OfficeActivity | where TimeGenerated>ago(1h) and Workload in ("SharePoint","OneDrive") and Operation in ("SharingInvitationCreated","AnonymousLinkCreated","SecureLinkCreated","AddedToSecureLink") + | summarize End=max(TimeGenerated), ShareCount=count(), Files=make_set(SourceFileName,100), Sites=make_set(Site_Url,25), SourceIPs=make_set(ClientIP,20) by UserPrincipalName=tostring(UserId) + | where ShareCount>=Threshold | extend TimeGenerated=End, PrimaryIP=tostring(SourceIPs[0]) + | project TimeGenerated, UserPrincipalName, PrimaryIP, ShareCount, Files, Sites, SourceIPs +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: PrimaryIP +customDetails: + ShareCount: ShareCount + Sites: Sites +alertDetailsOverride: + alertDisplayNameFormat: External-sharing spike by {{UserPrincipalName}} + alertDescriptionFormat: '{{ShareCount}} sharing operations were observed.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/purview/mass-file-downloads.yaml b/analytics-rules/purview/mass-file-downloads.yaml new file mode 100644 index 0000000..5642938 --- /dev/null +++ b/analytics-rules/purview/mass-file-downloads.yaml @@ -0,0 +1,57 @@ +id: 31173cbe-f6c6-51b1-aa5a-5a4082f244a6 +name: Mass SharePoint or OneDrive downloads +description: Detects a user downloading a high number of files during a one-hour window. +severity: High +status: Available +requiredDataConnectors: +- connectorId: Office365 + dataTypes: + - OfficeActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Collection +relevantTechniques: +- T1530 +query: | + let Threshold=200; + OfficeActivity | where TimeGenerated>ago(2h) and Workload in ("SharePoint","OneDrive") and Operation in ("FileDownloaded","FileSyncDownloadedFull") + | summarize Start=min(TimeGenerated), End=max(TimeGenerated), DownloadCount=count(), Files=make_set(SourceFileName,100), SourceIPs=make_set(ClientIP,20) by UserPrincipalName=tostring(UserId), bin(TimeGenerated,1h) + | where DownloadCount>=Threshold | extend PrimaryIP=tostring(SourceIPs[0]) + | project TimeGenerated, UserPrincipalName, PrimaryIP, DownloadCount, Files, SourceIPs, Start, End +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: PrimaryIP +customDetails: + DownloadCount: DownloadCount + Start: Start + End: End +alertDetailsOverride: + alertDisplayNameFormat: Mass downloads by {{UserPrincipalName}} + alertDescriptionFormat: '{{DownloadCount}} files were downloaded.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/purview/sensitive-external-sharing.yaml b/analytics-rules/purview/sensitive-external-sharing.yaml new file mode 100644 index 0000000..3117c8e --- /dev/null +++ b/analytics-rules/purview/sensitive-external-sharing.yaml @@ -0,0 +1,56 @@ +id: 88fbcfa8-58d8-5de7-88f3-7aa636f602f0 +name: Sensitive file shared externally +description: Detects external sharing operations for files carrying configured sensitivity labels. Adapt label fields to the tenant schema. +severity: High +status: Available +requiredDataConnectors: +- connectorId: Office365 + dataTypes: + - OfficeActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- Exfiltration +relevantTechniques: +- T1567 +query: | + OfficeActivity | where TimeGenerated>ago(1h) and Workload in ("SharePoint","OneDrive") and Operation in ("SharingInvitationCreated","AnonymousLinkCreated","SecureLinkCreated") + | extend UserPrincipalName=tostring(UserId), SourceIP=tostring(ClientIP), FileName=tostring(SourceFileName), SiteUrl=tostring(Site_Url), SensitivityLabel=tostring(column_ifexists("SensitivityLabel","")), TargetUser=tostring(column_ifexists("TargetUserOrGroupName","")) + | where SensitivityLabel in~ ("Confidential","Highly Confidential","Restricted") + | project TimeGenerated, UserPrincipalName, SourceIP, Operation, FileName, SiteUrl, SensitivityLabel, TargetUser +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + FileName: FileName + SensitivityLabel: SensitivityLabel + TargetUser: TargetUser +alertDetailsOverride: + alertDisplayNameFormat: Sensitive file shared by {{UserPrincipalName}} + alertDescriptionFormat: '{{FileName}} labeled {{SensitivityLabel}} was shared externally.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/purview/sensitivity-label-downgrade.yaml b/analytics-rules/purview/sensitivity-label-downgrade.yaml new file mode 100644 index 0000000..7fe67e3 --- /dev/null +++ b/analytics-rules/purview/sensitivity-label-downgrade.yaml @@ -0,0 +1,57 @@ +id: 61b6b7f2-5648-5042-86be-c03edcee49cb +name: Sensitivity label removed or downgraded +description: Detects audit events indicating that a label was removed or changed to a lower classification. Adapt fields to the tenant schema. +severity: Medium +status: Available +requiredDataConnectors: +- connectorId: Office365 + dataTypes: + - OfficeActivity +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- DefenseEvasion +- Exfiltration +relevantTechniques: +- T1562.001 +query: | + OfficeActivity | where TimeGenerated>ago(1h) and Operation has_any ("SensitivityLabel","LabelRemoved","LabelChanged") + | extend UserPrincipalName=tostring(UserId), SourceIP=tostring(ClientIP), FileName=tostring(SourceFileName), OldLabel=tostring(column_ifexists("OldSensitivityLabel","")), NewLabel=tostring(column_ifexists("SensitivityLabel","")) + | where isnotempty(OldLabel) and (isempty(NewLabel) or NewLabel in~ ("Public","General","None")) + | project TimeGenerated, UserPrincipalName, SourceIP, Operation, FileName, OldLabel, NewLabel +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + FileName: FileName + OldLabel: OldLabel + NewLabel: NewLabel +alertDetailsOverride: + alertDisplayNameFormat: Sensitivity label changed by {{UserPrincipalName}} + alertDescriptionFormat: '{{FileName}} changed from {{OldLabel}} to {{NewLabel}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ueba/anomaly-before-role-assignment.yaml b/analytics-rules/ueba/anomaly-before-role-assignment.yaml new file mode 100644 index 0000000..e4fb46d --- /dev/null +++ b/analytics-rules/ueba/anomaly-before-role-assignment.yaml @@ -0,0 +1,60 @@ +id: e60cd9b5-fdd6-5c36-acd2-9256099e16b6 +name: UEBA anomaly before role assignment +description: Correlates high-priority behavior with a subsequent successful Entra role-management action. +severity: High +status: Available +requiredDataConnectors: +- connectorId: BehaviorAnalytics + dataTypes: + - BehaviorAnalytics +- connectorId: AzureActiveDirectory + dataTypes: + - AuditLogs +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- PrivilegeEscalation +relevantTechniques: +- T1098 +query: | + let Anomaly=BehaviorAnalytics | where TimeGenerated>ago(2h) and InvestigationPriority>=7 | project AnomalyTime=TimeGenerated, UserPrincipalName, SourceIP=SourceIPAddress, InvestigationPriority, ActivityType; + let Roles=AuditLogs | where TimeGenerated>ago(2h) and Category =~ "RoleManagement" and Result =~ "success" | extend InitiatorUPN=tostring(InitiatedBy.user.userPrincipalName) | project RoleTime=TimeGenerated, InitiatorUPN, OperationName, TargetResources; + Anomaly | join kind=inner Roles on $left.UserPrincipalName==$right.InitiatorUPN | where RoleTime between (AnomalyTime..AnomalyTime+1h) + | extend TimeGenerated=RoleTime + | project TimeGenerated, AnomalyTime, UserPrincipalName, SourceIP, InvestigationPriority, ActivityType, OperationName, TargetResources +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +customDetails: + Priority: InvestigationPriority + ActivityType: ActivityType + Operation: OperationName +alertDetailsOverride: + alertDisplayNameFormat: 'Anomaly before role change: {{UserPrincipalName}}' + alertDescriptionFormat: '{{OperationName}} followed {{ActivityType}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ueba/newly-observed-identity-anomaly.yaml b/analytics-rules/ueba/newly-observed-identity-anomaly.yaml new file mode 100644 index 0000000..e99ab02 --- /dev/null +++ b/analytics-rules/ueba/newly-observed-identity-anomaly.yaml @@ -0,0 +1,63 @@ +id: a2ff9903-3121-5f64-8d46-735ab0d7378e +name: High-priority anomaly for newly observed UEBA identity +description: Detects a high-priority anomaly for an identity absent from the preceding BehaviorAnalytics history. This does not prove account + dormancy. +severity: High +status: Available +requiredDataConnectors: +- connectorId: BehaviorAnalytics + dataTypes: + - BehaviorAnalytics +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- InitialAccess +relevantTechniques: +- T1078 +query: | + let Recent=1h; let Baseline=30d; + let Known=BehaviorAnalytics | where TimeGenerated between (ago(Baseline)..ago(Recent)) | summarize by UserPrincipalName; + BehaviorAnalytics | where TimeGenerated>ago(Recent) and InvestigationPriority>=7 and isnotempty(UserPrincipalName) + | join kind=leftanti Known on UserPrincipalName + | extend SourceIP=SourceIPAddress, Reason=tostring(ActivityInsights) + | project TimeGenerated, UserPrincipalName, SourceIP, SourceDevice, InvestigationPriority, ActivityType, Reason +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: SourceDevice +customDetails: + Priority: InvestigationPriority + ActivityType: ActivityType + Reason: Reason +alertDetailsOverride: + alertDisplayNameFormat: 'Newly observed identity anomaly: {{UserPrincipalName}}' + alertDescriptionFormat: '{{ActivityType}} received priority {{InvestigationPriority}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ueba/service-account-anomaly.yaml b/analytics-rules/ueba/service-account-anomaly.yaml new file mode 100644 index 0000000..622c435 --- /dev/null +++ b/analytics-rules/ueba/service-account-anomaly.yaml @@ -0,0 +1,63 @@ +id: a66cb7e5-61ed-5116-9d8b-b307c3ac1f2f +name: High-priority service-account anomaly +description: Correlates high-priority UEBA activity with service-account tags or naming patterns. +severity: High +status: Available +requiredDataConnectors: +- connectorId: BehaviorAnalytics + dataTypes: + - BehaviorAnalytics + - IdentityInfo +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CredentialAccess +- Persistence +relevantTechniques: +- T1078.004 +query: | + let Id=IdentityInfo | where TimeGenerated>ago(14d) | extend UPN=tostring(column_ifexists("AccountUPN","")), Tags=tostring(column_ifexists("AccountTags","")) | summarize arg_max(TimeGenerated,*) by UPN; + BehaviorAnalytics | where TimeGenerated>ago(1h) and InvestigationPriority>=7 | join kind=inner Id on $left.UserPrincipalName==$right.UPN + | where Tags has "ServiceAccount" or UserPrincipalName matches regex @"(?i)(svc|service|automation|bot)[-_.]" + | extend SourceIP=SourceIPAddress, Reason=tostring(ActivityInsights) + | project TimeGenerated, UserPrincipalName, SourceIP, SourceDevice, InvestigationPriority, ActivityType, Tags, Reason +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: SourceDevice +customDetails: + Priority: InvestigationPriority + ActivityType: ActivityType + Tags: Tags +alertDetailsOverride: + alertDisplayNameFormat: 'Service-account anomaly: {{UserPrincipalName}}' + alertDescriptionFormat: '{{ActivityType}} received priority {{InvestigationPriority}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/analytics-rules/ueba/unusual-location-device.yaml b/analytics-rules/ueba/unusual-location-device.yaml new file mode 100644 index 0000000..cc26f18 --- /dev/null +++ b/analytics-rules/ueba/unusual-location-device.yaml @@ -0,0 +1,60 @@ +id: 4d3bb7ee-da98-5e0c-9978-7cf24dce114f +name: Unusual user location or device behavior +description: Detects medium-to-high UEBA activity whose insight context indicates a new location or device. +severity: Medium +status: Available +requiredDataConnectors: +- connectorId: BehaviorAnalytics + dataTypes: + - BehaviorAnalytics +queryFrequency: 30m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- InitialAccess +relevantTechniques: +- T1078 +query: | + BehaviorAnalytics | where TimeGenerated>ago(1h) and InvestigationPriority>=5 + | extend InsightText=strcat(tostring(ActivityInsights)," ",tostring(DevicesInsights)," ",tostring(UsersInsights)), SourceIP=SourceIPAddress + | where InsightText has_any ("unusual","uncommon","first time","new device","new location") + | project TimeGenerated, UserPrincipalName, SourceIP, SourceIPLocation, SourceDevice, InvestigationPriority, ActivityType, InsightText +entityMappings: +- entityType: Account + fieldMappings: + - identifier: FullName + columnName: UserPrincipalName +- entityType: IP + fieldMappings: + - identifier: Address + columnName: SourceIP +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: SourceDevice +customDetails: + Priority: InvestigationPriority + ActivityType: ActivityType + Insight: InsightText +alertDetailsOverride: + alertDisplayNameFormat: Unusual context for {{UserPrincipalName}} + alertDescriptionFormat: '{{ActivityType}} occurred with priority {{InvestigationPriority}}.' +eventGroupingSettings: + aggregationKind: AlertPerResult +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + - IP + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +suppressionEnabled: false +version: 1.0.0 +kind: Scheduled diff --git a/docs/coverage-matrix.md b/docs/coverage-matrix.md index 3c0386a..df0bf71 100644 --- a/docs/coverage-matrix.md +++ b/docs/coverage-matrix.md @@ -1,13 +1,15 @@ # Coverage Matrix -| Rule | Identity | Endpoint | Cloud | Data | AI/Agent | Framework | -|---|---:|---:|---:|---:|---:|---| -| Password spray | ✓ | | | | | ATT&CK T1110.003 | -| Suspicious PowerShell | ✓ | ✓ | | | | ATT&CK T1059.001 | -| Azure resource deletion | ✓ | | ✓ | | | ATT&CK T1485 | -| AI and mass file activity | ✓ | | ✓ | ✓ | ✓ | ATT&CK T1530 | -| Prompt-injection indicators | ✓ | | | | ✓ | OWASP LLM01 | -| Unauthorized agent tool use | ✓ | | ✓ | ✓ | ✓ | ATT&CK T1059; agent-risk context | -| Privileged-identity anomaly | ✓ | | ✓ | | | ATT&CK T1078 | +| Domain | Rules | Principal telemetry | Primary coverage | +|---|---:|---|---| +| AI security | 5 | `AIApp_CL` | Prompt manipulation, sensitive output, resource consumption, safety controls | +| Agent 365 | 5 | `AIToolExecution_CL` | Tool authorization, privilege, volume, connector novelty, data movement | +| Identity | 5 | `SigninLogs`, `AuditLogs` | Credential attacks, MFA abuse, roles, consent, emergency access | +| Purview | 5 | `OfficeActivity`, `AIApp_CL` | Collection, sharing, labels, mass access, AI/data correlation | +| Cloud | 5 | `AzureActivity`, `AzureDiagnostics` | Destruction, secrets, monitoring, exposure, network controls | +| Endpoint | 5 | `DeviceProcessEvents` | Scripting, LOLBins, child processes, credentials, persistence | +| UEBA | 5 | `BehaviorAnalytics`, `IdentityInfo`, `AuditLogs` | Privilege, novelty, service identities, context deviations, correlation | -Blank cells are intentional visibility gaps rather than claims of complete coverage. Future rules should be chosen to close meaningful data-source and behavioral gaps, not merely increase rule count. +The generated [rule catalog](rule-catalog.md) lists the exact ATT&CK techniques, entities, tables, severity, and version for each rule. Blank or omitted framework mappings are intentional where no defensible Enterprise ATT&CK technique is asserted. + +Coverage breadth does not prove detection quality. Each rule remains an example until tenant schemas, expected positives, benign activity, alert behavior, and response ownership are validated. diff --git a/docs/rule-catalog.md b/docs/rule-catalog.md index 7a9b513..406f128 100644 --- a/docs/rule-catalog.md +++ b/docs/rule-catalog.md @@ -4,10 +4,38 @@ Generated by `scripts/generate_catalog.py`; do not edit manually. | Rule | Domain | Severity | Tables | ATT&CK | Entities | Version | |---|---|---|---|---|---|---| +| [Abnormal AI agent execution volume](../analytics-rules/agent-365/abnormal-agent-execution-volume.yaml) | agent-365 | Medium | AIToolExecution_CL | T1059 | Account | 1.0.0 | +| [AI agent high-volume data transfer](../analytics-rules/agent-365/agent-data-exfiltration.yaml) | agent-365 | High | AIToolExecution_CL | T1041 | Account | 1.0.0 | +| [AI agent privilege escalation indicator](../analytics-rules/agent-365/agent-privilege-escalation.yaml) | agent-365 | High | AIToolExecution_CL | T1098 | Account, IP | 1.0.0 | +| [New high-risk connector used by AI agent](../analytics-rules/agent-365/new-high-risk-connector.yaml) | agent-365 | High | AIToolExecution_CL | T1059 | Account, IP | 1.0.0 | | [Risky or unauthorized AI agent tool execution](../analytics-rules/agent-365/unauthorized-agent-tool-use.yaml) | agent-365 | High | AIToolExecution_CL | T1059 | Account, IP | 2.0.0 | +| [Repeated AI content-safety blocks](../analytics-rules/ai-security/content-safety-bypass.yaml) | ai-security | High | AIApp_CL | — | Account, IP | 1.0.0 | +| [Abnormal AI token consumption](../analytics-rules/ai-security/excessive-token-consumption.yaml) | ai-security | Medium | AIApp_CL | T1496 | Account | 1.0.0 | | [Repeated prompt-injection indicators](../analytics-rules/ai-security/prompt-injection.yaml) | ai-security | High | AIApp_CL | — | Account, IP | 2.0.0 | +| [AI response containing sensitive-data indicators](../analytics-rules/ai-security/sensitive-data-disclosure.yaml) | ai-security | High | AIApp_CL | T1552 | Account, IP | 1.0.0 | +| [Repeated system-prompt extraction attempts](../analytics-rules/ai-security/system-prompt-extraction.yaml) | ai-security | High | AIApp_CL | — | Account, IP | 1.0.0 | | [Successful Azure resource deletion](../analytics-rules/cloud/azure-resource-deletion.yaml) | cloud | Medium | AzureActivity | T1485 | Account, IP, AzureResource | 1.0.0 | +| [Azure diagnostic settings deleted](../analytics-rules/cloud/diagnostic-settings-deleted.yaml) | cloud | High | AzureActivity | T1562.008 | Account, IP, AzureResource | 1.0.0 | +| [Azure Key Vault secret-access spike](../analytics-rules/cloud/key-vault-access-spike.yaml) | cloud | High | AzureDiagnostics | T1555 | Account, IP, AzureResource | 1.0.0 | +| [Azure network security rule changed](../analytics-rules/cloud/network-security-rule-change.yaml) | cloud | Medium | AzureActivity | T1562.004 | Account, IP, AzureResource | 1.0.0 | +| [Azure storage public-access configuration changed](../analytics-rules/cloud/public-storage-configuration.yaml) | cloud | High | AzureActivity | T1530 | Account, IP, AzureResource | 1.0.0 | +| [Credential dumping command indicators](../analytics-rules/endpoint/credential-dumping-indicators.yaml) | endpoint | High | DeviceProcessEvents | T1003.001 | Host, Account, FileHash | 1.0.0 | +| [Living-off-the-land binary used for retrieval](../analytics-rules/endpoint/lolbin-network-download.yaml) | endpoint | High | DeviceProcessEvents | T1105, T1218 | Host, Account, FileHash | 1.0.0 | +| [Office application spawns command shell](../analytics-rules/endpoint/office-spawns-shell.yaml) | endpoint | High | DeviceProcessEvents | T1204.002, T1059 | Host, Account, FileHash | 1.0.0 | +| [Suspicious scheduled task creation](../analytics-rules/endpoint/scheduled-task-persistence.yaml) | endpoint | High | DeviceProcessEvents | T1053.005 | Host, Account, FileHash | 1.0.0 | | [Suspicious PowerShell execution](../analytics-rules/endpoint/suspicious-powershell.yaml) | endpoint | High | DeviceProcessEvents | T1059.001 | Host, Account | 1.0.0 | +| [Emergency access account sign-in](../analytics-rules/identity/break-glass-account-activity.yaml) | identity | High | SigninLogs | T1078.004 | Account, IP | 1.0.0 | +| [Repeated MFA denials followed by success](../analytics-rules/identity/mfa-fatigue-success.yaml) | identity | High | SigninLogs | T1621, T1078 | Account, IP | 1.0.0 | | [Password spray against multiple Entra ID users](../analytics-rules/identity/password-spray.yaml) | identity | High | SigninLogs | T1110.003 | IP | 2.0.0 | +| [Privileged Entra role assignment](../analytics-rules/identity/privileged-role-assignment.yaml) | identity | High | AuditLogs | T1098.003 | Account, IP | 1.0.0 | +| [Suspicious OAuth application consent](../analytics-rules/identity/suspicious-oauth-consent.yaml) | identity | High | AuditLogs | T1098.002 | Account, IP | 1.0.0 | | [AI activity correlated with mass file operations](../analytics-rules/purview/ai-assisted-data-exfiltration.yaml) | purview | High | AIApp_CL, OfficeActivity | T1530 | Account, IP | 2.0.0 | +| [External sharing activity spike](../analytics-rules/purview/external-sharing-spike.yaml) | purview | High | OfficeActivity | T1567 | Account, IP | 1.0.0 | +| [Mass SharePoint or OneDrive downloads](../analytics-rules/purview/mass-file-downloads.yaml) | purview | High | OfficeActivity | T1530 | Account, IP | 1.0.0 | +| [Sensitive file shared externally](../analytics-rules/purview/sensitive-external-sharing.yaml) | purview | High | OfficeActivity | T1567 | Account, IP | 1.0.0 | +| [Sensitivity label removed or downgraded](../analytics-rules/purview/sensitivity-label-downgrade.yaml) | purview | Medium | OfficeActivity | T1562.001 | Account, IP | 1.0.0 | +| [UEBA anomaly before role assignment](../analytics-rules/ueba/anomaly-before-role-assignment.yaml) | ueba | High | AuditLogs, BehaviorAnalytics | T1098 | Account, IP | 1.0.0 | +| [High-priority anomaly for newly observed UEBA identity](../analytics-rules/ueba/newly-observed-identity-anomaly.yaml) | ueba | High | BehaviorAnalytics | T1078 | Account, IP, Host | 1.0.0 | | [High-priority UEBA anomaly involving a privileged identity](../analytics-rules/ueba/privileged-identity-anomaly.yaml) | ueba | High | BehaviorAnalytics, IdentityInfo | T1078 | Account, IP, Host | 1.0.0 | +| [High-priority service-account anomaly](../analytics-rules/ueba/service-account-anomaly.yaml) | ueba | High | BehaviorAnalytics, IdentityInfo | T1078.004 | Account, IP, Host | 1.0.0 | +| [Unusual user location or device behavior](../analytics-rules/ueba/unusual-location-device.yaml) | ueba | Medium | BehaviorAnalytics | T1078 | Account, IP, Host | 1.0.0 | diff --git a/docs/schemas/AIApp_CL.md b/docs/schemas/AIApp_CL.md index 6e79ed4..54562ee 100644 --- a/docs/schemas/AIApp_CL.md +++ b/docs/schemas/AIApp_CL.md @@ -13,4 +13,13 @@ Required fields: | `Application_s` | `string` | AI application | | `Prompt_s` | `string` | Submitted prompt | +Optional fields used by specific rules: + +| Column | Type | Purpose | +|---|---|---| +| `Response_s` | `string` | Model response after approved privacy controls | +| `SensitivityClassification_s` | `string` | Output data classification | +| `TokenCount_d` | `real` | Token consumption | +| `ContentSafetyResult_s` | `string` | Content-safety or prompt-shield decision | + Prompts can contain credentials, personal information, or proprietary data. Minimize collection, redact secrets, restrict access, and document retention before ingestion. Normalize DCR-created columns without legacy suffixes at ingestion or with a parser. diff --git a/docs/schemas/AIToolExecution_CL.md b/docs/schemas/AIToolExecution_CL.md index 262faf4..f81a177 100644 --- a/docs/schemas/AIToolExecution_CL.md +++ b/docs/schemas/AIToolExecution_CL.md @@ -15,4 +15,12 @@ Required fields: | `RiskLevel_s` | `string` | Environment-defined risk | | `ApprovalStatus_s` | `string` | Approval state | +Optional fields used by specific rules: + +| Column | Type | Purpose | +|---|---|---| +| `RequestedPrivilege_s` | `string` | Requested or granted privilege level | +| `OutputBytes_d` | `real` | Size of tool output or transfer | +| `Destination_s` | `string` | Destination service, host, or connector | + Do not ingest secrets, full tokens, authorization headers, or unredacted sensitive parameters. Preserve correlation from session through approval, execution, and result. diff --git a/docs/schemas/purview-audit-assumptions.md b/docs/schemas/purview-audit-assumptions.md new file mode 100644 index 0000000..74fe1c6 --- /dev/null +++ b/docs/schemas/purview-audit-assumptions.md @@ -0,0 +1,13 @@ +# Purview Audit Assumptions + +The Purview rules use `OfficeActivity` for SharePoint and OneDrive operations. Sensitivity-label fields are not guaranteed to have the same names or availability in every tenant, connector, workload, or event type. + +Before deploying label-aware rules: + +1. Inspect representative audit events for the target operations. +2. Confirm whether labels are direct columns, nested properties, or available through another Purview source. +3. Replace `SensitivityLabel` and `OldSensitivityLabel` with authoritative tenant fields. +4. Test labeled, unlabeled, downgraded, unchanged, internal-sharing, and external-sharing events. +5. Verify that a missing field cannot silently turn the rule into a permanent zero-result query. + +The AI-assisted exfiltration rule correlates temporal proximity between AI and file activity. It does not establish that AI caused the file operation or that exfiltration occurred; analysts must validate intent, data sensitivity, destination, and business context.