Saltar al contenido
Microsoft scripts

Scripts de Defender

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.

Antes de correrloCon Microsoft 365 Business Premium no funciona solo: Defender for Office 365 Plan 1 no incluye búsqueda avanzada. Necesitas Plan 2 o Microsoft Defender Suite for Business Premium. El remitente visible se puede falsificar: antes de bloquear un dominio, confirma que no sea el de un proveedor real al que suplantaron. Entregas cuenta destinatarios, no mensajes. EnNoDeseado cuenta destinatarios con el correo en su carpeta de correo no deseado: sigue a un clic de distancia.

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.csv

Obtiene el ranking de dominios y agrega los peores a la lista de bloqueo del tenant.

Cómo implementarlo

  1. Instala el módulo si no lo tienes: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser.
  2. Corre el script con -Dias e inicia sesión con una cuenta con el rol Lector de seguridad.
  3. Revisa en el CSV los dominios con más correos y la columna Situacion.
  4. 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
}

Relacionados