← Back to SOC feed Coverage →

Suspect Mailbox Export on IIS/OWA

kql LOW Azure-Sentinel
T1567
W3CIISLog
backdoorhuntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-06-04T23:00:00Z · Confidence: medium

Hunt Hypothesis

Adversaries may exfiltrate mailbox data by accessing suspicious files on an IIS/OWA server, leveraging T1567 to covertly transfer sensitive information. SOC teams should proactively hunt for this behavior to detect potential data exfiltration attempts in their Azure Sentinel environment.

KQL Query


let excludeIps = dynamic(["127.0.0.1", "::1"]);
let scriptingExt = dynamic(["aspx", "ashx", "asp"]);
W3CIISLog
| where csUriStem contains "/owa/"
//The actor pulls a file back but won't send it any URI params
| where isempty(csUriQuery)
| extend file_ext = tostring(split(csUriStem, ".")[-1])
//Giving your file a known scripting extension will throw an error
//rather than just serving the file as it will try to interpret the script
| where file_ext !in~ (scriptingExt)
//The actor was seen using image files, but we go wider in case they change this behaviour
//| where file_ext in~ ("jpg", "jpeg", "png", "bmp")
| extend file_name = tostring(split(csUriStem, "/")[-1])
| where file_name != ""
| where cIP !in~ (excludeIps)
//Exclude local addresses, using the ipv4_is_private operator
| where ipv4_is_private(cIP) == false and  cIP !startswith "fe80" and cIP !startswith "::" and cIP !startswith "127."
| project file_ext, csUriStem, file_name, Computer, cIP, sIP, TenantId, TimeGenerated
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), dcount(cIP), AccessingIPs=make_set(cIP), AccessTimes=make_set(TimeGenerated), Access=count() by TenantId, file_name, Computer, csUriStem
//Collection of the exfiltration will occur only once, lets check for 2 accesses in case they mess up
//Tailor this for hunting
| where Access <= 2 and dcount_cIP == 1
| extend timestamp = StartTime, HostCustomEntity = Computer

Analytic Rule Definition

id: a523786c-8382-4029-80e9-f1a7ecd067c1
name: Suspect Mailbox Export on IIS/OWA
description: |
  'The hunting query looks for suspicious files accessed on a IIS server that might indicate exfiltration hosting. This technique has been observed when exporting mailbox files from OWA servers.'
description_detailed: |
  'The hunting query looks for suspicious files accessed on a IIS server that might indicate exfiltration hosting.
  This technique has been observed when exporting mailbox files from OWA servers.
  Reference: https://www.volexity.com/blog/2020/12/14/dark-halo-leverages-solarwinds-compromise-to-breach-organizations/'
severity: Low
requiredDataConnectors:
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
tags:
  - Solorigate
  - NOBELIUM
query: |

  let excludeIps = dynamic(["127.0.0.1", "::1"]);
  let scriptingExt = dynamic(["aspx", "ashx", "asp"]);
  W3CIISLog
  | where csUriStem contains "/owa/"
  //The actor pulls a file back but won't send it any URI params
  | where isempty(csUriQuery)
  | extend file_ext = tostring(split(csUriStem, ".")[-1])
  //Giving your file a known scripting extension will throw an error
  //rather than just serving the file as it will try to interpret the script
  | where file_ext !in~ (scriptingExt)
  //The actor was seen using image files, but we go wider in case they change this behaviour
  //| where file_ext in~ ("jpg", "jpeg", "png", "bmp")
  | extend file_name = tostring(split(csUriStem, "/")[-1])
  | where file_name != ""
  | where cIP !in~ (excludeIps)
  //Exclude local addresses, using the ipv4_is_private operator
  | where ipv4_is_private(cIP) == false and  cIP !startswith "fe80" and cIP !startswith "::" and cIP !startswith "127."
  | project file_ext, csUriStem, file_name, Computer, cIP, sIP, TenantId, TimeGenerated
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), dcount(cIP), AccessingIPs=make_set(cIP), AccessTimes=make_set(TimeGenerated), Access=count() by TenantId, file_name, Computer, csUriStem
  //Collection of the exfiltration will occur only once, lets check for 2 accesses in case they mess up
  //Tailor this for hunting
  | where Access <= 2 and dcount_cIP == 1
  | extend timestamp = StartTime, HostCustomEntity = Computer
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
version: 1.0.1
metadata:
    source:
        kind: Community
    author:
        name: Pete Bryan
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Required Data Sources

Sentinel TableNotes
W3CIISLogEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/W3CIISLog/SuspectedMailBoxExportHostonOWA.yaml