Aller au contenu

Préservation urgente des preuves Microsoft 365

Face à une compromission avérée ou suspectée d’un tenant Microsoft 365, l’adversaire le plus redoutable de l’analyste DFIR n’est pas uniquement le groupe d’attaquants : c’est le compte à rebours de rétention des journaux. Contrairement aux infrastructures locales où une image disque ou une copie de journaux d’événements peut être figée indéfiniment, la télémétrie cloud de Microsoft 365 vieillit en continu et fait l’objet d’un écrasement automatique et irréversible dicté par les cycles de rétention de la plateforme.

Un retard de quelques heures ou de quelques jours dans les opérations initiales peut anéantir définitivement les historiques de connexion interactive, les traces de remise de messages en temps réel ou les journaux d’activité applicative. Cette fiche définit un protocole chronologique rigoureux de préservation d’urgence des preuves sur Microsoft 365 et Entra ID.


La préservation des preuves dans Microsoft 365 repose sur deux axes opérationnels distincts mais complémentaires :

  1. Préservation du plan des journaux (télémétrie éphémère) : extraire et archiver les logs opérationnels glissants (connexions Entra ID, audits d’annuaire, suivi des messages Exchange, télémétrie brute Defender) avant l’atteinte de leur date limite de rétention native.
  2. Préservation du plan des données (immuabilité des contenus) : appliquer des mesures de gel conservatoire (Litigation Hold, eDiscovery Hold Purview) sur les boîtes aux lettres, les bibliothèques OneDrive et les sites SharePoint pour empêcher l’attaquant ou les utilisateurs légitimes d’effacer définitivement des e-mails, des fichiers ou des messages instantanés.

Comprendre la frontière entre rétention des logs et rétention des contenus est fondamental : le Litigation Hold ne conserve aucun log d’audit, et l’export des logs d’audit ne fige aucun contenu de boîte mail. Les deux actions doivent être menées de front.


Ordre chronologique de préservation selon la vitesse d’expiration

Section intitulée « Ordre chronologique de préservation selon la vitesse d’expiration »

Les actions de collecte doivent être ordonnancées selon la vitesse d’expiration des données forensiques :

graph TD
subgraph "Hiérarchie de vitesse d'expiration"
T1["Niveau 1 : Ultra-Critique (Expiration 0 à 7 jours)<br/>- Logs Entra ID Free (7j)<br/>- Suivi des messages temps réel (10j)<br/>- Contexte éphémère de fraude MFA"]
T2["Niveau 2 : Haute Priorité (Expiration 10 à 30 jours)<br/>- Logs Entra ID P1/P2 (30j)<br/>- Advanced Hunting brut Defender XDR (30j)<br/>- Événements à risque Identity Protection"]
T3["Niveau 3 : Conformité & Contenus (30 à 180 jours)<br/>- Journal d'audit unifié Purview (180j standard)<br/>- Suivi des messages historique (90j)<br/>- Boucles de purge des boîtes et OneDrive"]
end
T1 -->|Préserver en Heure 1 à 4| T2
T2 -->|Préserver en Heure 4 à 12| T3
Fenêtre de rétentionFlux de donnéesConséquence du retardAction immédiate
7 joursConnexions & Audits Entra ID FreeHistorique d’authentification et adresses IP sources définitivement purgés.Export massif immédiat via script Graph API PowerShell.
10 joursSuivi des messages Exchange en temps réel (Get-MessageTrace)Les métadonnées détaillées par saut basculent vers la recherche historique lente.Extraire les traces de messages pour tous les comptes ciblés.
14 à 30 joursDossier Éléments supprimés & Purges (Boîte mail)Les e-mails effacés par l’attaquant sont purgés de Recoverable Items\Purges.Activer immédiatement le Litigation hold sur les boîtes concernées.
30 joursConnexions Entra P1/P2 & Télémétrie brute Defender XDRLes tables Advanced Hunting (CloudAppEvents, DeviceInfo) sont tronquées à 30j.Exporter les requêtes KQL et activer le streaming vers Log Analytics.
90 joursSuivi historique des messages ExchangeLes logs de transport antérieurs à 90 jours sont irrécupérables.Lancer un Start-HistoricalSearch sur le périmètre large.
180 joursJournal d’audit unifié Purview (Audit Standard)Les événements d’activité utilisateurs et administrateurs sont purgés.Exporter l’UAL via Search-UnifiedAuditLog par fenêtres temporelles.

Étape 1 : poser un gel conservatoire immédiat (litigation hold)

Section intitulée « Étape 1 : poser un gel conservatoire immédiat (litigation hold) »

Lorsqu’un attaquant accède à une boîte aux lettres, il configure couramment des règles de suppression automatique ou efface manuellement les traces de ses envois de phishing. L’activation du Litigation Hold fige immédiatement la boîte aux lettres : même si l’attaquant vide les éléments supprimés ou purge les messages par combinaison Maj+Suppr, Exchange Online déplace silencieusement les éléments vers les dossiers masqués Recoverable Items\Purges et DiscoveryHolds, d’où ils ne peuvent plus être effacés.

Fenêtre de terminal
# Connexion à Exchange Online
Connect-ExchangeOnline -ShowBanner:$false
$TargetMailbox = "victime@domaine.com"
$IncidentId = "INC-2026-8941"
# 1. Vérifier l'état actuel de rétention
Get-Mailbox -Identity $TargetMailbox | Select-Object DisplayName, LitigationHoldEnabled, LitigationHoldDate, RetentionHoldEnabled
# 2. Appliquer le Litigation Hold d'urgence (Rétention illimitée)
Set-Mailbox -Identity $TargetMailbox `
-LitigationHoldEnabled $true `
-LitigationHoldDuration Unlimited `
-LitigationHoldOwner "DFIR-$IncidentId" `
-RetentionUrl "https://incident-management.interne/cases/$IncidentId"
Write-Host "[+] Litigation Hold appliqué avec succès sur $TargetMailbox" -ForegroundColor Green
# 3. Contrôler l'état mis à jour
Get-Mailbox -Identity $TargetMailbox | Select-Object LitigationHoldEnabled, LitigationHoldDate, LitigationHoldOwner

[!WARNING] Délai de synchronisation du Litigation Hold : bien que le paramètre soit immédiatement visible dans les métadonnées de l’annuaire, la propagation complète dans le magasin de stockage Exchange Online peut prendre jusqu’à 60 minutes. Ne différez jamais cette action.


Étape 2 : export d’urgence des logs d’authentification et d’annuaire entra ID

Section intitulée « Étape 2 : export d’urgence des logs d’authentification et d’annuaire entra ID »

Sur un tenant Entra ID Free (7 jours de rétention) ou P1/P2 (30 jours), l’extraction immédiate des journaux de connexion est la priorité absolue.

Fenêtre de terminal
# ==============================================================================
# Hermes Codex - Extracteur d'urgence des Sign-In Logs Entra ID (Microsoft.Graph)
# Exporte les connexions avec pagination par @odata.nextLink
# ==============================================================================
Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Reports -ErrorAction Stop
# Connexion avec les privilèges de lecture des audits
Connect-MgGraph -Scopes "AuditLog.Read.All", "Directory.Read.All" -NoWelcome
$ExportDir = "./ForensicArtifacts_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
New-Item -ItemType Directory -Path $ExportDir -Force | Out-Null
$DaysToExtract = 30
$StartDate = (Get-Date).AddDays(-$DaysToExtract).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$OutputFile = "$ExportDir/Entra_SignIns_30Jours.jsonl"
Write-Host "[*] Extraction des Sign-Ins Entra depuis $StartDate vers $OutputFile..." -ForegroundColor Cyan
# Requête REST Graph avec filtre serveur et pagination
$Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=createdDateTime ge $StartDate&`$top=1000"
$Count = 0
do {
try {
$response = Invoke-MgGraphRequest -Method GET -Uri $Uri
$records = $response.value
foreach ($record in $records) {
$jsonLine = $record | ConvertTo-Json -Compress -Depth 10
Add-Content -Path $OutputFile -Value $jsonLine
$Count++
}
Write-Host "[+] $Count enregistrements extraits..." -ForegroundColor Gray
$Uri = $response.'@odata.nextLink'
# Pause préventive contre le throttling
Start-Sleep -Milliseconds 200
} catch {
if ($_.Exception.Response.StatusCode -eq 429) {
$retryAfter = $_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds
if (-not $retryAfter) { $retryAfter = 10 }
Write-Warning "[!] Throttling détecté (HTTP 429). Attente de $retryAfter secondes..."
Start-Sleep -Seconds $retryAfter
} else {
Write-Error "[-] Erreur critique Graph API : $($_.Exception.Message)"
break
}
}
} while ($null -ne $Uri)
# Calcul du hash SHA-256 pour la chaîne de traçabilité
$hash = Get-FileHash -Path $OutputFile -Algorithm SHA256
$hash | Export-Clixml -Path "$ExportDir/Entra_SignIns_30Jours.hash.xml"
Write-Host "[✓] Extraction terminée : $Count événements. SHA-256 : $($hash.Hash)" -ForegroundColor Green

Étape 3 : export d’urgence du journal d’audit unifié (UAL)

Section intitulée « Étape 3 : export d’urgence du journal d’audit unifié (UAL) »

Le journal d’audit unifié regroupe les actions menées sur Exchange, SharePoint, OneDrive, Teams et Entra ID. Pour éviter la troncature à 5 000 enregistrements et les dépassements de mémoire, le requêtage doit impérativement être segmenté par fenêtres temporelles.

Fenêtre de terminal
# ==============================================================================
# Hermes Codex - Extracteur UAL segmenté par fenêtres de 6 heures
# Évite la troncature des résultats et les interruptions de session
# ==============================================================================
Import-Module ExchangeOnlineManagement -ErrorAction Stop
Connect-ExchangeOnline -ShowBanner:$false
$TargetUser = "victime@domaine.com"
$StartDate = (Get-Date).AddDays(-15)
$EndDate = (Get-Date)
$OutputDir = "./UAL_Extract_$(Get-Date -Format 'yyyyMMdd')"
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
$CurrentStart = $StartDate
$IntervalHours = 6
while ($CurrentStart -lt $EndDate) {
$CurrentEnd = $CurrentStart.AddHours($IntervalHours)
if ($CurrentEnd -gt $EndDate) { $CurrentEnd = $EndDate }
$TimeStr = "$($CurrentStart.ToString('yyyyMMdd_HHmm'))_a_$($CurrentEnd.ToString('yyyyMMdd_HHmm'))"
$BatchFile = "$OutputDir/UAL_$TimeStr.json"
Write-Host "[*] Requêtage UAL : $CurrentStart -> $CurrentEnd..." -ForegroundColor Cyan
try {
$events = Search-UnifiedAuditLog `
-StartDate $CurrentStart `
-EndDate $CurrentEnd `
-FreeText $TargetUser `
-ResultSize 5000 `
-ErrorAction Stop
if ($events.Count -gt 0) {
# Parser la chaîne JSON interne AuditData
$structuredEvents = $events | ForEach-Object {
$rawAuditData = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
CreationDate = $_.CreationDate
RecordType = $_.RecordType
Operations = $_.Operations
UserIds = $_.UserIds
ResultStatus = $_.ResultStatus
AuditData = $rawAuditData
}
}
$structuredEvents | ConvertTo-Json -Depth 10 | Set-Content -Path $BatchFile
Write-Host " [+] $BatchFile sauvegardé ($($events.Count) événements)" -ForegroundColor Green
} else {
Write-Host " [-] Aucun événement dans cette tranche." -ForegroundColor Gray
}
} catch {
Write-Error " [!] Erreur sur la tranche UAL : $($_.Exception.Message)"
}
$CurrentStart = $CurrentEnd
Start-Sleep -Seconds 2 # Temporisation pour éviter le throttling Exchange
}

Étape 4 : mise en place du streaming continu vers un SIEM

Section intitulée « Étape 4 : mise en place du streaming continu vers un SIEM »

L’export ponctuel fige le passé, mais si l’investigation s’étend sur plusieurs semaines, les nouveaux logs continueront de disparaître. La configuration des Paramètres de diagnostic (Diagnostic Settings) dans Entra ID permet de router tous les flux futurs vers un espace de travail Log Analytics ou un Event Hub.

Fenêtre de terminal
# ==============================================================================
# Configuration des paramètres de diagnostic Entra ID vers Azure Log Analytics
# Requiert Global Administrator ou Security Administrator + Azure Contributor
# ==============================================================================
# 1. Identifiant de ressource de l'espace Log Analytics cible
$WorkspaceId = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-soc/providers/Microsoft.OperationalInsights/workspaces/law-retention-dfir"
# 2. Définition du payload JSON
$DiagnosticPayload = @{
name = "DFIR-PreservationContinue"
properties = @{
workspaceId = $WorkspaceId
logs = @(
@{ category = "SignInLogs"; enabled = $true },
@{ category = "NonInteractiveUserSignInLogs"; enabled = $true },
@{ category = "ServicePrincipalSignInLogs"; enabled = $true },
@{ category = "ManagedIdentitySignInLogs"; enabled = $true },
@{ category = "AuditLogs"; enabled = $true },
@{ category = "ProvisioningLogs"; enabled = $true },
@{ category = "RiskyUsers"; enabled = $true },
@{ category = "UserRiskEvents"; enabled = $true }
)
}
} | ConvertTo-Json -Depth 5
# 3. Envoi à l'API Diagnostic Settings de Microsoft Graph
$Uri = "https://graph.microsoft.com/v1.0/reports/diagnosticSettings"
Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $DiagnosticPayload -ContentType "application/json"
Write-Host "[+] Streaming continu vers Log Analytics activé !" -ForegroundColor Green

Étape 5 : gestion des quotas d’API et du throttling (HTTP 429)

Section intitulée « Étape 5 : gestion des quotas d’API et du throttling (HTTP 429) »

Lors de collectes volumineuses, les analystes rencontrent inévitablement les seuils de régulation de la plateforme Microsoft :

  • Code retour : HTTP 429 Too Many Requests.
  • En-tête clé : Retry-After (indique le nombre de secondes à patienter avant la prochaine requête).
  • Quota de parallélisme : maximum de 4 requêtes concurrentes par tenant sur les flux d’audit lourds.
  • Stratégie de traitement : toujours implémenter un recul exponentiel avec gigue aléatoire (jitter) : Délai = max(Retry-After, 2^tentative + rand(0, 1))
  • Limite d’espace d’exécution (runspace) : maximum 3 sessions simultanées par compte administrateur.
  • Micro-délais : insérer un Start-Sleep -Milliseconds 500 entre chaque appel à Search-UnifiedAuditLog ou Get-MessageTrace.
  • Plafond de résultat : Search-UnifiedAuditLog est plafonné à 5 000 enregistrements par exécution. Ne tentez jamais d’élargir la période sans découpage temporel.

Dans le rapport d’incident, l’analyste doit impérativement respecter la distinction entre ce qui est préservé et ce qui est prouvé :

+-------------------------------------------------------------------------------+
| LES 7 NIVEAUX DE CERTITUDE FORENSIQUE |
| |
| 1. Possible -> L'architecture prend en charge la rétention et les gels. |
| 2. Configuré -> Le Litigation Hold est actif et le diagnostic configuré. |
| 3. Autorisé -> L'intervenant possède les rôles eDiscovery et Audit Reader.|
| 4. Accessible -> Les endpoints API répondent, les jetons sont valides. |
| 5. Utilisé -> Les scripts d'export massif sont exécutés sur le tenant. |
| 6. Observé -> Les fichiers JSONL sont archivés et validés localement. |
| 7. Prouvé -> Certitude forensique scellée par empreinte SHA-256. |
+-------------------------------------------------------------------------------+

Applications directes à la préservation des preuves

Section intitulée « Applications directes à la préservation des preuves »
  1. Configuré != observé : poser un Litigation Hold est une action de configuration. Cela garantit la conservation des modifications futures ; cela ne prouve pas que des e-mails supprimés avant la pose du gel (au-delà des 14 jours de rétention par défaut des éléments supprimés) sont récupérables.
  2. Observé != prouvé : un enregistrement extrait de l’API Graph constitue un fait brut observé. Pour devenir une preuve juridiquement recevable (prouvée), il doit être scellé par une empreinte cryptographique SHA-256 dès son extraction, documenté dans un registre de chaîne de traçabilité, et corrélé avec les journaux de session correspondants.

PiègeCause techniqueConséquence pour l’enquêteAction corrective
Croire que litigation hold sauvegarde les logsLe Litigation Hold n’agit que sur le magasin Exchange (Recoverable Items) et n’a aucun impact sur l’UAL ou Entra ID.L’analyste néglige l’export des logs, qui disparaissent irrémédiablement au bout de 30 jours.Extraire les logs d’authentification et l’UAL en parallèle absolu des gels de contenu.
Requête UAL globale en une seule passeExécuter Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) en bloc.La requête plafonne à 5 000 enregistrements et tronque l’immense majorité des événements.Découper impérativement les requêtes en tranches de 6 à 24 heures.
Ignorer les connexions non interactivesN’extraire que les connexions interactives standard sans collecter les jetons de rafraîchissement ni les principaux de service.Les attaques par vol de session (AiTM) et les exfiltrations applicatives restent invisibles.Exporter systématiquement les flux NonInteractiveUserSignInLogs et ServicePrincipalSignInLogs.
Absence d’empreinte cryptographiqueDéposer des fichiers JSON/CSV bruts sur un poste d’analyse sans calcul de hash immédiat.Rejet potentiel des preuves lors d’un litige ou d’une notification réglementaire.Générer automatiquement le hash SHA-256 de chaque artefact dès l’export terminé.
Parallélisme agressif déclenchant un blocageLancer 50 threads concurrents sur Graph API.Blocage HTTP 429 massif impactant potentiellement les applications métiers de l’entreprise.Limiter l’extraction à 2-4 threads et respecter rigoureusement l’en-tête Retry-After.

  • Rétention standard UAL à 180 jours : la durée de conservation par défaut du journal d’audit unifié est de 180 jours pour tous les plans d’entreprise.
  • Modernisation de l’API audit dans Microsoft graph : l’extraction programmatique de l’UAL bascule sur les API Graph de conformité Purview, remplaçant progressivement l’ancienne API Office 365 Management Activity.
  • Paramètres de diagnostic unifiés : la diffusion en continu prend désormais en charge l’ensemble des catégories de connexions (utilisateurs, non interactifs, principaux de service, identités managées).
  • Modules Exchange V1 et V2 : définitivement retirés. L’ensemble des scripts doit exploiter le module ExchangeOnlineManagement v3+ reposant sur des appels REST.
  • Authentification de base : désactivée sans exception sur tous les endpoints d’administration. L’utilisation d’une application Entra avec certificat est obligatoire pour les collectes automatisées.
  • Latence d’ingestion UAL : même en réponse à incident critique, l’apparition d’un événement dans l’UAL peut nécessiter jusqu’à 60 minutes. Ne concluez pas à l’inactivité d’un attaquant sur la dernière heure sans prendre en compte cette latence.
  • Portée non rétroactive du litigation hold : les éléments purgés définitivement avant l’activation du gel ne peuvent en aucun cas être restitués.

  1. Prioriser selon la vitesse d’expiration : les logs Entra Free expirent à 7 jours, le suivi des messages temps réel à 10 jours et les logs Entra P1/P2 à 30 jours. Concentrer les efforts sur ces flux dans les 4 premières heures.
  2. Le litigation hold fige les données, pas les logs : poser immédiatement le gel sur les boîtes des victimes, mais extraire les logs d’audit séparément.
  3. Découper les requêtes UAL : ne jamais lancer d’interrogation massive d’un seul bloc sous peine de plafonner à 5 000 événements. Segmenter par tranches de 6 à 12 heures.
  4. Activer le streaming SIEM sans attendre : configurer les paramètres de diagnostic vers Log Analytics dès le premier jour pour garantir la traçabilité pendant toute la durée des investigations.
  5. Sceller immédiatement les preuves par empreinte SHA-256 : garantir l’intégrité et la valeur probante des artefacts extraits en calculant leur hash cryptographique dès leur écriture sur disque.