From f7cb3a4464f9c62072488b393085c81e2e4f3f40 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 21 Aug 2024 15:43:59 -0400 Subject: [PATCH 1/5] Exclude consent:set type --- Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 index 9e9d3d40cf48..9e76e9a01ca5 100644 --- a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 @@ -23,6 +23,7 @@ function Test-CIPPAuditLogRules { 'SAS:EndAuth' 'SAS:ProcessAuth' 'deviceAuth:ReprocessTls' + 'Consent:Set' ) $TrustedIPTable = Get-CIPPTable -TableName 'trustedIps' From dbfbfab5dbb21ceff24611830df47e67b15fa18e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 21 Aug 2024 15:44:35 -0400 Subject: [PATCH 2/5] add %tenantid% replace to $filter --- .../CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 index 4177e3e24368..23c8f4f691a6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 @@ -18,11 +18,11 @@ function Invoke-ListGraphRequest { $Parameters = @{} if ($Request.Query.'$filter') { - $Parameters.'$filter' = $Request.Query.'$filter' + $Parameters.'$filter' = $Request.Query.'$filter' -replace '%tenantid%', $env:TenantId } if (!$Request.Query.'$filter' -and $Request.Query.graphFilter) { - $Parameters.'$filter' = $Request.Query.graphFilter + $Parameters.'$filter' = $Request.Query.graphFilter -replace '%tenantid%', $env:TenantId } if ($Request.Query.'$select') { @@ -141,4 +141,4 @@ function Invoke-ListGraphRequest { StatusCode = $StatusCode Body = $Outputdata }) -} \ No newline at end of file +} From 749e85798c7c08a75c23f9a3fd0888e65d9d2996 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 21 Aug 2024 15:45:51 -0400 Subject: [PATCH 3/5] App permissions, updated by field --- .../Settings/Invoke-ExecSAMAppPermissions.ps1 | 6 +- .../Invoke-ExecAppPermissionTemplate.ps1 | 56 +++++++++++++++++++ .../GraphHelper/Get-CippSamPermissions.ps1 | 4 ++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 index a3c442c85656..8aeeafdb6222 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 @@ -8,6 +8,8 @@ function Invoke-ExecSAMAppPermissions { [CmdletBinding()] param($Request, $TriggerMetadata) + $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json + switch ($Request.Query.Action) { 'Update' { try { @@ -15,13 +17,15 @@ function Invoke-ExecSAMAppPermissions { $Entity = @{ 'PartitionKey' = 'CIPP-SAM' 'RowKey' = 'CIPP-SAM' - 'Permissions' = [string]($Permissions.Permissions | ConvertTo-Json -Depth 10 -Compress) + 'Permissions' = [string]($Permissions | ConvertTo-Json -Depth 10 -Compress) + 'UpdatedBy' = $User.UserDetails ?? 'CIPP-API' } $Table = Get-CIPPTable -TableName 'AppPermissions' $null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force $Body = @{ 'Results' = 'Permissions Updated' } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'ExecSAMAppPermissions' -message 'CIPP-SAM Permissions Updated' -Sev 'Info' -LogData $Permissions } catch { $Body = @{ 'Results' = $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 new file mode 100644 index 000000000000..a095252512f4 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 @@ -0,0 +1,56 @@ +function Invoke-ExecAppPermissionTemplate { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Application.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $Table = Get-CIPPTable -TableName 'AppPermissions' + + $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json + + switch ($Request.Query.Action) { + 'Save' { + try { + $Permissions = $Request.Body.Permissions + $Entity = @{ + 'PartitionKey' = 'Templates' + 'RowKey' = [string]($Request.Body.TemplateId ?? [guid]::NewGuid().ToString()) + 'TemplateName' = [string]$Request.Body.TemplateName + 'Permissions' = [string]($Permissions | ConvertTo-Json -Depth 10 -Compress) + 'UpdatedBy' = $User.UserDetails ?? 'CIPP-API' + } + $null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force + $Body = @{ + 'Results' = 'Template Saved' + 'TemplateId' = $Entity.RowKey + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'ExecAppPermissionTemplate' -message "Permissions Saved for template: $($Request.Body.TemplateName)" -Sev 'Info' -LogData $Permissions + } catch { + $Body = @{ + 'Results' = $_.Exception.Message + } + } + } + default { + $Body = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Templates'" | ForEach-Object { + [PSCustomObject]@{ + TemplateId = $_.RowKey + TemplateName = $_.TemplateName + Permissions = $_.Permissions | ConvertFrom-Json + UpdatedBy = $_.UpdatedBy + Timestamp = $_.Timestamp.DateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') + } + } + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = ConvertTo-Json -Depth 10 -InputObject @($Body) + }) + +} diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 index cd5a5e7997c8..323a424c81e7 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 @@ -137,10 +137,14 @@ function Get-CippSamPermissions { $SamAppPermissions = @{} if (($SavedPermissions.Permissions.PSObject.Properties.Name | Measure-Object).Count -gt 0) { $SamAppPermissions.Permissions = $SavedPermissions.Permissions + $SamAppPermissions.UpdatedBy = $SavedPermissions.UpdatedBy + $SamAppPermissions.Timestamp = $SavedPermissions.Timestamp.DateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') $SamAppPermissions.Type = 'Table' } else { $SamAppPermissions.Permissions = $Permissions $SamAppPermissions.Type = 'Manifest' + $SamAppPermissions.UpdatedBy = 'CIPP' + $SamAppPermissions.Timestamp = $SamManifest.LastWriteTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } if (!$NoDiff.IsPresent) { From 0020e6e82e1988e0a5fb98e89bbe1d87c37b000b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 21 Aug 2024 15:59:01 -0400 Subject: [PATCH 4/5] GDAP tweaks - Add If-match header - Switch add gdap group to use AsApp --- .../Tenant/GDAP/Invoke-ExecAddGDAPRole.ps1 | 4 ++-- .../Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecAddGDAPRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecAddGDAPRole.ps1 index 946eaddafaf0..f2ca561c1b36 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecAddGDAPRole.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecAddGDAPRole.ps1 @@ -17,7 +17,7 @@ Function Invoke-ExecAddGDAPRole { $Table = Get-CIPPTable -TableName 'GDAPRoles' $Results = [System.Collections.Generic.List[string]]::new() - $ExistingGroups = New-GraphGetRequest -NoAuthCheck $True -uri 'https://graph.microsoft.com/beta/groups' -tenantid $env:TenantID + $ExistingGroups = New-GraphGetRequest -NoAuthCheck $True -uri 'https://graph.microsoft.com/beta/groups' -tenantid $env:TenantID -AsApp $true $RoleMappings = foreach ($group in $Groups) { if ($CustomSuffix) { @@ -40,7 +40,7 @@ Function Invoke-ExecAddGDAPRole { $Results.Add("M365 GDAP $($Group.Name) already exists") } else { $BodyToship = [pscustomobject] @{'displayName' = $GroupName; 'description' = "This group is used to manage M365 partner tenants at the $($group.name) level."; securityEnabled = $true; mailEnabled = $false; mailNickname = $MailNickname } | ConvertTo-Json - $GraphRequest = New-GraphPostRequest -NoAuthCheck $True -uri 'https://graph.microsoft.com/beta/groups' -tenantid $env:TenantID -type POST -body $BodyToship -verbose + $GraphRequest = New-GraphPostRequest -NoAuthCheck $True -uri 'https://graph.microsoft.com/beta/groups' -tenantid $env:TenantID -type POST -body $BodyToship -AsApp $true @{ PartitionKey = 'Roles' RowKey = $GraphRequest.Id diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 index 0e737bb7f778..f93ae96552ba 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 @@ -22,6 +22,7 @@ Function Invoke-ExecGDAPInvite { $Table = Get-CIPPTable -TableName 'GDAPInvites' try { + $Step = 'Creating GDAP relationship' $JSONBody = @{ 'displayName' = "$((New-Guid).GUID)" 'accessDetails' = @{ @@ -45,7 +46,13 @@ Function Invoke-ExecGDAPInvite { $JSONBody = @{ 'action' = 'lockForApproval' } | ConvertTo-Json - $NewRelationshipRequest = New-GraphPostRequest -NoAuthCheck $True -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$($NewRelationship.id)/requests" -type POST -body $JSONBody -verbose -tenantid $env:TenantID + $Step = 'Locking GDAP relationship for approval' + + $AddedHeaders = @{ + 'If-Match' = $NewRelationship.'@odata.etag' + } + + $NewRelationshipRequest = New-GraphPostRequest -NoAuthCheck $True -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$($NewRelationship.id)/requests" -type POST -body $JSONBody -verbose -tenantid $env:TenantID -AddedHeaders $AddedHeaders if ($NewRelationshipRequest.action -eq 'lockForApproval') { $InviteUrl = "https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/$($NewRelationship.id)" @@ -74,7 +81,7 @@ Function Invoke-ExecGDAPInvite { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' } } catch { - $Message = 'Error creating GDAP relationship' + $Message = 'Error creating GDAP relationship, failed at step: ' + $Step Write-Host "GDAP ERROR: $($_.InvocationInfo.PositionMessage)" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $env:TenantID -message "$($Message): $($_.Exception.Message)" -Sev 'Error' -LogData (Get-CippException -Exception $_) } From d73c303fe29f6cb8fd88b507349dcb7cba49266c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 21 Aug 2024 18:36:21 -0400 Subject: [PATCH 5/5] BEC improvements - Add filtering on graph side - Use bulk requests - Add try/catch to last 50 signins - Adjust output for BEC remediate --- Durable_BECRun/run.ps1 | 271 +++++++++++------- .../Users/Invoke-ExecBECRemediate.ps1 | 22 +- 2 files changed, 181 insertions(+), 112 deletions(-) diff --git a/Durable_BECRun/run.ps1 b/Durable_BECRun/run.ps1 index 44eecff33d2f..1e5c1bf1cae1 100644 --- a/Durable_BECRun/run.ps1 +++ b/Durable_BECRun/run.ps1 @@ -6,122 +6,183 @@ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -m $TenantFilter = $Context.input.tenantfilter $SuspectUser = $Context.input.userid $UserName = $Context.input.username -Write-Host "Working on $UserName" +Write-Information "Working on $UserName" try { - $startDate = (Get-Date).AddDays(-7) - $endDate = (Get-Date) - $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled - $7dayslog = if ($auditLog -eq $false) { - $ExtractResult = 'AuditLog is disabled. Cannot perform full analysis' - } else { - $sessionid = Get-Random -Minimum 10000 -Maximum 99999 - $operations = @( - 'New-InboxRule', - 'Set-InboxRule', - 'UpdateInboxRules', - 'Remove-MailboxPermission', - 'Add-MailboxPermission', - 'UpdateCalendarDelegation', - 'AddFolderPermissions', - 'MailboxLogin', - 'UserLoggedIn' - ) - $startDate = (Get-Date).AddDays(-7) + $startDate = (Get-Date).AddDays(-7).ToUniversalTime() $endDate = (Get-Date) - $SearchParam = @{ - SessionCommand = 'ReturnLargeSet' - Operations = $operations - sessionid = $sessionid - startDate = $startDate - endDate = $endDate + Write-Information 'Getting audit logs' + $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled + $7dayslog = if ($auditLog -eq $false) { + $ExtractResult = 'AuditLog is disabled. Cannot perform full analysis' + } else { + $sessionid = Get-Random -Minimum 10000 -Maximum 99999 + $operations = @( + 'New-InboxRule', + 'Set-InboxRule', + 'UpdateInboxRules', + 'Remove-MailboxPermission', + 'Add-MailboxPermission', + 'UpdateCalendarDelegation', + 'AddFolderPermissions', + 'MailboxLogin', + 'UserLoggedIn' + ) + $startDate = (Get-Date).AddDays(-7) + $endDate = (Get-Date) + $SearchParam = @{ + SessionCommand = 'ReturnLargeSet' + Operations = $operations + sessionid = $sessionid + startDate = $startDate + endDate = $endDate + } + do { + New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Search-unifiedAuditLog' -cmdParams $SearchParam -Anchor $Username + Write-Information "Retrieved $($logsTenant.count) logs" + $logsTenant + } while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0) + $ExtractResult = 'Successfully extracted logs from auditlog' + } + Write-Information 'Getting last sign-in' + Try { + $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc" + $LastSignIn = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } }, + id, + @{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } }, + @{ Name = 'Status'; Expression = { if (($_.conditionalAccessStatus -eq 'Success' -or 'Not Applied') -and $_.status.errorCode -eq 0) { 'Success' } else { 'Failed' } } }, + @{ Name = 'IPAddress'; Expression = { $_.ipAddress } } + } catch { + $LastSignIn = [PSCustomObject]@{ + AppDisplayName = 'Unknown - could not retrieve information. No access to sign-in logs' + CreatedDateTime = 'Unknown' + Id = '0' + Status = 'Could not retrieve additional details' + } } - do { - New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Search-unifiedAuditLog' -cmdParams $SearchParam -Anchor $Username - Write-Host "Retrieved $($logsTenant.count) logs" -ForegroundColor Yellow - $logsTenant - } while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0) - $ExtractResult = 'Successfully extracted logs from auditlog' - } - Try { - $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc" - $LastSignIn = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } }, - id, - @{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } }, - @{ Name = 'Status'; Expression = { if (($_.conditionalAccessStatus -eq 'Success' -or 'Not Applied') -and $_.status.errorCode -eq 0) { 'Success' } else { 'Failed' } } }, - @{ Name = 'IPAddress'; Expression = { $_.ipAddress } } - } catch { - $LastSignIn = [PSCustomObject]@{ - AppDisplayName = 'Unknown - could not retrieve information. No access to sign-in logs' - CreatedDateTime = 'Unknown' - Id = '0' - Status = 'Could not retrieve additional details' + Write-Information 'Getting user devices' + #List all users devices + $Bytes = [System.Text.Encoding]::UTF8.GetBytes($SuspectUser) + $base64IdentityParam = [Convert]::ToBase64String($Bytes) + Try { + $Devices = New-GraphGetRequest -uri "https://outlook.office365.com:443/adminapi/beta/$($TenantFilter)/mailbox('$($base64IdentityParam)')/MobileDevice/Exchange.GetMobileDeviceStatistics()/?IsEncoded=True" -Tenantid $tenantfilter -scope ExchangeOnline + } catch { + $Devices = $null } - } - #List all users devices - $Bytes = [System.Text.Encoding]::UTF8.GetBytes($SuspectUser) - $base64IdentityParam = [Convert]::ToBase64String($Bytes) - Try { - $Devices = New-GraphGetRequest -uri "https://outlook.office365.com:443/adminapi/beta/$($TenantFilter)/mailbox('$($base64IdentityParam)')/MobileDevice/Exchange.GetMobileDeviceStatistics()/?IsEncoded=True" -Tenantid $tenantfilter -scope ExchangeOnline - } catch { - $Devices = $null - } - $PermissionsLog = ($7dayslog | Where-Object -Property Operations -In 'Remove-MailboxPermission', 'Add-MailboxPermission', 'UpdateCalendarDelegation', 'AddFolderPermissions' ).AuditData | ConvertFrom-Json -Depth 100 | ForEach-Object { - $perms = if ($_.Parameters) { - $_.Parameters | ForEach-Object { if ($_.Name -eq 'AccessRights') { $_.Value } } - } else - { $_.item.ParentFolder.MemberRights } - $objectID = if ($_.ObjectID) { $_.ObjectID } else { $($_.MailboxOwnerUPN) + $_.item.ParentFolder.Path } - [pscustomobject]@{ - Operation = $_.Operation - UserKey = $_.UserKey - ObjectId = $objectId - Permissions = $perms + + try { + $PermissionsLog = ($7dayslog | Where-Object -Property Operations -In 'Remove-MailboxPermission', 'Add-MailboxPermission', 'UpdateCalendarDelegation', 'AddFolderPermissions' ).AuditData | ConvertFrom-Json -ErrorAction Stop | ForEach-Object { + $perms = if ($_.Parameters) { + $_.Parameters | ForEach-Object { if ($_.Name -eq 'AccessRights') { $_.Value } } + } else + { $_.item.ParentFolder.MemberRights } + $objectID = if ($_.ObjectID) { $_.ObjectID } else { $($_.MailboxOwnerUPN) + $_.item.ParentFolder.Path } + [pscustomobject]@{ + Operation = $_.Operation + UserKey = $_.UserKey + ObjectId = $objectId + Permissions = $perms + } + } + } catch { + $PermissionsLog = @() } - } - $RulesLog = @(($7dayslog | Where-Object -Property Operations -In 'New-InboxRule', 'Set-InboxRule', 'UpdateInboxRules').AuditData | ConvertFrom-Json) | ForEach-Object { - Write-Host ($_ | ConvertTo-Json) - [pscustomobject]@{ - ClientIP = $_.ClientIP - CreationTime = $_.CreationTime - UserId = $_.UserId - RuleName = ($_.OperationProperties | ForEach-Object { if ($_.Name -eq 'RuleName') { $_.Value } }) - RuleCondition = ($_.OperationProperties | ForEach-Object { if ($_.Name -eq 'RuleCondition') { $_.Value } }) + try { + $RulesLog = @(($7dayslog | Where-Object -Property Operations -In 'New-InboxRule', 'Set-InboxRule', 'UpdateInboxRules').AuditData | ConvertFrom-Json -ErrorAction Stop) | ForEach-Object { + Write-Information ($_ | ConvertTo-Json) + [pscustomobject]@{ + ClientIP = $_.ClientIP + CreationTime = $_.CreationTime + UserId = $_.UserId + RuleName = ($_.OperationProperties | ForEach-Object { if ($_.Name -eq 'RuleName') { $_.Value } }) + RuleCondition = ($_.OperationProperties | ForEach-Object { if ($_.Name -eq 'RuleCondition') { $_.Value } }) + } + } + } catch { + $RulesLog = @() + } + + Write-Information 'Getting last 50 logons' + try { + $Last50Logons = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=userDisplayName ne 'On-Premises Directory Synchronization Service Account'&`$top=50&`$orderby=createdDateTime desc" -tenantid $TenantFilter -noPagination $true | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } }, + id, + @{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } }, + @{ Name = 'Status'; Expression = { if (($_.conditionalAccessStatus -eq 'Success' -or 'Not Applied') -and $_.status.errorCode -eq 0) { 'Success' } else { 'Failed' } } }, + @{ Name = 'IPAddress'; Expression = { $_.ipAddress } }, UserPrincipalName, UserDisplayName + } catch { + $Last50Logons = @( + [PSCustomObject]@{ + AppDisplayName = 'Unknown - could not retrieve information. No access to sign-in logs' + CreatedDateTime = 'Unknown' + Id = '0' + Status = 'Could not retrieve additional details' + Exception = $_.Exception.Message + } + ) + } + + $Requests = @( + @{ + id = 'PasswordChanges' + url = "users?`$select=lastPasswordChangeDateTime,displayname,UserPrincipalName&`$filter=lastPasswordChangeDateTime ge $($startDate.ToString('yyyy-MM-ddTHH:mm:ssZ'))" + method = 'GET' + } + @{ + id = 'NewUsers' + url = "users?`$select=displayname,userPrincipalName,createdDateTime?`$filter=createdDateTime ge $($startDate.ToString('yyyy-MM-ddTHH:mm:ssZ'))" + method = 'GET' + } + @{ + id = 'MFADevices' + url = "users/$($SuspectUser)/authentication/methods" + method = 'GET' + } + @{ + id = 'NewSPs' + url = "servicePrincipals?`$select=displayName,createdDateTime,id,appDisplayName&`$filter=createdDateTime ge $($startDate.ToString('yyyy-MM-ddTHH:mm:ssZ'))" + method = 'GET' + } + @{ + id = 'Last50Logons' + url = "auditLogs/signIns?`$top=50&`$orderby=createdDateTime desc&`$filter=createdDateTime ge $($startDate.ToString('yyyy-MM-ddTHH:mm:ssZ')) and userDisplayName ne 'On-Premises Directory Synchronization Service Account'" + method = 'GET' + } + ) + + Write-Information 'Getting bulk requests' + + $GraphResults = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true + $PasswordChanges = $GraphResults | Where-Object { $_.id -eq 'PasswordChanges' } | Select-Object -ExpandProperty body + $NewUsers = $GraphResults | Where-Object { $_.id -eq 'NewUsers' } | Select-Object -ExpandProperty body + $MFADevices = $GraphResults | Where-Object { $_.id -eq 'MFADevices' } | Select-Object -ExpandProperty body + $NewSPs = $GraphResults | Where-Object { $_.id -eq 'NewSPs' } | Select-Object -ExpandProperty body + + + $Results = [PSCustomObject]@{ + AddedApps = @($NewSPs) + SuspectUserMailboxLogons = @($Last50Logons) + LastSuspectUserLogon = @($LastSignIn) + SuspectUserDevices = @($Devices) + NewRules = @($RulesLog) + MailboxPermissionChanges = @($PermissionsLog) + NewUsers = @($NewUsers) + MFADevices = @($MFADevices) + ChangedPasswords = @($PasswordChanges) + ExtractedAt = (Get-Date).ToString('s') + ExtractResult = $ExtractResult } - } - $PasswordChanges = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`select=lastPasswordChangeDateTime,displayname,UserPrincipalName" -Tenantid $tenantfilter | Where-Object { $_.lastPasswordChangeDateTime -gt $startDate } - $NewUsers = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users?`$select=displayname,UserPrincipalName,CreatedDateTime" -Tenantid $tenantfilter | Where-Object { $_.CreatedDateTime -gt $startDate } - $MFADevices = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($SuspectUser)/authentication/methods" -Tenantid $tenantfilter - $NewSPs = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$select=displayName,createdDateTime,id,AppDisplayName&`$filter=createdDateTime ge $($startDate.ToString('yyyy-MM-ddTHH:mm:ssZ'))" -Tenantid $tenantfilter - $Last50Logons = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/auditLogs/signIns?`$top=50&`$orderby=createdDateTime desc" -tenantid $TenantFilter -noPagination $true -verbose | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } }, - id, - @{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } }, - @{ Name = 'Status'; Expression = { if (($_.conditionalAccessStatus -eq 'Success' -or 'Not Applied') -and $_.status.errorCode -eq 0) { 'Success' } else { 'Failed' } } }, - @{ Name = 'IPAddress'; Expression = { $_.ipAddress } }, UserPrincipalName - $Results = [PSCustomObject]@{ - AddedApps = @($NewSPs) - SuspectUserMailboxLogons = @($Last50Logons) - LastSuspectUserLogon = @($LastSignIn) - SuspectUserDevices = @($Devices) - NewRules = @($RulesLog) - MailboxPermissionChanges = @($PermissionsLog) - NewUsers = @($NewUsers) - MFADevices = @($MFADevices) - ChangedPasswords = @($PasswordChanges) - ExtractedAt = (Get-Date).ToString('s') - ExtractResult = $ExtractResult - } } catch { - $errMessage = Get-NormalizedError -message $_.Exception.Message - $results = [pscustomobject]@{'Results' = "$errMessage" } + $errMessage = Get-NormalizedError -message $_.Exception.Message + $CippError = Get-CippException -Exception $_ + $results = [pscustomobject]@{'Results' = "$errMessage"; Exception = $CippError } } $Table = Get-CippTable -tablename 'cachebec' $Table.Force = $true Add-CIPPAzDataTableEntity @Table -Entity @{ - UserId = $Context.input.userid - Results = "$($results | ConvertTo-Json -Depth 10)" - RowKey = $Context.input.userid - PartitionKey = 'bec' -} \ No newline at end of file + UserId = $Context.input.userid + Results = "$($results | ConvertTo-Json -Depth 10)" + RowKey = $Context.input.userid + PartitionKey = 'bec' +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 index b17f79b71886..560b5265647a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 @@ -22,27 +22,35 @@ Function Invoke-ExecBECRemediate { Write-Host $TenantFilter Write-Host $SuspectUser $Results = try { + $Step = 'Reset Password' Set-CIPPResetPassword -userid $username -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $User + $Step = 'Disable Account' Set-CIPPSignInState -userid $username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $User + $Step = 'Revoke Sessions' Revoke-CIPPSessions -userid $SuspectUser -username $request.body.username -ExecutingUser $User -APIName $APINAME -tenantFilter $TenantFilter + + $Step = 'Disable Inbox Rules' + $Rules = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true } $RuleDisabled = 0 - New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' } | ForEach-Object { - $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule $($_.Identity) for $username" - $RuleDisabled++ + if (($Rules | Measure-Object).Count -gt 0) { + $Rules | Where-Object { $_.Name -ne 'Junk E-Mail Rule' } | ForEach-Object { + $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } + "Disabled Inbox Rule $($_.Identity) for $username" + $RuleDisabled++ + } } - if ($RuleDisabled) { + if ($RuleDisabled -gt 0) { "Disabled $RuleDisabled Inbox Rules for $username" } else { "No Inbox Rules found for $username. We have not disabled any rules." } - Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $SuspectUser" -sev 'Info' + Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $username" -sev 'Info' } catch { $ErrorMessage = Get-CippException -Exception $_ $results = [pscustomobject]@{'Results' = "Failed to execute remediation. $($ErrorMessage.NormalizedError)" } - Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $SuspectUser failed" -sev 'Error' -LogData $ErrorMessage + Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $username failed at the $Step step" -sev 'Error' -LogData $ErrorMessage } $results = [pscustomobject]@{'Results' = @($Results) }