<# .SYNOPSIS ¿Qué remitentes y dominios nos mandan más correo con phishing o malware? .DESCRIPTION Consulta EmailEvents con búsqueda avanzada y agrupa por remitente y dominio los correos con phishing o malware detectado, indicando cuántos se entregaron y cuántos siguen en un buzón. Sirve para decidir qué bloquear y qué correos purgar. Solo lectura: el POST a runHuntingQuery es una consulta. .PARAMETER Dias Días hacia atrás, de 1 a 30 (la búsqueda avanzada guarda 30). Por defecto, 30. .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .PARAMETER ZonaHoraria Zona horaria para mostrar las fechas. Por defecto, Central Standard Time (Mexico). .EXAMPLE .\get-correos-maliciosos.ps1 -Dias 14 -RutaCsv .\correos.csv .NOTES Permisos: ThreatHunting.Read.All Rol mínimo: Lector de seguridad Licencia: Microsoft Defender for Office 365 Plan 2 (incluido en Microsoft 365 E5 o en Microsoft Defender Suite for Business Premium). Microsoft 365 Business Premium por sí solo no alcanza: trae Plan 1, sin búsqueda avanzada Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas. #> [CmdletBinding()] param( [ValidateRange(1, 30)] [int]$Dias = 30, [string]$RutaCsv, [string]$ZonaHoraria = 'Central Standard Time (Mexico)' ) $ErrorActionPreference = 'Stop' $permisos = @('ThreatHunting.Read.All') $zona = [TimeZoneInfo]::FindSystemTimeZoneById($ZonaHoraria) if (-not (Get-Command Connect-MgGraph -ErrorAction SilentlyContinue)) { throw 'Falta el módulo. Instálalo con: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser' } Connect-MgGraph -Scopes $permisos -NoWelcome function ConvertirFecha($valor) { if (-not $valor) { return $null } $utc = if ($valor -is [datetime]) { $valor.ToUniversalTime() } else { ([datetimeoffset]"$valor").UtcDateTime } [TimeZoneInfo]::ConvertTimeFromUtc($utc, $zona).ToString('yyyy-MM-dd HH:mm') } # El remitente lo escribe el atacante: el apóstrofo evita que Excel lo interprete como fórmula. function LimpiarTexto($valor) { if ("$valor" -match '^[=+\-@\t\r]') { "'$valor" } else { $valor } } $consulta = @" EmailEvents | where Timestamp > ago($($Dias)d) | where ThreatTypes has_any ("Phish", "Malware") | summarize Correos = dcount(NetworkMessageId), Destinatarios = dcount(RecipientEmailAddress), Entregas = countif(DeliveryAction == "Delivered"), EnBuzon = countif(LatestDeliveryLocation has "Inbox"), EnNoDeseado = countif(LatestDeliveryLocation has "Junk"), Amenazas = make_set(ThreatTypes), Ultimo = max(Timestamp) by SenderFromAddress, SenderFromDomain "@ $respuesta = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/security/runHuntingQuery' -Body (@{ Query = $consulta } | ConvertTo-Json) -ContentType 'application/json; charset=utf-8' if (@($respuesta.results).Count -ge 100000) { Write-Warning 'Se alcanzó el tope de 100,000 filas de la consulta: pueden faltar resultados. Reduce el periodo o filtra más.' } $totalPorDominio = @{} foreach ($r in $respuesta.results) { $totalPorDominio["$($r.SenderFromDomain)"] += [int]$r.Correos } $filas = foreach ($r in $respuesta.results) { [pscustomobject]@{ Dominio = LimpiarTexto $r.SenderFromDomain CorreosDominio = $totalPorDominio["$($r.SenderFromDomain)"] Remitente = LimpiarTexto $r.SenderFromAddress Correos = [int]$r.Correos Destinatarios = [int]$r.Destinatarios Entregas = [int]$r.Entregas EnBuzon = [int]$r.EnBuzon EnNoDeseado = [int]$r.EnNoDeseado Situacion = if ([int]$r.EnBuzon -gt 0) { 'Sigue en buzones' } elseif ([int]$r.EnNoDeseado -gt 0) { 'Sigue en correo no deseado' } elseif ([int]$r.Entregas -gt 0) { 'Se entregó, ya no está en la bandeja' } else { 'No se entregó' } Amenazas = @($r.Amenazas) -join ', ' UltimoVisto = ConvertirFecha $r.Ultimo } } $filas = $filas | Sort-Object @{ Expression = 'CorreosDominio'; Descending = $true }, Dominio, @{ Expression = 'Correos'; Descending = $true } Write-Host "Remitentes con correo malicioso en $Dias días: $(@($filas).Count) de $($totalPorDominio.Count) dominios" if ($RutaCsv) { # UTF-8 con BOM para que Excel en español muestre bien los acentos. $filas | Export-Csv -Path $RutaCsv -NoTypeInformation -Encoding utf8BOM Write-Host "CSV guardado en $RutaCsv" } else { $filas | Format-Table -AutoSize }