Le caselle postali condivise di Exchange Online (Shared Mailbox da ora, NdR) sono dotate di account Entra ID. Gli account sono dotati di password e le persone possono accedere all’account e avviare un client di posta elettronica collegato alla casella condivisa per elaborare la posta elettronica. È un problema? Assolutamente sì!
Le Shared Mailbox condivise non richiedono specifiche licenze di Exchange Online, a meno che le caselle di posta non abbiano un archivio, non necessitino di una quota superiore a 50 GB, non utilizzino il litigation hold o non siano soggette ai criteri di conservazione Purview. Come indicato nella descrizione del servizio Microsoft:
“Per accedere a una casella di posta condivisa, un utente deve disporre di una licenza di Exchange Online, ma la casella di posta condivisa non richiede una licenza separata”.
office365itpros.com/2024/08/26/shared-mailbox-signin
Parte così un interessante articolo su office365itpros, che ho avuto modo di leggere qualche tempo fa (durante la pausa estiva), e che mi è tornato utile per fare un “controllino sotto al cofano motore” del tenant aziendale. A memoria ricordavo un paio di Shared Mailbox che usavano un login ad-hoc tramite coppia di credenziali, ma erano state già messe in regola con licenza Exchange Online P1.
Ho quindi preso lo script pubblicato da Tony Redmond, l’ho verificato e fatto girare, raccogliendo poi i risultati.
Dandogli un’occhiata più attenta, mi sono accorto però che alcune di quelle caselle rilevate non aveva senso alcuno, non erano caselle che necessitavano di un login separato (per separato intendo fuori dalla solita modalità fatta di FullAccess e automapping su Outlook). Ho quindi approfondito aiutandomi con la console di Entra e verificando anche i log di accesso. Mi sono accorto che si trattava di tentativi di login falliti, attacchi a strascico da paesi stranieri. Questo rende quasi vano il lavoro eseguito dallo script, perché non rispecchia la realtà dei fatti, non c’è bisogno di licenza se la casella non deve essere acceduta direttamente.
Dando un’occhiata al singolo passaggio dello script, il problema risiede evidentemente nel fatto che, trovando almeno un tentativo di autenticazione (seppur andato a male), questo viene automaticamente preso come “buono” ai fini del conteggio finale:
[array]$Logs = Get-MgAuditLogSignIn -Filter "userid eq '$UserId'" -All -Top 1
Di per sé l’idea ha senso, per non perdere troppo tempo “mi fermo al primo risultato” (-Top 1
) e quindi faccio numero. Quello che invece ho pensato e inserito io, è stato un ulteriore controllo per andare più in profondità e verificare sul serio se quegli accessi sono leciti o meno, se nella casella l’utente è acceduto realmente, perciò:
If ($Logs) { # Check if there are successful sign-in records (at least one), otherwise, don't keep track of them (these could be attack attempts) Write-Output ("Checking for successful sign-in records for {0}" -f $M.DisplayName) [array]$search4SuccessfulLogins = Get-MgAuditLogSignIn -Filter "userid eq '$UserId'" -All $search4SuccessfulLogins | ForEach-Object { If ($_.Status.ErrorCode -eq "0") { $LogsFound = "Yes" } }
Se ottengo almeno un risultato dal -Top 1
, allora voglio che lo script tiri fuori tutti gli accessi (-All
) e verifichi la bontà di ciascuno di essi ($_.Status.ErrorCode -eq "0"
, perché diversamente sarà sempre un valore diverso da zero), ispirato da quanto discusso in un thread Reddit all’indirizzo reddit.com/r/PowerShell/comments/12o7yrc/accessing_status_property_under. Lo script intero, modificato, diventa quindi questo:
# Check-SharedMailboxes.PS1 # Check if people are signing into shared mailboxes. If they are, check if the accounts for the mailboxes are licensed for Exchange Online (Plan 1 or Plan 2). # # GitHub link: https://github.com/12Knocksinna/Office365itpros/blob/master/Check-SharedMailboxes.PS1 # Connect to the Microsoft Graph and Exchange Online Connect-MgGraph -Scopes "AuditLog.Read.All", "User.Read.All" -NoWelcome $Modules = Get-Module | Select-Object -ExpandProperty Name If ('ExchangeOnlineManagement' -notin $Modules) { Write-Output "Connecting to Exchange Online..." Connect-ExchangeOnline -SkipLoadingCmdletHelp } Write-Output "Finding shared mailboxes..." $Mbx = Get-ExoMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited | Sort-Object DisplayName If ($Mbx) { Write-Output ("{0} shared mailboxes found" -f $Mbx.Count) } Else { Write-Output "No shared mailboxes found" Break } # Define the service plan IDs for Exchange Online (Plan 1) and Exchange Online (Plan 2) $ExoServicePlan1 = "9aaf7827-d63c-4b61-89c3-182f06f82e5c" $ExoServicePlan2 = "efb87545-963c-4e0d-99df-69c6916d9eb0" $Report = [System.Collections.Generic.List[Object]]::new() ForEach ($M in $Mbx) { $ExoPlan1Found = $false; $ExoPlan2Found = $false; $LogsFound = "No" Write-Output ("Checking sign-in records for {0}" -f $M.DisplayName) $UserId = $M.ExternalDirectoryObjectId [array]$Logs = Get-MgAuditLogSignIn -Filter "userid eq '$UserId'" -All -Top 1 If ($Logs) { # Check if there are successful sign-in records (at least one), otherwise, don't keep track of them (these could be attack attempts) Write-Output ("Checking for successful sign-in records for {0}" -f $M.DisplayName) [array]$search4SuccessfulLogins = Get-MgAuditLogSignIn -Filter "userid eq '$UserId'" -All $search4SuccessfulLogins | ForEach-Object { If ($_.Status.ErrorCode -eq "0") { $LogsFound = "Yes" } } If ($LogsFound -eq "Yes") { Write-Host ("Sign-in records found for shared mailbox {0}" -f $M.DisplayName) -ForegroundColor Red # Check if the shared mailbox is licensed $User = Get-MgUser -UserId $M.ExternalDirectoryObjectId -Property UserPrincipalName, AccountEnabled, Id, DisplayName, assignedPlans [array]$ExoPlans = $User.AssignedPlans | Where-Object {$_.Service -eq 'exchange' -and $_.capabilityStatus -eq 'Enabled'} If ($ExoServicePlan1 -in $ExoPlans.ServicePlanId) { $ExoPlan1Found = $true } ElseIf ($ExoServicePlan2 -in $ExoPlans.ServicePlanId) { $ExoPlan2Found = $true } If ($ExoPlan1Found -eq $true) { Write-Output ("Shared mailbox {0} has Exchange Online (Plan 1) license" -f $M.DisplayName) } ElseIf ($ExoPlan2Found -eq $true) { Write-Output ("Shared mailbox {0} has Exchange Online (Plan 2) license" -f $M.DisplayName) } Else { Write-Host ("Shared mailbox {0} has no Exchange Online license" -f $M.DisplayName) -ForegroundColor Yellow } } else { Write-Host ("No successful sign-in records found for shared mailbox {0}" -f $M.DisplayName) -ForegroundColor Green } } $ReportLine = [PSCustomObject] @{ DisplayName = $M.DisplayName ExternalDirectoryObjectId = $M.ExternalDirectoryObjectId 'Sign in Record Found' = $LogsFound 'Exchange Online Plan 1' = $ExoPlan1Found 'Exchange Online Plan 2' = $ExoPlan2Found } $Report.Add($ReportLine) } $Report | Out-GridView -Title "Shared Mailbox Sign-In Records and Licensing Status" # An example script used to illustrate a concept. More information about the topic can be found in the Office 365 for IT Pros eBook https://gum.co/O365IT/ # and/or a relevant article on https://office365itpros.com or https://www.practical365.com. See our post about the Office 365 for IT Pros repository # https://office365itpros.com/office-365-github-repository/ for information about the scripts we write. # Do not use our scripts in production until you are satisfied that the code meets the needs of your organization. Never run any code downloaded from the Internet without # first validating the code in a non-production environment.
Eseguendolo, ho ottenuto esattamente i risultati che mi aspettavo. Lo script ha trovato le reali caselle che usano coppie di credenziali dirette, scartando tutte quelle che hanno subito tentativi di attacchi falliti, e che quindi non necessitano certamente di licenza Microsoft 365 ad-hoc.
Ho già aperto una proposta di modifica sul repository GitHub originale dello script (qui: github.com/12Knocksinna/Office365itpros/pull/123) e sono ancora in attesa di approvazione.
In caso di dubbi o suggerimenti per migliorare quanto fatto, come sempre, hai l’area commenti a tua totale disposizione :-)
#KeepItSimple