Correo malicioso por remitente y dominio
Agrupa por remitente y dominio el correo con phishing o malware detectado, e indica si se entregó o sigue en buzones, para decidir qué bloquear.
Ejemplo en México
Comercializadora de alimentos en Culiacán, 110 personas
El área de cobranza recibe correos que imitan avisos del SAT y proveedores, y TI quiere saber de qué dominios salen antes de bloquearlos.
.\get-correos-maliciosos.ps1 -Dias 14 -RutaCsv .\correos.csvObtiene el ranking de dominios y agrega los peores a la lista de bloqueo del tenant.
Cómo implementarlo
- Instala el módulo si no lo tienes: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser.
- Corre el script con -Dias e inicia sesión con una cuenta con el rol Lector de seguridad.
- Revisa en el CSV los dominios con más correos y la columna Situacion.
- Bloquea los dominios que no reconozcas y purga los correos marcados como Sigue en buzones.
Ver el código completo de get-correos-maliciosos.ps1
<#
.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
}