Salta al contenuto
Guida In evidenza

Tenant Microsoft 365 compromesso: il toolkit per le prime ore

Dieci script PowerShell per contenere una compromissione senza perdere la via di ritorno, e per raccogliere i log prima che scadano. Con i limiti veri delle tre fonti, che decidono quanto indietro si riesce a guardare.

SynSphere Italia Pubblicato il 14 min di lettura
Le tre fonti di log di un tenant Microsoft 365 compromesso e le rispettive finestre di conservazione: 30 giorni per i sign-in log, 180 per il log unificato, 90 per il tracciamento dei messaggi

Le prime ore di una compromissione di un tenant Microsoft 365 si perdono quasi sempre nello stesso modo: si blocca in fretta, si raccoglie male, e quando qualcuno chiede da quando si scopre che la finestra utile è già scaduta. Questo toolkit è fatto per quelle ore.

Sono dieci script PowerShell. Otto leggono soltanto. Due scrivono — bloccano e riabilitano gli account — e sono in simulazione per default: senza -Esegui non toccano nulla e stampano solo l’elenco di ciò che farebbero.

Scarica il toolkit (ZIP, 10 script + LEGGIMI, uso libero anche commerciale).

Il contenimento va progettato per essere annullato

Bloccare tutti gli utenti di un tenant è l’operazione più distruttiva che si possa lanciare su Microsoft 365. È anche, a volte, quella giusta: durante una compromissione attiva ogni minuto in cui l’attaccante mantiene una sessione valida è altro danno.

Il problema non è bloccare. È tornare indietro.

Dopo un blocco di massa, senza una fotografia dello stato precedente, non esiste più modo di sapere quali account erano già disabilitati prima dell’incidente. E in un tenant reale ce ne sono sempre: ex dipendenti, contractor a progetto chiuso, utenze di servizio dismesse, caselle condivise. Un «riabilita tutti» li rimette in servizio tutti insieme, e apre un secondo problema di sicurezza mentre si sta chiudendo il primo.

Per questo lo script che fotografa lo stato viene prima, e quello che blocca si rifiuta di partire senza il suo CSV. Non è una raccomandazione nel LEGGIMI: è un controllo nel codice.

.\02-Backup-StatoUtenti.ps1
.\03-Invoke-BloccoEmergenza.ps1 -SnapshotCsv .\IR-02-StatoUtenti-20260828-1030.csv

Il secondo comando, così com’è, non blocca nessuno: mostra l’ambito e si ferma. Serve -Esegui per agire davvero.

Le tre esclusioni

Lo script che blocca protegge tre categorie, e la prima non è aggirabile in nessun modo:

  1. L’account che sta eseguendo lo script. È la vostra via di rientro. Nessun parametro lo include.
  2. I Global Administrator, esclusi per default. Bloccarli tutti significa perdere il controllo del tenant. Se l’attaccante ha compromesso proprio un’utenza amministrativa serve -IncludiAdmin, e va usato solo dopo aver verificato di avere un’utenza di emergenza funzionante.
  3. Gli account passati in -Escludi: è lì che vanno le utenze break-glass.

La revoca delle sessioni è la metà che conta

Disabilitare un account non invalida i token di aggiornamento già emessi. Una sessione aperta prima del blocco continua a funzionare: l’attaccante legge la posta per ore su un account che nel portale risulta bloccato, e chi ha lanciato il blocco pensa di aver contenuto.

Il toolkit revoca le sessioni insieme al blocco, per default. L’interruttore per non farlo esiste (-NonRevocareSessioni) ed è documentato come sconsigliato.

Un tenant ibrido non si contiene dal cloud

Sugli account sincronizzati da Active Directory locale il blocco lato cloud viene sovrascritto al successivo ciclo di sincronizzazione. Vanno disabilitati a monte, in AD.

Gli script li contano e li segnalano a parte, perché è un errore che dà una falsa sensazione di contenimento: il portale mostra gli account bloccati, e mezz’ora dopo sono di nuovo attivi.

C’è un dettaglio che rende questo controllo meno banale di quanto sembri: la proprietà onPremisesSyncEnabled di Microsoft Graph è tri-stato — vale true oppure è assente, mai false. Uno script che filtra con -eq $false non trova nessuno, e un tenant ibrido risulta interamente cloud.

Le tre fonti di log, e quanto durano davvero

Questa è la parte che decide l’esito di tutta la raccolta, e non dipende da come si scrive lo script.

FonteFinestra realeRequisito
Sign-in log di Entra ID30 giorni (7 con Entra ID Free)Entra ID P1 o P2
Log di controllo di Entra ID30 giorniEntra ID P1 o P2
Log di controllo unificato180 giorni con Audit Standardincluso; 1 anno solo con Audit Premium / E5
Tracciamento dei messaggi90 giorni, max 10 per interrogazioneExchangeOnlineManagement 3.7.0+

Tre conseguenze pratiche.

I sign-in log richiedono una licenza, e la loro assenza non somiglia a un’assenza. Su un tenant Business Basic o Standard la chiamata fallisce con un errore di autorizzazione, che a occhio sembra un problema di permessi o di ruolo. Chi non conosce il vincolo perde tempo a farsi assegnare ruoli che non servono. Il primo script del toolkit distingue i due casi e lo scrive. Business Premium include Entra ID P1, quindi lì i log ci sono.

Il log unificato registra solo se era già acceso. Se la registrazione era disattivata quando i fatti sono accaduti, accenderla oggi non recupera nulla: non è retroattiva. Il toolkit lo verifica e lo dice prima di produrre un CSV vuoto che somiglia a un tenant tranquillo.

Su quel controllo c’è una trappola documentata che vale la pena conoscere anche fuori da questo contesto: UnifiedAuditLogIngestionEnabled va letta da Exchange Online. Letta dalla sessione Security & Compliance risponde sempre False, senza errore. Uno script che apre la connessione sbagliata riferisce «audit disattivato» su un tenant dove è perfettamente attivo.

La finestra più lunga è quella che si usa meno. Quando i 30 giorni dei sign-in log sono esauriti, il log unificato con i suoi 180 giorni è l’unica fonte che resta, e contiene comunque gli eventi di accesso.

Le due trappole che producono un export incompleto

Sono entrambe silenziose: nessun errore, nessun avviso, un CSV che sembra completo.

Search-UnifiedAuditLog restituisce 100 record

Senza il parametro -SessionCommand il cmdlet si ferma a 100 record. Non è un errore e non è un troncamento dichiarato: sembra semplicemente che nel periodo esaminato ci siano stati cento eventi.

La paginazione corretta è questa:

$sessione = [guid]::NewGuid().ToString()
$tutti = New-Object System.Collections.Generic.List[object]
do {
    $blocco = @(Search-UnifiedAuditLog -StartDate $da -EndDate $a `
        -SessionId $sessione -SessionCommand ReturnLargeSet -ResultSize 5000)
    if ($blocco.Count -gt 0) { $tutti.AddRange($blocco) }
} while ($blocco.Count -gt 0 -and $tutti.Count -lt 50000)

Tre regole dietro a quelle righe: lo stesso SessionId su tutte le chiamate; mai mescolare ReturnLargeSet e ReturnNextPreviewPage sulla stessa sessione, o il risultato viene troncato a 10.000; e i record arrivano non ordinati, quindi vanno ordinati alla fine.

Il tetto è 50.000 per sessione. Quando lo si tocca, quel numero non è «tutti gli eventi»: è «il massimo che la sessione rende». Lo script lo dichiara e chiede di restringere la finestra, invece di lasciar passare 50.000 per un totale.

Get-MessageTrace non è più il cmdlet giusto

La dismissione è partita il 1° settembre 2025, e il supporto legacy sul Reporting Webservice si è chiuso il 18 marzo 2026. Il sostituto è Get-MessageTraceV2, che richiede ExchangeOnlineManagement 3.7.0 o superiore.

Il cmdlet nuovo ha tre vincoli che cambiano come va scritto lo script: copre 90 giorni ma accetta al massimo 10 giorni per interrogazione; non ha paginazione; si ferma a 5.000 risultati. Su una casella molto attiva, una finestra di dieci giorni supera quel tetto e i messaggi oltre il cinquemillesimo semplicemente non compaiono.

Il toolkit spezza la finestra da solo e, quando un blocco tocca il tetto, lo rifà giorno per giorno. Se anche un singolo giorno resta troncato, lo scrive in rosso con la data: meglio un export dichiaratamente incompleto che uno che sembra completo.

Quello che il blocco non chiude

È l’errore concettuale più costoso. Bloccare gli utenti ferma l’accesso. Non tocca ciò che l’attaccante ha lasciato dentro.

  • Le regole della posta in arrivo. Una regola che sposta in Posta eliminata tutto ciò che contiene «fattura» o «bonifico» continua a funzionare da sola, e serve a impedire alla vittima di vedere le risposte. È il meccanismo della frode del bonifico.
  • L’inoltro a livello di cassetta. Diverso dalle regole: non è impostato dall’utente e in Outlook non si vede.
  • I consensi applicativi. Un consenso OAuth concesso a un’applicazione malevola legge la posta tramite un token proprio: sopravvive al blocco dell’account, al cambio password e alla revoca delle sessioni. È la forma di persistenza che resiste a tutte e tre le contromisure che di solito si adottano.

Due script del toolkit cercano queste tre cose e le ordinano per gravità. Vanno guardati prima di riabilitare chiunque, e il rientro si fa a ondate — per questo lo script di ripristino accetta un elenco di utenti invece di agire su tutti.

Il report, e una regola su come è scritto

L’ultimo script non interroga il tenant: legge i CSV già raccolti e produce una pagina HTML unica. Gira quindi anche su un’altra macchina, o a incidente chiuso, e si può allegare alla comunicazione al cliente.

Ha una regola che vale la pena spiegare, perché è il motivo per cui questo report è utile invece che rassicurante: una fonte assente e una fonte vuota non sono la stessa cosa, e non vengono mai mostrate allo stesso modo.

«Nessun accesso sospetto» quando i sign-in log non sono mai stati scaricati è la conclusione più pericolosa che uno strumento del genere possa produrre. Dove un CSV non è stato raccolto il report scrive n.d., non zero, e dice quale script mancava.

Il report raccoglie indizi e mette in una pagina i numeri su cui una persona decide. Non stabilisce se ci sia stata una compromissione: quella resta una valutazione umana.

L’ordine in emergenza

01 → 02 → 03 → 05 → 06 → 09 → 07 → 08 → 10 → 04

Prima si guarda cosa si riesce a raccogliere su quel tenant, poi si fotografa, poi si contiene. Le fonti che scadono prima — i 30 giorni di Entra — si raccolgono subito dopo il contenimento. Le regole di posta prima del log unificato, perché servono a decidere il rientro. Il ripristino per ultimo, a ondate, e mai prima di aver bonificato.

Prima di lanciarlo

  • Lo snapshot dello script 02 è salvato fuori dal tenant (chiavetta, altro cloud). Se il tenant è compromesso, un file dentro OneDrive non è al sicuro.
  • Esiste un’utenza di emergenza funzionante e non compromessa, da passare in -Escludi.
  • La decisione di fermare l’azienda è stata presa da chi ha l’autorità per prenderla, ed è annotata con l’ora.
  • Lo script 03 è stato lanciato prima in simulazione, e l’elenco è stato letto.

Se siete in un incidente in corso e serve una mano, scriveteci: il toolkit serve a non perdere le prime ore, non a sostituire una risposta condotta da persone.

Domande frequenti

Bloccare gli account basta a fermare l’attaccante?

No, non da solo. Disabilitare l’account non invalida i token di aggiornamento già emessi: una sessione aperta prima del blocco resta valida e continua a leggere la posta. Serve anche la revoca delle sessioni, che azzera refreshTokensValidFromDateTime. Nel toolkit le due azioni sono nello stesso script e la revoca è attiva per default.

Perché serve una fotografia degli account prima di bloccarli?

Perché senza, il blocco non è reversibile. Dopo un blocco di massa non esiste più modo di sapere quali account erano già disabilitati prima dell’incidente: riabilitandoli tutti si rimettono in servizio ex dipendenti, contractor scaduti e utenze chiuse apposta. Lo script che blocca si rifiuta di partire senza il CSV prodotto dallo script che fotografa.

Quanto indietro posso guardare nei log?

Dipende dalla fonte e non è negoziabile. I sign-in log di Entra ID coprono 30 giorni con Entra ID P1 o P2 (7 con Entra ID Free) e richiedono quella licenza per essere letti via API. Il log di controllo unificato copre 180 giorni con Audit Standard, un anno solo con Audit Premium o E5. Il tracciamento dei messaggi copre 90 giorni, con un massimo di 10 giorni per interrogazione.

Il toolkit funziona su Microsoft 365 Business Premium?

Sì. Business Premium include Entra ID P1, quindi i sign-in log sono leggibili, e il log di controllo unificato con conservazione a 180 giorni non richiede E5. Su Business Basic e Standard i sign-in log via API non esistono: in quel caso resta il log di controllo unificato, che contiene comunque gli eventi di accesso.

Perché i miei script con Search-UnifiedAuditLog restituiscono pochi record?

Perché senza il parametro -SessionCommand il cmdlet restituisce 100 record e non segnala nulla: sembra semplicemente che nel periodo ci siano stati cento eventi. La paginazione corretta usa -SessionCommand ReturnLargeSet con lo stesso -SessionId su chiamate ripetute finché non tornano zero record, fino a un tetto di 50.000. I record arrivano non ordinati e vanno ordinati alla fine.

Get-MessageTrace non funziona più: cosa uso?

Get-MessageTraceV2, che richiede il modulo ExchangeOnlineManagement 3.7.0 o superiore. La dismissione di Get-MessageTrace è partita il 1° settembre 2025 e il supporto legacy sul Reporting Webservice si è chiuso il 18 marzo 2026. Il cmdlet nuovo accetta al massimo 10 giorni per interrogazione, non ha paginazione e si ferma a 5.000 risultati.

Dopo il blocco posso riabilitare tutti e chiudere l’incidente?

No. Il blocco non tocca le regole della posta in arrivo, gli inoltri impostati a livello di cassetta e i consensi applicativi: un consenso OAuth concesso a un’app malevola continua a leggere la posta dopo il blocco, dopo il cambio password e dopo la revoca delle sessioni. Vanno cercati e rimossi prima del rientro, che si fa a ondate e non in blocco.


Fonti. Documentazione Microsoft Learn consultata il 28 agosto 2026: Search-UnifiedAuditLog e Get-MessageTraceV2 (ExchangePowerShell), Get-MgAuditLogSignIn (Microsoft.Graph.Reports), «Microsoft Entra data retention», «Manage audit log retention policies» e l’annuncio di disponibilità generale del nuovo Message Trace in Exchange Online. Le finestre di conservazione e i requisiti di licenza citati provengono da quelle pagine.

Prodotti e servizi SynSphere correlati

Dal catalogo SynSphere, quello che c'entra con questo articolo.