Detection rules › Splunk

O365 Email Suspicious Search Behavior

Status
production
Severity
low
Group by
_time, signature, user
Author
Steven Dick
Source
github.com/splunk/security_content

The following analytic identifies when Office 365 users search for suspicious keywords or have an excessive number of queries to a mailbox within a limited timeframe. This behavior may indicate that a malicious actor has gained control of a mailbox and is conducting discovery or enumeration activities.

MITRE ATT&CK coverage

Rule body splunk

name: O365 Email Suspicious Search Behavior
id: 3b6e1d36-6916-4eec-a7d5-bc98953ba595
version: 6
creation_date: '2025-01-08'
modification_date: '2026-05-13'
author: Steven Dick
status: production
type: Anomaly
description: The following analytic identifies when Office 365 users search for suspicious keywords or have an excessive number of queries to a mailbox within a limited timeframe. This behavior may indicate that a malicious actor has gained control of a mailbox and is conducting discovery or enumeration activities.
data_source:
    - Office 365 Universal Audit Log
search: |-
    `o365_management_activity` Operation=SearchQueryInitiatedExchange
    | eval command = case(Operation=="SearchQueryPerformed",SearchQueryText,true(),QueryText), UserId = lower(UserId), signature_id = CorrelationId, signature=Operation, src = ClientIP, user = lower(UserId), object_name=case(Operation=="SearchQueryPerformed",'EventData',true(),QuerySource), -time = _time, suspect_terms = case(match(command, `o365_suspect_search_terms_regex`),command,true(),null())
    | where command != "*" AND command != "(*)"
    | bin _time span=1hr
    | stats values(ScenarioName) as app, values(object_name) as object_name values(command) as command, values(suspect_terms) as suspect_terms, values(src) as src, dc(suspect_terms) as suspect_terms_count, dc(command) as count, min(-time) as firstTime, max(-time) as lastTime by user,signature,_time
    | where count > 20 OR suspect_terms_count >= 2
    | `security_content_ctime(firstTime)`
    | `security_content_ctime(lastTime)`
    | `o365_email_suspicious_search_behavior_filter`
how_to_implement: You must install the Splunk Microsoft Office 365 Add-on and ingest Office 365 management activity events. You must also enable SearchQueryInitiated category as part of your organizations mailbox audit logging policy. The thresholds and match terms set within the analytic are initial guidelines and should be customized based on the organization's user behavior and risk profile. Security teams are encouraged to adjust these thresholds to optimize the balance between detecting genuine threats and minimizing false positives, ensuring the detection is tailored to their specific environment.
known_false_positives: Users searching excessively or possible false positives related to matching conditions.
references:
    - https://learn.microsoft.com/en-us/purview/audit-get-started#step-3-enable-searchqueryinitiated-events
    - https://www.cisa.gov/sites/default/files/2025-01/microsoft-expanded-cloud-logs-implementation-playbook-508c.pdf
    - https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a
    - https://attack.mitre.org/techniques/T1114/002/
drilldown_searches:
    - name: View the detection results for - "$user$"
      search: '%original_detection_search% | search user = "$user$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$user$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
    - name: Investigate search behavior by $user$
      search: '`o365_management_activity` AND Operation=SearchQueryInitiatedExchange AND UserId = "$user$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
intermediate_findings:
    entities:
        - field: user
          type: user
          score: 20
          message: The user $user$ searched email suspiciously, $count$ unique terms and $suspect_terms_count$ suspect terms were searched within a limited timeframe.
threat_objects:
    - field: src
      type: ip_address
analytic_story:
    - Office 365 Account Takeover
    - Office 365 Collection Techniques
    - Compromised User Account
    - CISA AA22-320A
asset_type: O365 Tenant
mitre_attack_id:
    - T1114.002
    - T1552
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: cloud
security_domain: threat
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1213.002/o365_sus_sharepoint_search/o365_sus_sharepoint_search.log
          source: o365
          sourcetype: o365:management:activity
      test_type: unit

Stages and Predicates

Stage 1: search

`o365_management_activity` Operation=SearchQueryInitiatedExchange

Stage 2: eval

| eval command = case(Operation=="SearchQueryPerformed",SearchQueryText,true(),QueryText), UserId = lower(UserId), signature_id = CorrelationId, signature=Operation, src = ClientIP, user = lower(UserId), object_name=case(Operation=="SearchQueryPerformed",'EventData',true(),QuerySource), -time = _time, suspect_terms = case(match(command, `o365_suspect_search_terms_regex`),command,true(),null())
command =
ifSearchQueryText
elseQueryText
object_name =
if'EventData'
elseQuerySource
suspect_terms =
ifmatch(command, "(?i)password|credential|login|passwd|shadow|active directory|account|username|network|computer|access|MFA|bank|deposit|payroll|EFT|Electonic Funds|routing")command
elsenull()

Stage 3: where

| where command != "*" AND command != "(*)"

Stage 4: bucket

| bin _time span=1hr

Stage 5: stats

| stats values(ScenarioName) as app, values(object_name) as object_name values(command) as command, values(suspect_terms) as suspect_terms, values(src) as src, dc(suspect_terms) as suspect_terms_count, dc(command) as count, min(-time) as firstTime, max(-time) as lastTime by user,signature,_time

Stage 6: where

| where count > 20 OR suspect_terms_count >= 2

Stage 7: search

| `security_content_ctime(firstTime)`

Stage 8: search

| `security_content_ctime(lastTime)`

Stage 9: search

| `o365_email_suspicious_search_behavior_filter`

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
Operationeq
  • SearchQueryInitiatedExchange
commandne
  • "(*)"
  • "*"
countgt
  • 20
sourcetypeeq
  • o365:management:activity
suspect_terms_countge
  • 2