From 9931ec0f8b73d89cbb097c7c5d8f558c497f9870 Mon Sep 17 00:00:00 2001 From: Shain <45466083+shainw@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:45:29 -0800 Subject: [PATCH 1/3] commit first update --- ...palAssignedAppRoleWithSensitiveAccess.yaml | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml index 94e1c20119..ef78b7dcce 100644 --- a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml +++ b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml @@ -21,42 +21,68 @@ relevantTechniques: tags: - AADSecOpsGuide query: | - // Add other permissions to this list as needed - let permissions = dynamic(["Mail.Read", "offline_access", "Files.Read", "Notes.Read", "ChannelMessage.Read", "Chat.Read", "TeamsActivity.Read", + // Add other permissions to this list as needed + let permissions = dynamic([".All", "ReadWrite", "Mail.", "offline_access", "Files.Read", "Notes.Read", "ChannelMessage.Read", "Chat.Read", "TeamsActivity.Read", "Group.Read", "EWS.AccessAsUser.All", "EAS.AccessAsUser.All"]); + let auditList = AuditLogs | where OperationName =~ "Add app role assignment to service principal" | mv-expand TargetResources[0].modifiedProperties | extend TargetResources_0_modifiedProperties = column_ifexists("TargetResources_0_modifiedProperties", '') | where isnotempty(TargetResources_0_modifiedProperties) + ; + let detailsList = auditList | where TargetResources_0_modifiedProperties.displayName =~ "AppRole.Value" or TargetResources_0_modifiedProperties.displayName =~ "DelegatedPermissionGrant.Scope" | extend Permissions = split((parse_json(tostring(TargetResources_0_modifiedProperties.newValue))), " ") | where Permissions has_any (permissions) | summarize AddedPermissions=make_set(Permissions,200) by CorrelationId - | join kind=inner (AuditLogs - | where OperationName =~ "Add app role assignment to service principal") on CorrelationId - | extend InitiatedBy = tostring(iff(isnotempty(InitiatedBy.user.userPrincipalName),InitiatedBy.user.userPrincipalName, InitiatedBy.app.displayName)) - | extend ServicePrincipal = tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[4].newValue))) - | extend SPID = tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[6].newValue))) - | extend InitiatedBy = pack("User", InitiatedBy, "UA", column_ifexists("UserAgent",''), "IPAddress", column_ifexists("IpAddress",'')) - | mv-expand kind=array AddedPermissions - | summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), make_set(InitiatedBy,200), make_set(AddedPermissions,200) by SPID, ServicePrincipal + | join kind=inner auditList on CorrelationId + | extend InitiatingAppName = tostring(InitiatedBy.app.displayName) + | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId) + | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | extend InitiatingAadUserId = tostring(InitiatedBy.user.id) + | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress) + | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) + | extend displayName = tostring(TargetResources_0_modifiedProperties.displayName), newValue = tostring(parse_json(tostring(TargetResources_0_modifiedProperties.newValue))) + | where displayName == "ServicePrincipal.ObjectID" or displayName == "ServicePrincipal.DisplayName" + | project TimeGenerated, CorrelationId, Id, AddedPermissions = tostring(AddedPermissions), InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIPAddress, InitiatingUserPrincipalName, InitiatedBy, displayName, newValue + ; + detailsList | project Id, displayName, newValue + | evaluate pivot(displayName, make_set(newValue)) + | join kind=inner detailsList on Id + | mv-expand ['ServicePrincipal.ObjectID'], ['ServicePrincipal.DisplayName'] + | project-away Id1, displayName, newValue + | extend ServicePrincipalObjectID = tostring(['ServicePrincipal.ObjectID']), ServicePrincipalDisplayName = tostring(['ServicePrincipal.DisplayName']) + | summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), EventIds = make_set(Id,200) by CorrelationId, AddedPermissions, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIPAddress, InitiatingUserPrincipalName, InitiatedBy, ServicePrincipalDisplayName, ServicePrincipalObjectID + | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]) entityMappings: - entityType: Account fieldMappings: - identifier: Name - columnName: ServicePrincipal + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - identifier: AadUserId + columnName: InitiatingAadUserId - entityType: Account fieldMappings: - identifier: AadUserId - columnName: SPID -version: 1.0.3 + columnName: InitiatingAppServicePrincipalId + - entityType: Account + fieldMappings: + - identifier: ObjectGuid + columnName: ServicePrincipalObjectID + - entityType: IP + fieldMappings: + - identifier: Address + columnName: InitiatingIPAddress +version: 2.0.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: From 92bd6411f0c3aaf2a61d4c6b650a6c5c1f273c37 Mon Sep 17 00:00:00 2001 From: Shain <45466083+shainw@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:28:05 -0800 Subject: [PATCH 2/3] fixing typo of ObjectID --- .../ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml index ef78b7dcce..716f1d60b4 100644 --- a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml +++ b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml @@ -45,14 +45,16 @@ query: | | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) | extend displayName = tostring(TargetResources_0_modifiedProperties.displayName), newValue = tostring(parse_json(tostring(TargetResources_0_modifiedProperties.newValue))) | where displayName == "ServicePrincipal.ObjectID" or displayName == "ServicePrincipal.DisplayName" + | extend displayName = case(displayName == "ServicePrincipal.ObjectID", "ServicePrincipalObjectID", displayName == "ServicePrincipal.DisplayName", "ServicePrincipalDisplayName", displayName) | project TimeGenerated, CorrelationId, Id, AddedPermissions = tostring(AddedPermissions), InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIPAddress, InitiatingUserPrincipalName, InitiatedBy, displayName, newValue ; detailsList | project Id, displayName, newValue | evaluate pivot(displayName, make_set(newValue)) | join kind=inner detailsList on Id - | mv-expand ['ServicePrincipal.ObjectID'], ['ServicePrincipal.DisplayName'] + | extend ServicePrincipalObjectID = todynamic(column_ifexists("ServicePrincipalObjectID", "")), ServicePrincipalDisplayName = todynamic(column_ifexists("ServicePrincipalDisplayName", "")) + | mv-expand ServicePrincipalObjectID, ServicePrincipalDisplayName | project-away Id1, displayName, newValue - | extend ServicePrincipalObjectID = tostring(['ServicePrincipal.ObjectID']), ServicePrincipalDisplayName = tostring(['ServicePrincipal.DisplayName']) + | extend ServicePrincipalObjectID = tostring(ServicePrincipalObjectID), ServicePrincipalDisplayName = tostring(ServicePrincipalDisplayName) | summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), EventIds = make_set(Id,200) by CorrelationId, AddedPermissions, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIPAddress, InitiatingUserPrincipalName, InitiatedBy, ServicePrincipalDisplayName, ServicePrincipalObjectID | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]) entityMappings: From 7bb12216385a50047a09d9e7c01f6ce0241b42a5 Mon Sep 17 00:00:00 2001 From: Shain <45466083+shainw@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:19:59 -0800 Subject: [PATCH 3/3] Fixing up remaining AuditLog detections with entity mappings --- ...palAssignedAppRoleWithSensitiveAccess.yaml | 6 +- ...ervicePrincipalAssignedPrivilegedRole.yaml | 55 ++++++++++++------- ...ousLinkingofExternalIdtoExistingUsers.yaml | 53 +++++++++++++----- ...RLAddedtoApplicationfromUnknownDomain.yaml | 36 ++++++++---- ...ountCreatedUsingIncorrectNamingFormat.yaml | 54 +++++++++++++----- .../UserStatechangedfromGuesttoMember.yaml | 39 +++++++++++-- ...eatedwithoutexpectedattributesdefined.yaml | 52 +++++++++++++----- 7 files changed, 215 insertions(+), 80 deletions(-) diff --git a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml index 716f1d60b4..b095e85711 100644 --- a/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml +++ b/Detections/AuditLogs/ServicePrincipalAssignedAppRoleWithSensitiveAccess.yaml @@ -60,18 +60,20 @@ query: | entityMappings: - entityType: Account fieldMappings: + - identifier: FullName + columnName: InitiatingUserPrincipalName - identifier: Name columnName: InitiatingAccountName - identifier: UPNSuffix columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: - identifier: AadUserId columnName: InitiatingAadUserId - entityType: Account fieldMappings: - identifier: AadUserId columnName: InitiatingAppServicePrincipalId - - entityType: Account - fieldMappings: - identifier: ObjectGuid columnName: ServicePrincipalObjectID - entityType: IP diff --git a/Detections/AuditLogs/ServicePrincipalAssignedPrivilegedRole.yaml b/Detections/AuditLogs/ServicePrincipalAssignedPrivilegedRole.yaml index 88d30cce54..91aa2bec25 100644 --- a/Detections/AuditLogs/ServicePrincipalAssignedPrivilegedRole.yaml +++ b/Detections/AuditLogs/ServicePrincipalAssignedPrivilegedRole.yaml @@ -26,40 +26,57 @@ query: | | extend type_ = tostring(TargetResources[0].type) | where type_ =~ "ServicePrincipal" | where isnotempty(TargetResources) - | extend ServicePrincipal = tostring(TargetResources[0].displayName) - | extend SPID = tostring(TargetResources[0].id) + | extend InitiatingAppName = tostring(InitiatedBy.app.displayName) + | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId) + | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | extend InitiatingAadUserId = tostring(InitiatedBy.user.id) + | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress) + | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) + | extend ServicePrincipalName = tostring(TargetResources[0].displayName) + | extend ServicePrincipalId = tostring(TargetResources[0].id) | mv-expand TargetResources[0].modifiedProperties | extend TargetResources_0_modifiedProperties = columnifexists("TargetResources_0_modifiedProperties", '') | where isnotempty(TargetResources_0_modifiedProperties) - | where TargetResources_0_modifiedProperties.displayName =~ "Role.DisplayName" - | extend TargetRole = parse_json(tostring(TargetResources_0_modifiedProperties.newValue)) - | where TargetRole contains "admin" - | extend AddedByApp = iif( - isnotempty(tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalName)), - tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalName), - tostring(parse_json(tostring(InitiatedBy.app)).displayName) - ) - | extend AddedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) - | extend AddedBy = iif(isnotempty(AddedByApp), AddedByApp, AddedByUser) - | extend IpAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) - | project-reorder TimeGenerated, ServicePrincipal, SPID, TargetRole, AddedBy, IpAddress - | project-away AddedByApp, AddedByUser + | extend displayName = tostring(TargetResources_0_modifiedProperties.displayName), newValue = tostring(parse_json(tostring(TargetResources_0_modifiedProperties.newValue))) + | where displayName == "Role.DisplayName" and newValue contains "admin" + | extend TargetRole = newValue + | project-reorder TimeGenerated, ServicePrincipalName, ServicePrincipalId, InitiatedBy, TargetRole, InitiatingIPAddress entityMappings: - entityType: Account fieldMappings: + - identifier: Name + columnName: InitiatingAppName - identifier: AadUserId - columnName: SPID + columnName: InitiatingAppServicePrincipalId - entityType: Account fieldMappings: - identifier: FullName - columnName: AddedBy -version: 1.0.1 + columnName: InitiatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: InitiatingAadUserId + - entityType: Account + fieldMappings: + - identifier: Name + columnName: ServicePrincipalName + - identifier: AadUserId + columnName: ServicePrincipalId + - entityType: IP + fieldMappings: + - identifier: Address + columnName: InitiatingIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: diff --git a/Detections/AuditLogs/SuspiciousLinkingofExternalIdtoExistingUsers.yaml b/Detections/AuditLogs/SuspiciousLinkingofExternalIdtoExistingUsers.yaml index d51df6b3a3..8c1ff749a6 100644 --- a/Detections/AuditLogs/SuspiciousLinkingofExternalIdtoExistingUsers.yaml +++ b/Detections/AuditLogs/SuspiciousLinkingofExternalIdtoExistingUsers.yaml @@ -19,38 +19,63 @@ relevantTechniques: tags: - GuestorExternalIdentities query: | - let lookback = 1d; - AuditLogs - | where TimeGenerated > ago(lookback) + AuditLogs | where OperationName=~ "Update user" | where Result =~ "success" | mv-expand TargetResources | mv-expand TargetResources.modifiedProperties - | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName) , oldValue_ = tostring(TargetResources_modifiedProperties.oldValue), newValue_ = tostring(TargetResources_modifiedProperties.newValue) - | where displayName_ == "UserPrincipalName" and oldValue_ !has "#EXT" and newValue_ has "#EXT" - | extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName) - | extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)) , IPAddress = tostring(InitiatedBy.["user"].["ipAddress"]) - | project TimeGenerated, AADTenantId, IPAddress, Initiator, displayName_, oldValue_, newValue_ + | extend displayName = tostring(TargetResources_modifiedProperties.displayName), + TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]), + TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0]) + | where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT" + | extend InitiatingAppName = tostring(InitiatedBy.app.displayName) + | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId) + | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | extend InitiatingAadUserId = tostring(InitiatedBy.user.id) + | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress) + | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) + | summarize arg_max(TimeGenerated, *) by CorrelationId + | project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue + | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]) + | extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1]) entityMappings: - entityType: Account fieldMappings: - - identifier: FullName - columnName: Initiator + - identifier: Name + columnName: InitiatingAppName + - identifier: AadUserId + columnName: InitiatingAppServicePrincipalId - entityType: Account fieldMappings: - identifier: FullName - columnName: displayName_ + columnName: InitiatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: InitiatingAadUserId + - entityType: Account + fieldMappings: + - identifier: FullName + columnName: TargetUPN_oldValue + - identifier: Name + columnName: TargetAccountName + - identifier: UPNSuffix + columnName: TargetUPNSuffix - entityType: IP fieldMappings: - identifier: Address - columnName: IPAddress -version: 1.0.2 + columnName: InitiatingIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Ashwin Patil + name: Microsoft Security Research support: tier: Community categories: diff --git a/Detections/AuditLogs/URLAddedtoApplicationfromUnknownDomain.yaml b/Detections/AuditLogs/URLAddedtoApplicationfromUnknownDomain.yaml index 2f58f26ef6..723c43f1da 100644 --- a/Detections/AuditLogs/URLAddedtoApplicationfromUnknownDomain.yaml +++ b/Detections/AuditLogs/URLAddedtoApplicationfromUnknownDomain.yaml @@ -45,12 +45,13 @@ query: | | extend AddedUrls = set_difference(NewUrls, OldUrls) | where array_length(AddedUrls) > 0 | extend UserAgent = iif(tostring(AdditionalDetails[0].key) == "User-Agent", tostring(AdditionalDetails[0].value), "") - | extend AddingUser = iif(isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)) , tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName), "") - | extend AddingApp = iif(isnotempty(tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalName)) , tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalName), "") - | extend AddedBy = iif(isnotempty(AddingUser), AddingUser, AddingApp) - | project-away AddingApp, AddingUser + | extend InitiatingAppName = tostring(InitiatedBy.app.displayName) + | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId) + | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | extend InitiatingAadUserId = tostring(InitiatedBy.user.id) + | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress) + | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) | extend AppDisplayName = tostring(TargetResources.displayName) - | extend ipAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) | where isnotempty(AddedUrls) | mv-expand AddedUrls | extend AddedUrls = trim(@'"', tostring(AddedUrls)) @@ -58,27 +59,42 @@ query: | | where isnotempty(Domain) | extend Domain = strcat(split(Domain, ".")[-2], ".", split(Domain, ".")[-1]) | where Domain !in (domains) - | project-reorder TimeGenerated, AppDisplayName, AddedUrls, AddedBy, UserAgent, ipAddress + | project-reorder TimeGenerated, AppDisplayName, AddedUrls, InitiatedBy, UserAgent, InitiatingIPAddress + | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]) entityMappings: - entityType: URL fieldMappings: - identifier: Url columnName: AddedUrls + - entityType: Account + fieldMappings: + - identifier: Name + columnName: InitiatingAppName + - identifier: AadUserId + columnName: InitiatingAppServicePrincipalId - entityType: Account fieldMappings: - identifier: FullName - columnName: AddedBy + columnName: InitiatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: InitiatingAadUserId - entityType: IP fieldMappings: - identifier: Address - columnName: ipAddress -version: 1.0.3 + columnName: InitiatingIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: diff --git a/Detections/AuditLogs/UserAccountCreatedUsingIncorrectNamingFormat.yaml b/Detections/AuditLogs/UserAccountCreatedUsingIncorrectNamingFormat.yaml index b703a07058..ad9b3bbfb0 100644 --- a/Detections/AuditLogs/UserAccountCreatedUsingIncorrectNamingFormat.yaml +++ b/Detections/AuditLogs/UserAccountCreatedUsingIncorrectNamingFormat.yaml @@ -23,33 +23,59 @@ tags: - AADSecOpsGuide query: | // Add the environments expected username format regex below before deploying - let user_regex = ""; - AuditLogs - | where OperationName =~ "Add user" - | where Result =~ "success" - | extend userAgent = tostring(AdditionalDetails[0].value) - | extend addingUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) - | extend addingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName) - | extend AddedBy = iif(isnotempty(addingUser), addingUser, addingApp) - | extend ipAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) - | extend AddedUser = tostring(TargetResources[0].userPrincipalName) - | where AddedUser matches regex user_regex + let user_regex = ""; + AuditLogs + | where OperationName =~ "Add user" + | where Result =~ "success" + | extend userAgent = tostring(AdditionalDetails[0].value) + | extend InitiatingAppName = tostring(InitiatedBy.app.displayName) + | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId) + | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | extend InitiatingAadUserId = tostring(InitiatedBy.user.id) + | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress) + | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName)) + | extend AddedUser = tostring(TargetResources[0].userPrincipalName) + | where AddedUser matches regex user_regex + | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]) + | extend TargetAccountName = tostring(split(AddedUser, "@")[0]), TargetAccountUPNSuffix = tostring(split(AddedUser, "@")[1]) entityMappings: + - entityType: Account + fieldMappings: + - identifier: Name + columnName: InitiatingAppName + - identifier: AadUserId + columnName: InitiatingAppServicePrincipalId - entityType: Account fieldMappings: - identifier: FullName - columnName: AddedBy + columnName: InitiatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: InitiatingAadUserId - entityType: Account fieldMappings: - identifier: FullName columnName: AddedUser -version: 1.0.1 + - identifier: Name + columnName: TargetAccountName + - identifier: UPNSuffix + columnName: TargetAccountUPNSuffix + - entityType: IP + fieldMappings: + - identifier: Address + columnName: InitiatingIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: diff --git a/Detections/AuditLogs/UserStatechangedfromGuesttoMember.yaml b/Detections/AuditLogs/UserStatechangedfromGuesttoMember.yaml index ebf9490077..744b28e4cd 100644 --- a/Detections/AuditLogs/UserStatechangedfromGuesttoMember.yaml +++ b/Detections/AuditLogs/UserStatechangedfromGuesttoMember.yaml @@ -27,28 +27,55 @@ query: | | mv-expand TargetResources | mv-expand TargetResources.modifiedProperties | where TargetResources_modifiedProperties.displayName =~ "TargetId.UserType" - | extend UpdatingServicePrincipal = tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalId) - | extend UpdatingUserPrincipal = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) - | extend UpdatingUser = iif(isnotempty(UpdatingServicePrincipal), UpdatingServicePrincipal, UpdatingUserPrincipal) + | extend UpdatingAppName = tostring(parse_json(tostring(InitiatedBy.app)).displayName) + | extend UpdatingServicePrincipalId = tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalId) + | extend UpdatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) + | extend UpdatingUserAadUserId = tostring(parse_json(tostring(InitiatedBy.user)).id) + | extend UpdatingUserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) + | extend UpdatingUser = iif(isnotempty(UpdatingServicePrincipalId), UpdatingServicePrincipalId, UpdatingUserPrincipalName) | extend UpdatedUserPrincipalName = tostring(TargetResources.userPrincipalName) | project-reorder TimeGenerated, UpdatedUserPrincipalName, UpdatingUser | where parse_json(tostring(TargetResources_modifiedProperties.newValue)) =~ "\"Member\"" and parse_json(tostring(TargetResources_modifiedProperties.oldValue)) =~ "\"Guest\"" + | extend InitiatingAccountName = tostring(split(UpdatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(UpdatingUserPrincipalName, "@")[1]) + | extend TargetAccountName = tostring(split(UpdatedUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(UpdatedUserPrincipalName, "@")[1]) entityMappings: + - entityType: Account + fieldMappings: + - identifier: Name + columnName: UpdatingAppName + - identifier: AadUserId + columnName: UpdatingServicePrincipalId - entityType: Account fieldMappings: - identifier: FullName - columnName: UpdatingUser + columnName: UpdatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: UpdatingUserAadUserId - entityType: Account fieldMappings: - identifier: FullName columnName: UpdatedUserPrincipalName -version: 1.0.1 + - identifier: Name + columnName: TargetAccountName + - identifier: UPNSuffix + columnName: TargetAccountUPNSuffix + - entityType: IP + fieldMappings: + - identifier: Address + columnName: UpdatingUserIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: diff --git a/Detections/AuditLogs/Useraccountcreatedwithoutexpectedattributesdefined.yaml b/Detections/AuditLogs/Useraccountcreatedwithoutexpectedattributesdefined.yaml index e05cfae789..f2cd3add7a 100644 --- a/Detections/AuditLogs/Useraccountcreatedwithoutexpectedattributesdefined.yaml +++ b/Detections/AuditLogs/Useraccountcreatedwithoutexpectedattributesdefined.yaml @@ -23,50 +23,72 @@ tags: query: | let threshold = 10; let default_ad_attributes = dynamic(["LastDirSyncTime", "StsRefreshTokensValidFrom", "Included Updated Properties", "AccountEnabled", "Action Client Name", "SourceAnchor"]); - AuditLogs + let addUsers = AuditLogs | where OperationName =~ "Add user" | where Result =~ "success" - | extend properties = TargetResources[0].modifiedProperties - | mv-expand properties - | evaluate bag_unpack(properties) : (displayName:string, oldValue: string, newValue: string , TenantId : string, SourceSystem : string, TimeGenerated : datetime, ResourceId : string, OperationName : string, OperationVersion : string, Category : string, ResultType : string, ResultSignature : string, ResultDescription : string, DurationMs : long, CorrelationId : string, Resource : string, ResourceGroup : string, ResourceProvider : string, Identity : string, Level : string, Location : string, AdditionalDetails : dynamic, Id : string, InitiatedBy : dynamic, LoggedByService : string, Result : string, ResultReason : string, TargetResources : dynamic, AADTenantId : string, ActivityDisplayName : string, ActivityDateTime : datetime, AADOperationType : string, Type : string) + | extend AccountProperties = TargetResources[0].modifiedProperties + | mv-expand AccountProperties + ; + addUsers + | evaluate bag_unpack(AccountProperties) : (displayName:string, oldValue: string, newValue: string , TenantId : string, SourceSystem : string, TimeGenerated : datetime, ResourceId : string, OperationName : string, OperationVersion : string, Category : string, ResultType : string, ResultSignature : string, ResultDescription : string, DurationMs : long, CorrelationId : string, Resource : string, ResourceGroup : string, ResourceProvider : string, Identity : string, Level : string, Location : string, AdditionalDetails : dynamic, Id : string, InitiatedBy : dynamic, LoggedByService : string, Result : string, ResultReason : string, TargetResources : dynamic, AADTenantId : string, ActivityDisplayName : string, ActivityDateTime : datetime, AADOperationType : string, Type : string) | extend displayName = column_ifexists("displayName", "Unknown Value") | summarize count() by displayName, TenantId | where displayName !in (default_ad_attributes) | top threshold by count_ desc | summarize make_set(displayName) by TenantId - | join kind=inner (AuditLogs - | where Result =~ "success" - | where OperationName =~ "Add user" + | join kind=inner ( + addUsers | extend CreatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) + | extend CreatingAadUserId = tostring(InitiatedBy.user.id) + | extend CreatingUserIPAddress = tostring(InitiatedBy.user.ipAddress) | extend CreatedUserPrincipalName = tostring(TargetResources[0].userPrincipalName) - | extend AccountProperties = TargetResources[0].modifiedProperties - | mv-expand AccountProperties - | extend PropName = tostring(AccountProperties.displayName)) on TenantId - | summarize makeset(PropName) by TimeGenerated, CorrelationId, CreatedUserPrincipalName, CreatingUserPrincipalName, tostring(set_displayName) + | extend PropName = tostring(AccountProperties.displayName)) + on TenantId + | summarize makeset(PropName) by TimeGenerated, CorrelationId, CreatedUserPrincipalName, CreatingUserPrincipalName, CreatingAadUserId, CreatingUserIPAddress, tostring(set_displayName) | extend missing_props = set_difference(todynamic(set_displayName), set_PropName) | where array_length(missing_props) > 0 - | join kind=innerunique (AuditLogs + | join kind=innerunique ( + AuditLogs | where Result =~ "success" | where OperationName =~ "Add user" - | extend CreatedUserPrincipalName = tostring(TargetResources[0].userPrincipalName)) on CorrelationId, CreatedUserPrincipalName + | extend CreatedUserPrincipalName = tostring(TargetResources[0].userPrincipalName)) + on CorrelationId, CreatedUserPrincipalName | extend ExpectedProperties = set_displayName | project-away set_displayName, set_PropName + | extend InitiatingAccountName = tostring(split(CreatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(CreatingUserPrincipalName, "@")[1]) + | extend TargetAccountName = tostring(split(CreatedUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(CreatedUserPrincipalName, "@")[1]) entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: CreatingUserPrincipalName + - identifier: Name + columnName: InitiatingAccountName + - identifier: UPNSuffix + columnName: InitiatingAccountUPNSuffix + - entityType: Account + fieldMappings: + - identifier: AadUserId + columnName: CreatingAadUserId - entityType: Account fieldMappings: - identifier: FullName columnName: CreatedUserPrincipalName -version: 1.0.3 + - identifier: Name + columnName: TargetAccountName + - identifier: UPNSuffix + columnName: TargetAccountUPNSuffix + - entityType: IP + fieldMappings: + - identifier: Address + columnName: CreatingUserIPAddress +version: 1.1.0 kind: Scheduled metadata: source: kind: Community author: - name: Pete Bryan + name: Microsoft Security Research support: tier: Community categories: