Revisión de seguridad
$(H $env:COMPUTERNAME) · $(Get-Date -Format 'dd-MM-yyyy HH:mm')
# Revisión de seguridad de Windows Herramientas BAIT · https://bait.cl/herramientas/script-revision-seguridad/ Qué hace: Revisa 12 puntos básicos de seguridad y les pone un semáforo (OK, Advertencia o Riesgo): BitLocker, firewall por perfil, Microsoft Defender (protección en tiempo real y firmas), actualizaciones pendientes, cuentas administradoras, cuenta Invitado, SMBv1, Escritorio remoto, Control de cuentas de usuario (UAC), contraseñas sin vencimiento o vacías y bloqueo de pantalla por inactividad. Muestra el resultado en colores en la consola y guarda un resumen HTML en el Escritorio. Solo lee la configuración: no cambia nada del equipo. Requiere administrador: si no lo es, pregunta si quieres abrirlo de nuevo como administrador. Uso: Clic derecho > Ejecutar con PowerShell o bien: powershell -ExecutionPolicy Bypass -File .\revision-seguridad.ps1 #> $Host.UI.RawUI.WindowTitle = 'Revisión de seguridad (BAIT)' # ---------- permisos de administrador ---------- function Test-Administrador { $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } if (-not (Test-Administrador)) { Write-Host '' Write-Host 'Esta revisión necesita permisos de administrador para leer BitLocker, Defender y las cuentas.' -ForegroundColor Yellow $r = Read-Host '¿Abrir de nuevo como administrador? (S/N)' if ($r -match '^[sS]' -and $PSCommandPath) { Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$PSCommandPath`"") -Verb RunAs } elseif (-not $PSCommandPath) { Write-Host 'Abre PowerShell como administrador y vuelve a ejecutar el script.' } exit } # ---------- registro de resultados ---------- $resultados = New-Object System.Collections.Generic.List[object] # Estado: OK, Advertencia o Riesgo (o Info cuando no se pudo revisar) function Anotar([string]$Punto, [string]$Estado, [string]$Detalle, [string]$Consejo = '') { $resultados.Add([pscustomobject]@{ Punto = $Punto; Estado = $Estado; Detalle = $Detalle; Consejo = $Consejo }) $color = switch ($Estado) { 'OK' { 'Green' } 'Advertencia' { 'Yellow' } 'Riesgo' { 'Red' } default { 'Gray' } } Write-Host ('[{0,-11}] ' -f $Estado) -ForegroundColor $color -NoNewline Write-Host ('{0}: {1}' -f $Punto, $Detalle) } function Leer-Registro([string]$Ruta, [string]$Nombre) { try { return (Get-ItemProperty -Path $Ruta -Name $Nombre -ErrorAction Stop).$Nombre } catch { return $null } } Write-Host '' Write-Host '=== Revisión de seguridad ===' -ForegroundColor Cyan Write-Host "Equipo: $env:COMPUTERNAME Fecha: $(Get-Date -Format 'dd-MM-yyyy HH:mm')" Write-Host '' # 1. BitLocker en el disco del sistema try { $bl = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop if ([string]$bl.ProtectionStatus -eq 'On') { Anotar 'BitLocker' 'OK' "El disco $env:SystemDrive está cifrado y protegido." } elseif ([string]$bl.VolumeStatus -ne 'FullyDecrypted') { Anotar 'BitLocker' 'Advertencia' "El disco está cifrado pero la protección está suspendida." 'Reanuda la protección con Resume-BitLocker o desde el Panel de control.' } else { Anotar 'BitLocker' 'Riesgo' "El disco $env:SystemDrive no está cifrado." 'Si roban o pierden el equipo, cualquiera puede leer los archivos. Activa BitLocker y guarda la clave de recuperación.' } } catch { Anotar 'BitLocker' 'Advertencia' 'No disponible en este equipo (Windows Home o sin TPM).' 'En Windows Home revisa si existe "Cifrado de dispositivo" en Configuración > Privacidad y seguridad.' } # 2. Firewall por perfil try { foreach ($p in Get-NetFirewallProfile -ErrorAction Stop) { $nombre = switch ($p.Name) { 'Domain' { 'Dominio' } 'Private' { 'Privado' } 'Public' { 'Público' } default { $p.Name } } if ([string]$p.Enabled -eq 'True') { Anotar "Firewall ($nombre)" 'OK' 'Activado.' } else { Anotar "Firewall ($nombre)" 'Riesgo' 'Desactivado.' 'Actívalo en Seguridad de Windows > Firewall y protección de red.' } } } catch { Anotar 'Firewall' 'Info' 'No se pudo leer la configuración del firewall.' } # 3. Microsoft Defender try { $mp = Get-MpComputerStatus -ErrorAction Stop if ($mp.AMRunningMode -and $mp.AMRunningMode -ne 'Normal') { # Defender en modo pasivo: otro antivirus está a cargo $otro = (Get-CimInstance -Namespace 'root/SecurityCenter2' -ClassName AntiVirusProduct -ErrorAction SilentlyContinue | Where-Object { $_.displayName -notmatch 'Defender' } | Select-Object -ExpandProperty displayName) -join ', ' Anotar 'Antivirus' 'OK' "Defender en modo $($mp.AMRunningMode). Antivirus activo: $otro" } else { if ($mp.RealTimeProtectionEnabled) { Anotar 'Defender: tiempo real' 'OK' 'La protección en tiempo real está activa.' } else { Anotar 'Defender: tiempo real' 'Riesgo' 'La protección en tiempo real está apagada.' 'Actívala en Seguridad de Windows > Protección contra virus y amenazas.' } $edad = [int]$mp.AntivirusSignatureAge if ($edad -le 3) { Anotar 'Defender: firmas' 'OK' "Firmas actualizadas hace $edad día(s)." } elseif ($edad -le 7) { Anotar 'Defender: firmas' 'Advertencia' "Firmas con $edad días de antigüedad." 'Ejecuta Update-MpSignature o busca actualizaciones.' } else { Anotar 'Defender: firmas' 'Riesgo' "Firmas con $edad días de antigüedad." 'El antivirus no reconoce amenazas nuevas. Revisa Windows Update.' } } } catch { $av = Get-CimInstance -Namespace 'root/SecurityCenter2' -ClassName AntiVirusProduct -ErrorAction SilentlyContinue if ($av) { Anotar 'Antivirus' 'OK' ("Antivirus registrado: " + (($av | Select-Object -ExpandProperty displayName) -join ', ')) } else { Anotar 'Antivirus' 'Riesgo' 'No se encontró un antivirus activo.' 'Activa Microsoft Defender o instala un antivirus.' } } # 4. Actualizaciones pendientes (consulta a Windows Update; sin internet no se puede saber) Write-Host ' Buscando actualizaciones pendientes, puede tardar un minuto...' -ForegroundColor Gray try { $sesion = New-Object -ComObject 'Microsoft.Update.Session' $buscador = $sesion.CreateUpdateSearcher() $busqueda = $buscador.Search("IsInstalled=0 and IsHidden=0 and Type='Software'") $pendientes = $busqueda.Updates.Count $seguridad = 0 foreach ($u in $busqueda.Updates) { foreach ($c in $u.Categories) { if ($c.Name -match 'Security|Seguridad|Critical|Críticas') { $seguridad++; break } } } if ($pendientes -eq 0) { Anotar 'Actualizaciones' 'OK' 'No hay actualizaciones pendientes.' } elseif ($seguridad -gt 0) { Anotar 'Actualizaciones' 'Riesgo' "$pendientes pendientes, $seguridad de seguridad o críticas." 'Instálalas desde Configuración > Windows Update y reinicia.' } else { Anotar 'Actualizaciones' 'Advertencia' "$pendientes actualizaciones pendientes." 'Instálalas cuando puedas.' } } catch { $ultima = Get-HotFix -ErrorAction SilentlyContinue | Where-Object { $_.InstalledOn } | Sort-Object InstalledOn -Descending | Select-Object -First 1 if ($ultima) { $dias = [int]((Get-Date) - $ultima.InstalledOn).TotalDays $estado = 'OK'; if ($dias -gt 45) { $estado = 'Riesgo' } elseif ($dias -gt 30) { $estado = 'Advertencia' } Anotar 'Actualizaciones' $estado "No se pudo consultar Windows Update. La última actualización se instaló hace $dias días." 'Revisa que Windows Update funcione.' } else { Anotar 'Actualizaciones' 'Info' 'No se pudo consultar Windows Update.' } } # 5. Cuentas administradoras try { $admins = @(Get-LocalGroupMember -SID 'S-1-5-32-544' -ErrorAction Stop) $nombres = ($admins | ForEach-Object { $_.Name }) -join ', ' if ($admins.Count -le 2) { Anotar 'Administradores' 'OK' "$($admins.Count) cuenta(s): $nombres" } else { Anotar 'Administradores' 'Advertencia' "$($admins.Count) cuentas con permisos de administrador: $nombres" 'Usa una cuenta normal para el día a día y deja la de administrador solo para instalar.' } } catch { Anotar 'Administradores' 'Info' 'No se pudo leer el grupo Administradores (suele pasar con cuentas de dominio eliminadas).' 'Revisa con: net localgroup Administradores' } $adminIntegrado = Get-LocalUser -ErrorAction SilentlyContinue | Where-Object { $_.SID -like '*-500' } if ($adminIntegrado -and $adminIntegrado.Enabled) { Anotar 'Administrador integrado' 'Advertencia' "La cuenta $($adminIntegrado.Name) está habilitada." 'Es la primera que prueban los ataques. Desactívala si no se usa.' } # 6. Cuenta Invitado $invitado = Get-LocalUser -ErrorAction SilentlyContinue | Where-Object { $_.SID -like '*-501' } if ($invitado -and $invitado.Enabled) { Anotar 'Cuenta Invitado' 'Riesgo' 'La cuenta Invitado está habilitada.' ("Desactívala con: Disable-LocalUser -Name '" + $invitado.Name + "'") } else { Anotar 'Cuenta Invitado' 'OK' 'Deshabilitada.' } # 7. SMBv1 (protocolo antiguo usado por WannaCry) try { $smb = Get-SmbServerConfiguration -ErrorAction Stop if ($smb.EnableSMB1Protocol) { Anotar 'SMBv1' 'Riesgo' 'El protocolo SMBv1 está habilitado.' 'Desactívalo: Set-SmbServerConfiguration -EnableSMB1Protocol $false' } else { Anotar 'SMBv1' 'OK' 'Deshabilitado.' } } catch { Anotar 'SMBv1' 'Info' 'No se pudo revisar.' } # 8. Escritorio remoto (RDP) $rdpBloqueado = Leer-Registro 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' 'fDenyTSConnections' $nla = Leer-Registro 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' 'UserAuthentication' if ($rdpBloqueado -eq 1 -or $null -eq $rdpBloqueado) { Anotar 'Escritorio remoto' 'OK' 'Deshabilitado.' } elseif ($nla -eq 1) { Anotar 'Escritorio remoto' 'Advertencia' 'Habilitado, con autenticación a nivel de red (NLA).' 'No lo dejes expuesto a internet: úsalo solo por VPN o limita las IP en el firewall.' } else { Anotar 'Escritorio remoto' 'Riesgo' 'Habilitado y sin autenticación a nivel de red (NLA).' 'Activa NLA o deshabilita el Escritorio remoto si no se usa.' } # 9. Control de cuentas de usuario (UAC) $rutaUac = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' $lua = Leer-Registro $rutaUac 'EnableLUA' $aviso = Leer-Registro $rutaUac 'ConsentPromptBehaviorAdmin' if ($lua -eq 0) { Anotar 'UAC' 'Riesgo' 'El Control de cuentas de usuario está desactivado.' 'Actívalo: cualquier programa puede tomar control del equipo sin avisar.' } elseif ($aviso -eq 0) { Anotar 'UAC' 'Advertencia' 'UAC activo, pero eleva permisos sin preguntar.' 'Sube el nivel en Panel de control > Cuentas de usuario > Cambiar configuración de Control de cuentas.' } else { Anotar 'UAC' 'OK' 'Activo y pide confirmación.' } # 10. Contraseñas: cuentas habilitadas sin contraseña obligatoria o que nunca vencen $locales = Get-LocalUser -ErrorAction SilentlyContinue | Where-Object { $_.Enabled } $sinClave = @($locales | Where-Object { -not $_.PasswordRequired }) $sinVencer = @($locales | Where-Object { $null -eq $_.PasswordExpires }) if ($sinClave.Count -gt 0) { Anotar 'Contraseñas' 'Riesgo' ("Cuentas que pueden no tener contraseña: " + (($sinClave | ForEach-Object { $_.Name }) -join ', ')) 'Ponles una contraseña larga a todas las cuentas habilitadas.' } elseif ($sinVencer.Count -gt 0) { Anotar 'Contraseñas' 'Advertencia' ("Contraseñas que nunca vencen: " + (($sinVencer | ForEach-Object { $_.Name }) -join ', ')) 'No es grave si son largas y únicas. Si no lo son, cámbialas o define un vencimiento.' } else { Anotar 'Contraseñas' 'OK' 'Todas las cuentas locales habilitadas tienen contraseña con vencimiento.' } # 11. Bloqueo de pantalla por inactividad (política del equipo o protector de pantalla con contraseña) $inactividad = Leer-Registro $rutaUac 'InactivityTimeoutSecs' $protector = Leer-Registro 'HKCU:\Control Panel\Desktop' 'ScreenSaveActive' $protectorSeguro = Leer-Registro 'HKCU:\Control Panel\Desktop' 'ScreenSaverIsSecure' $protectorTiempo = Leer-Registro 'HKCU:\Control Panel\Desktop' 'ScreenSaveTimeOut' if ($inactividad -and [int]$inactividad -gt 0 -and [int]$inactividad -le 900) { Anotar 'Bloqueo de pantalla' 'OK' ("Se bloquea tras {0} minutos sin uso (política del equipo)." -f ([int]$inactividad / 60)) } elseif ($protector -eq '1' -and $protectorSeguro -eq '1' -and $protectorTiempo -and [int]$protectorTiempo -le 900) { Anotar 'Bloqueo de pantalla' 'OK' ("El protector de pantalla pide contraseña a los {0} minutos." -f ([int]$protectorTiempo / 60)) } else { Anotar 'Bloqueo de pantalla' 'Advertencia' 'No se encontró un bloqueo automático de 15 minutos o menos para este usuario.' 'Configúralo con el generador de ajustes de BAIT o en Configuración > Cuentas > Opciones de inicio de sesión.' } # ---------- resumen ---------- $ok = @($resultados | Where-Object { $_.Estado -eq 'OK' }).Count $adv = @($resultados | Where-Object { $_.Estado -eq 'Advertencia' }).Count $riesgo = @($resultados | Where-Object { $_.Estado -eq 'Riesgo' }).Count Write-Host '' Write-Host '=== Resumen ===' -ForegroundColor Cyan Write-Host "OK: $ok" -ForegroundColor Green Write-Host "Advertencias: $adv" -ForegroundColor Yellow Write-Host "Riesgos: $riesgo" -ForegroundColor Red function H($t) { return [System.Net.WebUtility]::HtmlEncode([string]$t) } $filas = ($resultados | ForEach-Object { $clase = switch ($_.Estado) { 'OK' { 'ok' } 'Advertencia' { 'warn' } 'Riesgo' { 'bad' } default { 'info' } } "
$(H $env:COMPUTERNAME) · $(Get-Date -Format 'dd-MM-yyyy HH:mm')