Detection rules › Panther
Panther rules: microsoft365
| Rule | Severity |
|---|---|
| Microsoft365 Brute Force Login by User | medium |
| Microsoft365 External Document Sharing | low |
| Microsoft365 MFA Disabled | low |
Microsoft365 Brute Force Login by User
#A Microsoft365 user was denied login access several times
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | UserLoginFailed |
Rules detecting the same action
These rules filter on the same operation.
Detection logic
from panther_msft_helpers import m365_alert_context
def rule(event):
return event.get("Operation", "") == "UserLoginFailed"
def title(event):
return (
f"Microsoft365: [{event.get('UserId', '<user-not-found>')}] "
"may be undergoing a Brute Force Attack."
)
def alert_context(event):
return m365_alert_context(event)
Rule specification
AnalysisType: rule
Description: A Microsoft365 user was denied login access several times
DisplayName: "Microsoft365 Brute Force Login by User"
Enabled: true
Filename: microsoft365_brute_force_login_by_user.py
Reports:
MITRE ATT&CK:
- TA0006:T1110 # Credential Access - Brute Force
Runbook: Analyze the IP they came from and actions taken before/after.
Reference: https://learn.microsoft.com/en-us/microsoft-365/troubleshoot/authentication/access-denied-when-connect-to-office-365
Severity: Medium
DedupPeriodMinutes: 60
LogTypes:
- Microsoft365.Audit.AzureActiveDirectory
RuleID: "Microsoft365.Brute.Force.Login.by.User"
Threshold: 10
Stages and Predicates
Fires on Microsoft365.Audit.AzureActiveDirectory events when the condition below holds.
Condition
OperationisUserLoginFailed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Operation | eq |
| field:"Operation" kind:eq value:"UserLoginFailed" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
operation | Operation |
organization_id | OrganizationId |
client_ip | ClientIp |
extended_properties | ExtendedProperties |
modified_properties | ModifiedProperties |
application | Application |
actor | Actor |
UserId |
Response runbook
Analyze the IP they came from and actions taken before/after.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"Actor": [
{
"ID": "012345-abcde-543-xyz",
"Type": 0
},
{
"ID": "sample.user@yourorg.onmicrosoft.com",
"Type": 5
}
],
"ActorContextId": "123-abc-xyz-567",
"ActorIpAddress": "1.2.3.4",
"ApplicationId": "123-abc-sfa-321",
"AzureActiveDirectoryEventType": 1,
"ClientIP": "1.2.3.4",
"CreationTime": "2022-12-12 15:57:57",
"ExtendedProperties": [
{
"Name": "ResultStatusDetail",
"Value": "Success"
},
{
"Name": "UserAgent",
"Value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
},
{
"Name": "UserAuthenticationMethod",
"Value": "1"
},
{
"Name": "RequestType",
"Value": "Login:login"
}
],
"Id": "abc-def-123",
"InterSystemsId": "987-432-123",
"IntraSystemId": "aaa-bbb-ccc",
"LogonError": "InvalidUserNameOrPassword",
"ObjectId": "aa-11-22-bb",
"Operation": "UserLoginFailed",
"OrganizationId": "11-aa-22-bb",
"RecordType": 15,
"ResultStatus": "Success",
"SupportTicketId": "",
"Target": [
{
"ID": "11-22-33",
"Type": 0
}
],
"TargetContextId": "11-22-33-44",
"UserId": "sample.user@yourorg.onmicrosoft.com",
"UserKey": "012345-abcde-543-xyz",
"UserType": 0,
"Workload": "AzureActiveDirectory"
}
Microsoft365 External Document Sharing
#Document shared externally
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Collection |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | AnonymousLinkCreated |
Rules detecting the same action
These rules filter on the same operation.
Detection logic
import re
from fnmatch import fnmatch
from panther_base_helpers import EMAIL_REGEX
from panther_msft_helpers import m365_alert_context
ALLOWED_DOMAINS = ["mycompany.com", "alloweddomain.com"] # should be in lowercase
ALLOWED_USERS = ["exception@outsider.com"] # should be in lowercase
ALLOWED_PATHS = ["*/External/*", "External/*"]
def allowed_path(relative_url):
for path in ALLOWED_PATHS:
if fnmatch(relative_url, path):
return True
return False
def rule(event):
if event.get("Operation", "") == "AnonymousLinkCreated":
return not allowed_path(event.get("SourceRelativeUrl"))
if event.get("Operation", "") == "AddedToSecureLink":
if allowed_path(event.get("SourceRelativeUrl")):
return False
target = event.get("TargetUserOrGroupName", "")
if target.lower() in ALLOWED_USERS:
return False
if re.fullmatch(EMAIL_REGEX, target):
if target.split("@")[1].lower() not in ALLOWED_DOMAINS:
return True
return False
def title(event):
return (
f"Microsoft365: [{event.get('SourceRelativeUrl')}] "
"has been shared with external users by "
f"[{event.get('UserId', '<user-not-found>')}]"
)
def alert_context(event):
return m365_alert_context(event)
Rule specification
AnalysisType: rule
Description: Document shared externally
DisplayName: "Microsoft365 External Document Sharing"
Enabled: true
Filename: microsoft365_external_sharing.py
Reports:
MITRE ATT&CK:
- TA0009:T1039 # Collection - Data from Network Shared Drive
Runbook: Check the document metadata to ensure it is not a sensitive document.
Reference: https://support.microsoft.com/en-us/topic/manage-sharing-with-external-users-in-microsoft-365-small-business-2951a85f-c970-4375-aa4f-6b0d7035fe35#:~:text=Top%20of%20Page-,Turn%20external%20sharing%20on%20or%20off,-The%20ability%20to
Severity: Low
DedupPeriodMinutes: 60
LogTypes:
- Microsoft365.Audit.SharePoint
RuleID: "Microsoft365.External.Document.Sharing"
Threshold: 1
Stages and Predicates
Fires on Microsoft365.Audit.SharePoint events when any of the conditions below holds.
Condition
any of:
all of:
OperationisAnonymousLinkCreatedSourceRelativeUrldoes not match the pattern*/External/*SourceRelativeUrldoes not match the patternExternal/*
all of:
Operationis notAnonymousLinkCreatedOperationisAddedToSecureLinkSourceRelativeUrldoes not match the pattern*/External/*SourceRelativeUrldoes not match the patternExternal/*TargetUserOrGroupNameis not one ofexception@outsider.com
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Operation | eq |
| field:"Operation" kind:eq |
Operation | ne |
| field:"Operation" kind:ne value:"AnonymousLinkCreated" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
operation | Operation |
organization_id | OrganizationId |
client_ip | ClientIp |
extended_properties | ExtendedProperties |
modified_properties | ModifiedProperties |
application | Application |
actor | Actor |
SourceRelativeUrl | |
UserId |
Response runbook
Check the document metadata to ensure it is not a sensitive document.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"AppAccessContext": {
"AADSessionId": "aa-bb-cc",
"CorrelationId": "dd-ee-ff"
},
"ClientIP": "1.2.3.4",
"CreationTime": "2022-12-12 19:31:41",
"EventData": "<Type>Edit</Type><MembersCanShareApplied>False</MembersCanShareApplied>",
"EventSource": "SharePoint",
"Id": "111-aa-234",
"ItemType": "File",
"ObjectId": "https://yourorg.sharepoint.com/personal/user_yourorg/Documents/importantsecrets.docx",
"Operation": "AddedToSecureLink",
"OrganizationId": "11-22-abc",
"RecordType": 14,
"Site": "aa-bb-dd-ee-ff",
"SiteUrl": "https://yourorg.sharepoint.com/personal/user_yourorg",
"SourceFileExtension": "docx",
"SourceFileName": "importantsecrets.docx",
"SourceRelativeUrl": "Documents/importantsecrets.docx",
"TargetUserOrGroupName": "OUTSIDER@EXTERNAL.IO",
"TargetUserOrGroupType": "Guest",
"UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
"UserId": "my.user@yourorg.onmicrosoft.com",
"UserKey": "i:0h.f|membership|12345@live.com",
"UserType": 0,
"Workload": "OneDrive"
}
Microsoft365 MFA Disabled
#A user's MFA has been removed
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | Update user. |
Detection logic
import json
from panther_msft_helpers import m365_alert_context
def rule(event):
if event.get("Operation", "") == "Update user.":
modified_properties = event.get("ModifiedProperties", [])
for prop in modified_properties:
if prop.get("Name", "") == "StrongAuthenticationMethod":
new_value = prop.get("NewValue")
old_value = prop.get("OldValue")
if isinstance(new_value, str):
new_value = json.loads(new_value)
if isinstance(old_value, str):
old_value = json.loads(old_value)
if old_value and not new_value:
return True
break
return False
def title(event):
return "Microsoft365: MFA Removed on " f"[{event.get('ObjectId', '')}]"
def alert_context(event):
return m365_alert_context(event)
Rule specification
AnalysisType: rule
Description: A user's MFA has been removed
DisplayName: "Microsoft365 MFA Disabled"
Enabled: true
Filename: microsoft365_mfa_disabled.py
Reports:
MITRE ATT&CK:
- TA003:T1556 # Persistence - Modify Authentication Process
- TA005:T1556 # Defense Evansion - Modify Authentication Process
- TA006:T1556 # Credential Access - Modify Authentication Process
Runbook: Depending on company policy, either suggest or require the user re-enable two step verification.
Reference: https://learn.microsoft.com/en-us/microsoft-365/admin/security-and-compliance/set-up-multi-factor-authentication?view=o365-worldwide
Severity: Low
DedupPeriodMinutes: 60
LogTypes:
- Microsoft365.Audit.AzureActiveDirectory
RuleID: "Microsoft365.MFA.Disabled"
Threshold: 1
Stages and Predicates
Fires on Microsoft365.Audit.AzureActiveDirectory events when the condition below holds.
Condition
OperationisUpdate user.
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Operation | eq |
| field:"Operation" kind:eq value:"Update user." |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
operation | Operation |
organization_id | OrganizationId |
client_ip | ClientIp |
extended_properties | ExtendedProperties |
modified_properties | ModifiedProperties |
application | Application |
actor | Actor |
ObjectId |
Response runbook
Depending on company policy, either suggest or require the user re-enable two step verification.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"Actor": [
{
"ID": "Azure MFA StrongAuthenticationService",
"Type": 1
},
{
"ID": "ABC-123",
"Type": 2
},
{
"ID": "ServicePrincipal_123-abc",
"Type": 2
},
{
"ID": "321-cba",
"Type": 2
},
{
"ID": "ServicePrincipal",
"Type": 2
}
],
"ActorContextId": "123-abc-456",
"AzureActiveDirectoryEventType": 1,
"CreationTime": "2022-12-12 17:28:35",
"ExtendedProperties": [
{
"Name": "additionalDetails",
"Value": "{\"UserType\":\"Member\"}"
},
{
"Name": "extendedAuditEventCategory",
"Value": "User"
}
],
"Id": "123-abc-123",
"InterSystemsId": "abc-123-321",
"IntraSystemId": "aa-bbb-333",
"ModifiedProperties": [
{
"Name": "StrongAuthenticationMethod",
"NewValue": "[]",
"OldValue": "[{\"Default\": true,\"MethodType\": 7}]"
},
{
"Name": "Included Updated Properties",
"NewValue": "StrongAuthenticationMethod",
"OldValue": ""
},
{
"Name": "TargetId.UserType",
"NewValue": "Member",
"OldValue": ""
}
],
"ObjectId": "sample.user@yourorg.onmicrosoft.com",
"Operation": "Update user.",
"OrganizationId": "111-222-333",
"RecordType": 8,
"ResultStatus": "Success",
"SupportTicketId": "",
"Target": [
{
"ID": "User_111-222-bbb",
"Type": 2
},
{
"ID": "111-aa-bbb-321",
"Type": 2
},
{
"ID": "User",
"Type": 2
},
{
"ID": "sample.user@yourorg.onmicrosoft.com",
"Type": 5
},
{
"ID": "123abcdef",
"Type": 3
}
],
"TargetContextId": "aaa-bb-222",
"UserId": "ServicePrincipal_aa-bb-ccc",
"UserKey": "Not Available",
"UserType": 4,
"Workload": "AzureActiveDirectory"
}