We came across a case where Microsoft Defender for Office 365 (MDO) had been changed and no one in the organization wasn’t aware of the change. MDO policies are rarely changed (or should be) and configuration changes should be monitored to prevent possible malicious actors cause harm to the organization. An example of this could be a rogue admin (I don’t claim that there is one) who could change the settings.

Monitoring of service level settings is a similar scenario to monitoring Azure AD log ingestion to Sentinel or changes to Conditional Access Policies and maps to MITRE ATT&CK framework to the following TTPs:

  • T1562 – Impair Defenses
  • T1565 – Data Manipulation

In this blog post, we explain and demonstrate how changes in Defender for Office 365 (MDO) can be tracked and where you can find the audit data. How to create analytics rule in Sentinel – I will leave this one to you:). Research for this blog post was done by my colleague ‘Saku and me’.

  1. Service Level Settings & Policies
  2. Scenario 1 – Disabling global security features
    1. KQL
  3. Scenario 2 – Evasion by modifying a phish/threat policy
    1. KQL
      1. Anti-phishing policy – Individual setting is changed
      2. Anti-Phishing Policy is disabled
  4. Scenario 3 – Malware Filter Policies are Disabled
    1. KQL
  5. Summary

Service Level Settings & Policies

Microsoft Defender for Office 365 (MDO) provides the possibility to protect collaboration workloads with different security mechanisms. Even though, the solutions have been around for years already they have been lacking in one area which is the auditing of the activities.

The audit events of the activities described in this blog are found in two places: O365 Management API & Defender for Cloud Apps (MDA). I’m not surprised by the latter one, MDA has saved me many times:). When audit data is found from O365 Management API it means it’s possible to ingest the needed data to Log Analytics (+Sentinel).

Scenario 1 – Disabling global security features

MDO has service level settings, also known as Global settings which admins should change very rarely. For that reason, it would be a good idea to track possible changes in that area.

Affects the following threat policies:

  • Safe Attachments
  • Safe Links

KQL

Here you can find queries for both, OfficeActivity & CloudAppEvent tables depending on what you ingest into Log Analytics. Data is parsed a bit differently between tables but both tables have the same data.

// Query changes for Global MDO ATP Policy (Set-AtpPolicyForO365) in OfficeActivity table
OfficeActivity 
OfficeActivity 
| where TimeGenerated > ago(1d) 
| where RecordType contains "ExchangeAdmin" 
| where Operation contains "Set-AtpPolicyForO365" 
| extend ['ATPForSPOTeamsODB'] = tostring(parse_json(Parameters)[1].Name)
| extend ['ATPForSPOTeamsODBValue'] = tostring(parse_json(Parameters)[1].Value)
| extend ['EnableSafeDocs'] = tostring(parse_json(Parameters)[2].Name) 
| extend ['EnableSafeDocsValue'] = tostring(parse_json(Parameters)[2].Value) 
| extend ['AllowSafeDocsOpen'] = tostring(parse_json(Parameters)[3].Name) 
| extend ['AllowSafeDocsOpenValue'] = tostring(parse_json(Parameters)[3].Value)
| where ATPForSPOTeamsODBValue == 'False' or EnableSafeDocs contains "False" or AllowSafeDocsOpen contains "False"

// Query changes for Global MDO ATP Policy (Set-AtpPolicyForO365) in Defender for Cloud Apps table
CloudAppEvents
CloudAppEvents
| where TimeGenerated > ago(1d) 
| where ActionType == "Set-AtpPolicyForO365"
| where Application == "Microsoft Exchange Online"
| extend AppId = tostring(RawEventData.AppId)
| extend ['EnableATPForSPOTeamsODB'] = tostring(ActivityObjects[3].Name)
| extend ['EnableATPForSPOTeamsODBValue'] = tostring(ActivityObjects[3].Value)
| extend ['EnableSafeDocs'] = tostring(ActivityObjects[4].Name)
| extend ['EnableSafeDocsValue'] = tostring(ActivityObjects[4].Value)
| extend ['AllowSafeDocsOpen'] = tostring(ActivityObjects[5].Name)
| extend ['AllowSafeDocsOpenValue'] = tostring(ActivityObjects[5].Value)
| where EnableATPForSPOTeamsODBValue contains "False" or EnableSafeDocs contains "False" or AllowSafeDocsOpen contains "False"

The following queries show values of all the ‘Safe Links’ policy parameters just for example. On the last line, there is a detection for when some of the following settings are turned into a false state:

  • EnableSafeLinksForTeams
  • EnableSafeLinksForEmail
  • EnableSafeLinksForOffice
  • EnableForInternalSenders
// Query changes for SafeLinks Policy in OfficeActivity table
OfficeActivity 
OfficeActivity 
| where TimeGenerated > ago(1d) 
| where RecordType contains "ExchangeAdmin"
| where OfficeWorkload == "Exchange"
| where Operation contains "Set-SafeLinksPolicy"
| extend ['PolicyName'] = tostring(parse_json(Parameters)[6].Value)
| extend ['ScanUrls'] = tostring(parse_json(Parameters)[1].Name)
| extend ['ScanUrlsValue'] = tostring(parse_json(Parameters)[1].Value)
| extend ['DoNotRewriteUrls'] = tostring(parse_json(Parameters)[2].Name)
| extend ['DoNotRewriteUrlsValue'] = tostring(parse_json(Parameters)[2].Value)
| extend ['EnableSafeLinksForTeams'] = tostring(parse_json(Parameters)[4].Name)
| extend ['EnableSafeLinksForTeamsValue'] = tostring(parse_json(Parameters)[4].Value)
| extend ['EnableSafeLinksForEmail'] = tostring(parse_json(Parameters)[5].Name)
| extend ['EnableSafeLinksForEmailValue'] = tostring(parse_json(Parameters)[5].Value)
| extend ['DisableUrlRewrite'] = tostring(parse_json(Parameters)[7].Name)
| extend ['DisableUrlRewriteValue'] = tostring(parse_json(Parameters)[7].Value)
| extend ['EnableForInternalSenders'] = tostring(parse_json(Parameters)[8].Name)
| extend ['EnableForInternalSendersValue'] = tostring(parse_json(Parameters)[8].Value)
| extend ['AllowClickThrough'] = tostring(parse_json(Parameters)[9].Name)
| extend ['AllowClickThroughValue'] = tostring(parse_json(Parameters)[9].Value)
| extend ['Identity'] = tostring(parse_json(Parameters)[10].Name)
| extend ['IdentityValue'] = tostring(parse_json(Parameters)[10].Value)
| extend ['EnableSafeLinksForOffice'] = tostring(parse_json(Parameters)[12].Name)
| extend ['EnableSafeLinksForOfficeValue'] = tostring(parse_json(Parameters)[12].Value)
| extend ['TrackClicks'] = tostring(parse_json(Parameters)[14].Name)
| extend ['TrackClicksValue'] = tostring(parse_json(Parameters)[14].Value)
| where EnableSafeLinksForTeamsValue == 'False' or EnableSafeLinksForEmailValue == 'False' or EnableSafeLinksForOfficeValue == 'False' or EnableForInternalSendersValue == 'False'



// Query changes for SafeLinks Policy in OfficeActivity table
CloudAppEvents
| where TimeGenerated > ago(7d) 
| where Application contains "Microsoft Exchange Online"
| where ApplicationId == '20893'
| where ActionType contains "Set-SafeLinksPolicy"
| extend ['PolicyName'] = tostring(ActivityObjects[8].Name)
| extend ['PolicyValue'] = tostring(ActivityObjects[8].Value)
| extend ['ScanUrls'] = tostring(parse_json(tostring(RawEventData.Parameters))[1].Name)
| extend ['ScanUrlsValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[1].Value)
| extend ['DoNotRewriteUrls'] = tostring(parse_json(tostring(RawEventData.Parameters))[2].Name)
| extend ['DoNotRewriteUrlsValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[2].Value)
| extend ['EnableSafeLinksForTeams'] = tostring(parse_json(tostring(RawEventData.Parameters))[4].Name)
| extend ['EnableSafeLinksForTeamsValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[4].Value)
| extend ['EnableSafeLinksForEmail'] = tostring(parse_json(tostring(RawEventData.Parameters))[5].Name)
| extend ['EnableSafeLinksForEmailValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[5].Value)
| extend ['DisableUrlRewrite'] = tostring(parse_json(tostring(RawEventData.Parameters))[7].Name)
| extend ['DisableUrlRewriteValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[7].Value)
| extend ['EnableForInternalSenders'] = tostring(parse_json(tostring(RawEventData.Parameters))[8].Name)
| extend ['EnableForInternalSendersValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[8].Value)
| extend ['AllowClickThrough'] = tostring(parse_json(tostring(RawEventData.Parameters))[9].Name)
| extend ['AllowClickThroughValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[9].Value)
| extend ['Identity'] = tostring(parse_json(tostring(RawEventData.Parameters))[10].Name)
| extend ['IdentityValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[10].Value)
| extend ['EnableSafeLinksForOffice'] = tostring(parse_json(tostring(RawEventData.Parameters))[12].Name)
| extend ['EnableSafeLinksForOfficeValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[12].Value)
| extend ['TrackClicks'] = tostring(parse_json(tostring(RawEventData.Parameters))[14].Name)
| extend ['TrackClicksValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[14].Value)
| where EnableSafeLinksForTeamsValue == 'False' or EnableSafeLinksForEmailValue == 'False' or EnableSafeLinksForOfficeValue == 'False' or EnableForInternalSendersValue == 'False'

Scenario 2 – Evasion by modifying a phish/threat policy

Phishing is an email attack that tries to steal sensitive information in messages that appear to be from legitimate or trusted senders. There are specific categories of phishing and here are some examples, of where MDO provides protection:

  • Spear phishing
  • Whaling 
  • Business email compromise (BEC)
  • Ransomware 

There are two (2) scenarios that are described here:

  • Change of an individual setting
  • The policy is turned off

Anti Phishing policy has a huge number of settings and we wanted to have detection when the individual setting is changed. How this is achieved (example of modifying users):

  • TargetUsersToProtected is key to success here
    • When the policy is turned off, the value of the ‘TargetedUserToProtect’ will be empty as seen below

KQL

Affects the following policies:

  • Anti-phishing policy – Individual setting is changed
  • Anti-phishing policy – Is set off

Anti-phishing policy – Individual setting is changed

// Show Anti-Phishing Policy changes - query from OfficeActvity table
OfficeActivity
|where TimeGenerated > ago(1d)
| where Operation contains "Set-AntiPhishPolicy"
| where OfficeWorkload == "Exchange"
| where RecordType == "ExchangeAdmin"
| extend ['TargetedUsersToProtect'] = tostring(parse_json(Parameters)[17].Name)
| extend ['TargetedUsersToProtectValue'] = tostring(parse_json(Parameters)[17].Value)
| where isempty(TargetedUsersToProtectValue)

// Show Anti-Phishing Policy changes - query from CloudAppEvents table
CloudAppEvents
CloudAppEvents
| where TimeGenerated > ago(1d)
| where ActionType contains "Set-AntiPhishPolicy"
| extend ['TargetedUsersToProtect'] = tostring(parse_json(tostring(RawEventData.Parameters))[17].Name)
| extend ['TargetedUsersToProtectValue'] = tostring(parse_json(tostring(RawEventData.Parameters))[17].Value) 
| where isempty(TargetedUsersToProtectValue)

Anti-Phishing Policy is disabled

// Anti-Phisping Policy is turned off - query from OfficeActvity table
OfficeActivity
| where TimeGenerated > ago(1d)
| where Operation contains "Disable-AntiPhishRule"
| where OfficeWorkload == "Exchange"
| where RecordType == "ExchangeAdmin"
| where ResultStatus == "True"


// Anti-Phisping Policy is turned off - query from CloudAppEvents table
CloudAppEvents
| where TimeGenerated > ago(1d)
| where ActionType contains "Disable-AntiPhishRule"
| where Application == "Microsoft Exchange Online"
| where ApplicationId == 20893
| extend UserId_ = tostring(RawEventData.UserId)

Scenario 3 – Malware Filter Policies are Disabled

Unwanted changes in the malware policies can end up in a disaster. That being said, it’s crucial to monitor changes in the policy state (on/off) and the most critical changes in the policy configuration.

Things that should be paid attention to for malware filters are the changes in the zero-hour purge and/or changes in the file scan options. It would be possible for an attacker to modify the files being stopped by a “hard-coded” configuration of anti-malware policy and zero-hour configuration. The secondary method would be changed to the quarantine policy being used, granting everyone permission to release messages from Quarantine.

KQL

Here you can find an example query for the scenario where the Anti-malware policy is turned off.

Affects the following policies:

  • Anti-Malware policies
// Anti-MalwareFilter Rule Policy is turned off - query from OfficeActvity table
OfficeActivity
| where TimeGenerated > ago(1d)
| where Operation contains "Disable-MalwareFilterRule"
| where OfficeWorkload == "Exchange"
| where RecordType == "ExchangeAdmin"
| where ResultStatus == "True"

// Anti-MalwareFilter Rule Policy is turned off - query from OfficeActvity table
CloudAppEvents
| where TimeGenerated > ago(1d)
| where ActionType contains "Disable-MalwareFilterRule"
| where Application == "Microsoft Exchange Online"
| where ApplicationId == 20893
| extend UserId_ = tostring(RawEventData.UserId)

Summary

In general, monitoring of cloud security solutions service level settings is often neglected even though changes in the setting can have unwanted side effects. I highly recommend considering monitoring service level settings & data streams in all security solutions one way or another.

All queries presented in this blog can be found in my GitHub repo.