<# ============================================================================ OK-COMPUTER · Escaner de auditoria de seguridad de red (v2) ============================================================================ Auditoria DEFENSIVA y de DESCUBRIMIENTO a fondo. Inventaria la red, audita en profundidad la postura de seguridad del equipo y correlaciona los hallazgos con vulnerabilidades conocidas de Windows. NO explota, NO prueba contrasenas, NO ataca: descubre, documenta y recomienda. USO LEGITIMO UNICAMENTE: ejecutalo SOLO en redes donde el cliente te ha autorizado por escrito. El script registra esa autorizacion en el informe. Ejecutar (MEJOR como Administrador para la auditoria completa): powershell -ExecutionPolicy Bypass -File .\scanner-red.ps1 Parametros: -Target "192.168.1.0/24" Subred a escanear (por defecto autodetecta). -Empresa "Nombre Cliente" Nombre del cliente para el informe. -Quick Escaneo de puertos reducido (mas rapido). -SkipDiscovery Solo auditar este equipo, sin barrer la red. -Yes Salta la confirmacion (solo con autorizacion). ============================================================================ #> [CmdletBinding()] param( [string]$Target, [string]$Empresa = "", [switch]$Quick, [switch]$SkipDiscovery, [switch]$Yes ) $ErrorActionPreference = "SilentlyContinue" $ProgressPreference = "SilentlyContinue" Add-Type -AssemblyName System.Web | Out-Null $script:Findings = New-Object System.Collections.ArrayList $script:Hosts = New-Object System.Collections.ArrayList $script:Attacker = New-Object System.Collections.ArrayList # "que podria hacer un atacante" # -------------------------------------------------------------------------- # Salida y registro de hallazgos # -------------------------------------------------------------------------- function Write-Title($t) { Write-Host "`n=== $t ===" -ForegroundColor Cyan } function Add-Finding { param( [ValidateSet("Critico","Riesgo","Info","OK")] $Sev, $Area, $Titulo, $Detalle = "", $Solucion = "", $Impacto = "", $Ref = "" ) [void]$script:Findings.Add([pscustomobject]@{ Sev=$Sev; Area=$Area; Titulo=$Titulo; Detalle=$Detalle; Solucion=$Solucion; Impacto=$Impacto; Ref=$Ref }) if ($Impacto) { [void]$script:Attacker.Add($Impacto) } switch ($Sev) { "Critico" { Write-Host " [!!!] $Area - $Titulo" -ForegroundColor Red } "Riesgo" { Write-Host " [!] $Area - $Titulo" -ForegroundColor Yellow } "OK" { Write-Host " [OK] $Area - $Titulo" -ForegroundColor Green } default { Write-Host " [i] $Area - $Titulo" -ForegroundColor Gray } } if ($Impacto -and ($Sev -eq "Critico" -or $Sev -eq "Riesgo")) { $rf = if ($Ref) { " [$Ref]" } else { "" } Write-Host " Si no se arregla: $Impacto$rf" -ForegroundColor DarkGray } } function Reg([string]$path, [string]$name) { try { return (Get-ItemProperty -Path $path -Name $name -ErrorAction Stop).$name } catch { return $null } } # -------------------------------------------------------------------------- # Escaneo de puertos asincrono (rapido, no intrusivo: solo connect TCP) # -------------------------------------------------------------------------- $PORTS = [ordered]@{ 21="FTP"; 22="SSH"; 23="Telnet"; 25="SMTP"; 53="DNS"; 80="HTTP"; 110="POP3" 135="RPC/DCOM"; 139="NetBIOS"; 143="IMAP"; 389="LDAP"; 443="HTTPS"; 445="SMB" 465="SMTPS"; 587="SMTP"; 636="LDAPS"; 993="IMAPS"; 1433="SQL Server" 1434="SQL Browser"; 2049="NFS"; 3306="MySQL"; 3389="RDP"; 5432="PostgreSQL" 5900="VNC"; 5985="WinRM"; 5986="WinRM-S"; 6379="Redis"; 8000="HTTP/Camara" 8080="HTTP-alt"; 8443="HTTPS-alt"; 9100="Impresora"; 9200="Elasticsearch" 27017="MongoDB"; 37777="DVR Dahua"; 34567="DVR XMeye"; 49152="UPnP" 554="RTSP"; 5000="UPnP/DSM"; 5001="DSM-S"; 5060="SIP/VoIP"; 8554="RTSP-alt"; 8899="DVR"; 9000="Camara/NVR" } $QUICK_PORTS = 21,22,23,80,135,139,443,445,1433,3306,3389,5432,5900,5985,554,8000,8080,37777 $DB_PORTS = @{ 1433="SQL Server"; 3306="MySQL"; 5432="PostgreSQL"; 6379="Redis"; 27017="MongoDB"; 9200="Elasticsearch"; 2049="NFS" } function Scan-HostPorts { param([string]$IpAddress, $Ports, [int]$TimeoutMs = 700) $tasks = @() foreach ($pt in $Ports) { $c = New-Object System.Net.Sockets.TcpClient try { $iar = $c.BeginConnect($IpAddress, [int]$pt, $null, $null) } catch { continue } $tasks += [pscustomobject]@{ Port=[int]$pt; Client=$c; Iar=$iar } } Start-Sleep -Milliseconds $TimeoutMs $open = @() foreach ($t in $tasks) { if ($t.Iar.IsCompleted -and $t.Client.Connected) { $open += $t.Port } try { $t.Client.Close() } catch {} } return ($open | Sort-Object) } # Banner / cabecera HTTP rapida (solo lectura) function Get-HttpInfo { param([string]$IpAddress, [int]$Port, [bool]$Https = $false) try { $scheme = if ($Https) { "https" } else { "http" } $r = Invoke-WebRequest -Uri "$scheme`://$IpAddress`:$Port/" -TimeoutSec 3 -UseBasicParsing -ErrorAction Stop $srv = $r.Headers["Server"] $title = "" if ($r.Content -match "(.*?)") { $title = $matches[1].Trim() } return ("$srv $title").Trim() } catch { return "" } } # OUI -> fabricante (camaras, DVR, alarmas e IoT comunes) $OUI_VENDOR = @{ # Hikvision "BCAD28"="Hikvision"; "4419B7"="Hikvision"; "C056E3"="Hikvision"; "ACCB51"="Hikvision" "28571C"="Hikvision"; "44A642"="Hikvision"; "18800C"="Hikvision"; "F84DFC"="Hikvision" "542B8D"="Hikvision"; "E0CA94"="Hikvision"; "C40938"="Hikvision" # Dahua "001C27"="Dahua"; "9CA3A9"="Dahua"; "E03E44"="Dahua"; "3C1A89"="Dahua" "14A78B"="Dahua"; "38AF29"="Dahua"; "4C11BF"="Dahua"; "90020A"="Dahua"; "08ED02"="Dahua" # Axis "00408C"="Axis"; "ACCC8E"="Axis"; "B8A44F"="Axis"; "E82725"="Axis" # Uniview "48EA63"="Uniview"; "5C5AEC"="Uniview" # Reolink "EC71DB"="Reolink" # Hanwha / Samsung Techwin "0009DC"="Hanwha"; "002342"="Hanwha" # XiongMai (DVR/camaras baratas, carne de Mirai) "00128A"="XiongMai"; "001210"="XiongMai" # Paneles de alarma / accesos "F4B85E"="Ajax"; "00184F"="Risco"; "0040C7"="Honeywell"; "B82410"="Honeywell" # NAS (almacenamiento en red) "001132"="Synology"; "245EBE"="QNAP"; "00089B"="QNAP" "00903D"="WD"; "0014EE"="WD"; "00903A"="WD" # Routers / equipo de red "4C5E0C"="Mikrotik"; "B869F4"="Mikrotik"; "E48D8C"="Mikrotik"; "DC2C6E"="Mikrotik" "50C7BF"="TP-Link"; "A42BB0"="TP-Link"; "F4F26D"="TP-Link"; "14CC20"="TP-Link" "5404A6"="Asus"; "1C872C"="Asus"; "AC9E17"="Asus"; "088620"="Asus" } $ALARM_BRANDS = @("Ajax","Risco","Honeywell","Resideo","Visonic") function Guess-Vendor($mac) { if (-not $mac) { return "desconocido" } $clean = ($mac -replace '[^0-9A-Fa-f]', '').ToUpper() if ($clean.Length -lt 6) { return "MAC $mac" } $p = $clean.Substring(0, 6) if ($OUI_VENDOR.ContainsKey($p)) { return $OUI_VENDOR[$p] } return "MAC $mac" } # Inteligencia de vulnerabilidades por marca de videovigilancia. # Solo para REPORTAR: el escaner no explota ni prueba estas credenciales. $CAM_INTEL = @{ "Hikvision" = @{ cve="CVE-2021-36260"; cred="admin / 12345"; impacto="Si el firmware no esta parcheado, ejecutar comandos en la camara SIN contrasena (RCE) y usarla para entrar en la red, espiar o pivotar a otros equipos." } "Dahua" = @{ cve="CVE-2021-33044 / 33045"; cred="admin / admin"; impacto="Saltarse el login del DVR/camara y ver el video en directo y grabado sin credenciales si esta sin parchear." } "Axis" = @{ cve="CVE-2018-10660 / 10661"; cred="root / (definida al instalar)"; impacto="En firmware antiguo, ejecutar comandos en la camara sin autenticarse." } "Uniview" = @{ cve="CVE-2021-45039 (varios)"; cred="admin / 123456"; impacto="Acceso no autorizado a la camara/NVR si esta sin parchear." } "Reolink" = @{ cve="exposicion de credenciales (varios)"; cred="admin / (vacia)"; impacto="Acceso al video y a la configuracion si las credenciales son las de fabrica." } "Hanwha" = @{ cve="varios segun modelo"; cred="admin / 4321"; impacto="Acceso no autorizado en firmware antiguo o con la contrasena de fabrica." } "XiongMai" = @{ cve="Mirai / CVE-2017-7577"; cred="root / xc3511 (oculta)"; impacto="DVR/camara barato muy explotado por botnets (Mirai): control remoto, espionaje y uso para ataques DDoS. La contrasena root suele estar oculta y no se puede cambiar." } } # Inteligencia por marca de NAS (almacenamiento en red). $NAS_INTEL = @{ "Synology" = @{ cve="varios DSM + ransomware eCh0raix"; cred="admin / admin"; impacto="Cifrar el NAS entero con ransomware (eCh0raix) y perder a la vez los datos y las copias de seguridad; o acceso remoto al NAS si DSM no esta parcheado." } "QNAP" = @{ cve="DeadBolt / Qlocker (varios CVE-2021/2022)"; cred="admin / admin"; impacto="Cifrar el NAS con ransomware (DeadBolt/Qlocker) y exigir rescate; o tomar control remoto si QTS no esta parcheado. Los NAS son uno de los blancos preferidos del ransomware." } "WD" = @{ cve="CVE-2018-17153 (auth bypass a root)"; cred="admin / (vacia)"; impacto="Acceso remoto como root al NAS si el firmware MyCloud es antiguo: leer, borrar o cifrar todo lo guardado." } } # Inteligencia por marca de equipo de red (router, gateway). $ROUTER_INTEL = @{ "Mikrotik" = @{ cve="CVE-2018-14847 (Winbox auth bypass)"; cred="admin / (vacia)"; impacto="Leer la contrasena del router si RouterOS no esta parcheado y entrar en toda la red; redirigir o espiar el trafico." } "TP-Link" = @{ cve="varios (CVE-2017-13772, CVE-2023-1389...)"; cred="admin / admin"; impacto="Cambiar el DNS del router para redirigir el trafico a paginas falsas, o ejecutar comandos si el firmware es antiguo." } "Asus" = @{ cve="varios AsusWRT"; cred="admin / admin"; impacto="Tomar control del router con las credenciales de fabrica y manipular toda la red." } } # -------------------------------------------------------------------------- # 0 · Autorizacion # -------------------------------------------------------------------------- Clear-Host Write-Host "============================================================" -ForegroundColor DarkCyan Write-Host " OK-COMPUTER · Escaner de auditoria de seguridad (v2)" -ForegroundColor White Write-Host "============================================================" -ForegroundColor DarkCyan Write-Host "" Write-Host " Esta herramienta SOLO debe ejecutarse en redes donde el" -ForegroundColor Yellow Write-Host " cliente te ha AUTORIZADO POR ESCRITO la auditoria." -ForegroundColor Yellow Write-Host "" $autorizadoPor = "" if (-not $Yes) { $resp = Read-Host " Confirmas que tienes autorizacion para auditar esta red? (escribe SI)" if ($resp -notmatch '^\s*SI\s*$') { Write-Host "`n Cancelado. Sin autorizacion no se ejecuta.`n" -ForegroundColor Red exit } if (-not $Empresa) { $Empresa = Read-Host " Nombre del cliente" } $autorizadoPor = Read-Host " Quien autoriza (nombre y cargo)" } $inicio = Get-Date if (-not $Empresa) { $Empresa = "Cliente" } $esAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) # ========================================================================== # 1 · AUDITORIA DEL EQUIPO # ========================================================================== Write-Title "1 - Auditoria del equipo" if (-not $esAdmin) { Add-Finding "Info" "Equipo" "Ejecutado SIN privilegios de administrador" ` "Varias comprobaciones (SMBv1, BitLocker, parches, servicios) quedan incompletas." ` "Para la auditoria completa, ejecutar PowerShell como Administrador." } # --- Sistema operativo y soporte --- $os = Get-CimInstance Win32_OperatingSystem $build = [int]($os.BuildNumber) $osName = $os.Caption $eol = $false if ($osName -match "Windows 7|Windows 8|Vista|XP|Server 2003|Server 2008|Server 2012") { $eol = $true } if ($eol) { Add-Finding "Critico" "Equipo" "Sistema operativo SIN soporte: $osName" ` "Sin parches de seguridad. Puerta de entrada favorita del ransomware." ` "Renovar o actualizar a una version con soporte (Windows 10 22H2 / 11)." ` "Explotar fallos publicos sin parchear para tomar el control del equipo." } else { Add-Finding "OK" "Equipo" "Sistema operativo con soporte: $osName (build $build)" } # --- Ultima actualizacion --- $ultHotfix = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1 if ($ultHotfix -and $ultHotfix.InstalledOn) { $dias = (New-TimeSpan -Start $ultHotfix.InstalledOn -End (Get-Date)).Days if ($dias -gt 45) { Add-Finding "Riesgo" "Parches" "Sin actualizaciones desde hace $dias dias" ` "Ultima: $($ultHotfix.InstalledOn.ToString('yyyy-MM-dd')) ($($ultHotfix.HotFixID))." ` "Activar Windows Update automatico y aplicar parches pendientes." ` "Aprovechar vulnerabilidades parcheadas hace meses que este equipo aun no tiene." } else { Add-Finding "OK" "Parches" "Actualizado recientemente (hace $dias dias)" } } # --- Reinicio pendiente --- $pending = $false if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $pending = $true } if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $pending = $true } if ($pending) { Add-Finding "Info" "Parches" "Hay un reinicio pendiente" ` "Algunos parches no se aplican del todo hasta reiniciar." "Reiniciar el equipo." } # --- Firewall --- $fw = Get-NetFirewallProfile $fwOff = $fw | Where-Object { -not $_.Enabled } if ($fwOff) { Add-Finding "Riesgo" "Equipo" "Firewall desactivado en: $($fwOff.Name -join ', ')" ` "" "Activar el firewall de Windows en todos los perfiles." ` "Conectarse libremente a servicios del equipo que el firewall deberia bloquear." } elseif ($fw) { Add-Finding "OK" "Equipo" "Firewall activo en todos los perfiles" } # --- Antivirus / Defender --- $mp = Get-MpComputerStatus if ($mp) { if (-not $mp.RealTimeProtectionEnabled) { Add-Finding "Critico" "Equipo" "Proteccion en tiempo real DESACTIVADA" ` "" "Activar la proteccion en tiempo real del antivirus." ` "Ejecutar malware sin que nada lo detenga." } else { Add-Finding "OK" "Equipo" "Antivirus con proteccion en tiempo real activa" } if ($mp.AntivirusSignatureLastUpdated) { $antig = (New-TimeSpan -Start $mp.AntivirusSignatureLastUpdated -End (Get-Date)).Days if ($antig -gt 7) { Add-Finding "Riesgo" "Equipo" "Firmas de antivirus desactualizadas ($antig dias)" "" "Forzar actualizacion de firmas y revisar conectividad." "El antivirus no reconoce el malware mas reciente y puede dejarlo pasar." } } # Exclusiones de Defender (pueden esconder malware) $excl = (Get-MpPreference).ExclusionPath if ($excl) { Add-Finding "Riesgo" "Equipo" "Defender tiene rutas excluidas del analisis" ` ("Excluido: " + (($excl | Select-Object -First 4) -join '; ')) ` "Revisar que esas exclusiones sean legitimas; el malware suele esconderse ahi." ` "Colocar malware en una carpeta excluida para que el antivirus lo ignore." } } # --- SMBv1 --- $smb1 = (Get-SmbServerConfiguration).EnableSMB1Protocol if ($smb1 -eq $true) { Add-Finding "Critico" "Equipo" "SMBv1 ACTIVADO (EternalBlue / MS17-010)" ` "Protocolo obsoleto explotado por WannaCry y NotPetya." ` "Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol" ` "Cifrar toda la red con ransomware sin necesidad de contrasena (EternalBlue)." ` "MS17-010 / CVE-2017-0144" } elseif ($smb1 -eq $false) { Add-Finding "OK" "Equipo" "SMBv1 desactivado" } # --- Firma SMB --- $smbSign = (Get-SmbServerConfiguration).RequireSecuritySigning if ($smbSign -eq $false) { Add-Finding "Riesgo" "Red" "SMB sin exigir firma (relay / SMB relay attack)" ` "Permite ataques de retransmision de credenciales en la red." ` "Exigir la firma SMB por GPO (RequireSecuritySigning)." ` "Robar y reutilizar credenciales mediante SMB relay." ` "NTLM relay / CVE-2019-1040" } # --- WDigest (credenciales en texto plano en memoria) --- $wdigest = Reg "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" "UseLogonCredential" if ($wdigest -eq 1) { Add-Finding "Critico" "Credenciales" "WDigest guarda contrasenas en texto plano en memoria" ` "Permite extraerlas con herramientas tipo Mimikatz." ` "Poner UseLogonCredential=0 en el registro." ` "Volcar las contrasenas en claro de quien haya iniciado sesion." ` "Mimikatz / MITRE T1003" } # --- UAC --- $uac = Reg "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" "EnableLUA" if ($uac -eq 0) { Add-Finding "Critico" "Equipo" "UAC (Control de cuentas) DESACTIVADO" ` "Sin UAC, cualquier proceso se ejecuta con permisos altos sin avisar." ` "Reactivar UAC (EnableLUA=1)." ` "Ejecutar acciones de administrador sin ninguna advertencia." } # --- AutoLogon con contrasena en claro --- $alPwd = Reg "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "DefaultPassword" if ($alPwd) { Add-Finding "Critico" "Credenciales" "Contrasena de inicio de sesion guardada en el registro (AutoLogon)" ` "La contrasena del usuario esta en texto plano en el registro." ` "Quitar el AutoLogon o usar el cifrado LSA secret." ` "Leer directamente la contrasena del usuario desde el registro." } # --- LLMNR / NBT-NS (envenenamiento - Responder) --- $llmnr = Reg "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" "EnableMulticast" if ($llmnr -ne 0) { Add-Finding "Riesgo" "Red" "LLMNR habilitado (envenenamiento de nombres)" ` "Permite a un atacante en la red robar hashes de credenciales (Responder)." ` "Desactivar LLMNR por GPO (EnableMulticast=0)." ` "Hacerse pasar por otro equipo y capturar credenciales de la red." ` "LLMNR/NBT-NS poisoning (Responder)" } # --- PowerShell v2 (downgrade) --- try { $psv2 = Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 -ErrorAction Stop if ($psv2.State -eq "Enabled") { Add-Finding "Riesgo" "Equipo" "PowerShell v2 habilitado (ataque de downgrade)" ` "PS v2 no registra ni aplica las protecciones modernas." ` "Desinstalar la caracteristica PowerShell v2." ` "Bajar a PS v2 para ejecutar codigo sin que se registre." } } catch {} # --- BitLocker --- $bl = $null try { $bl = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop } catch { $bl = $null } if ($bl) { if ($bl.ProtectionStatus -ne "On") { Add-Finding "Riesgo" "Equipo" "Disco del sistema SIN cifrar (BitLocker off)" ` "Si roban el equipo, los datos se leen sin contrasena." ` "Activar BitLocker en discos con datos sensibles." ` "Sacar el disco o arrancar desde un USB y leer todos los datos." } else { Add-Finding "OK" "Equipo" "Disco del sistema cifrado con BitLocker" } } # --- RDP --- $rdpDeny = Reg "HKLM:\System\CurrentControlSet\Control\Terminal Server" "fDenyTSConnections" if ($rdpDeny -eq 0) { $nla = Reg "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" "UserAuthentication" $nlaTxt = if ($nla -eq 0) { " y SIN autenticacion de red (NLA)" } else { "" } $sev = if ($nla -eq 0) { "Critico" } else { "Riesgo" } Add-Finding $sev "Red" "Escritorio remoto (RDP) habilitado$nlaTxt" ` "RDP es uno de los vectores de intrusion mas usados (BlueKeep, fuerza bruta)." ` "Si no se usa, desactivar. Si se usa, solo por VPN, con NLA y doble factor." ` "Entrar al escritorio del equipo por RDP (fuerza bruta o BlueKeep)." ` "CVE-2019-0708 (BlueKeep)" } # --- Servicios con ruta sin comillas (privilege escalation clasico) --- $unquoted = Get-CimInstance Win32_Service | Where-Object { $_.PathName -and $_.PathName -notmatch '^"' -and $_.PathName -match ' ' -and $_.PathName -notmatch '^[A-Za-z]:\\Windows\\' -and $_.StartMode -ne 'Disabled' } | Select-Object -First 5 if ($unquoted) { Add-Finding "Riesgo" "Equipo" "Servicios con ruta sin comillas (escalada de privilegios)" ` ("Ej.: " + (($unquoted | ForEach-Object { $_.Name }) -join ', ')) ` "Poner comillas en el ImagePath de esos servicios." ` "Colocar un ejecutable en la ruta y que arranque como SYSTEM." } # ========================================================================== # 2 · CUENTAS Y ACCESOS # ========================================================================== Write-Title "2 - Cuentas y accesos" $guest = Get-LocalUser | Where-Object { $_.SID -like '*-501' } if ($guest -and $guest.Enabled) { Add-Finding "Riesgo" "Cuentas" "Cuenta Invitado habilitada" "" "Deshabilitar la cuenta de invitado." "Acceder al equipo sin credenciales." } $adminAcct = Get-LocalUser | Where-Object { $_.SID -like '*-500' } if ($adminAcct -and $adminAcct.Enabled) { Add-Finding "Info" "Cuentas" "Cuenta Administrador integrada activa" "" "Renombrarla y/o deshabilitarla; usar cuentas nominales." } $sinCaducar = Get-LocalUser | Where-Object { $_.Enabled -and $_.PasswordExpires -eq $null -and $_.Name -notmatch 'DefaultAccount|WDAGUtility' } if ($sinCaducar) { Add-Finding "Riesgo" "Cuentas" "Contrasenas que nunca caducan: $($sinCaducar.Name -join ', ')" ` "" "Aplicar politica de caducidad de contrasenas." ` "Reutilizar una contrasena filtrada que nunca cambia." } # PasswordRequired=false NO significa que la cuenta este sin contrasena: es la # politica (si esta apagada, la cuenta PUEDE quedarse sin ella). Para detectar # cuentas realmente sin contrasena, combinamos politica relajada + PasswordLastSet # vacio (nunca se establecio una). Asi evitamos falsos positivos en cuentas que # SI tienen contrasena pero tienen la politica relajada (caso habitual en # cuentas Microsoft o cuentas locales convertidas). $sinPwd = Get-LocalUser | Where-Object { $_.Enabled -and $_.PasswordRequired -eq $false -and -not $_.PasswordLastSet } if ($sinPwd) { Add-Finding "Critico" "Cuentas" "Cuentas SIN contrasena: $($sinPwd.Name -join ', ')" ` "Detectado por PasswordRequired=false Y PasswordLastSet vacio (no se ha establecido nunca una contrasena)." ` "Exigir una contrasena fuerte a esas cuentas o deshabilitarlas." ` "Iniciar sesion en el equipo sin necesidad de contrasena." } # Por separado: cuentas con politica relajada pero que SI tienen contrasena # establecida -> solo informativo, no critico. Se ignora DefaultAccount y la # WDAGUtility (cuentas de sistema gestionadas por Windows). $pwdPolicyOff = Get-LocalUser | Where-Object { $_.Enabled -and $_.PasswordRequired -eq $false -and $_.PasswordLastSet -and $_.Name -notmatch 'DefaultAccount|WDAGUtility' } if ($pwdPolicyOff) { Add-Finding "Info" "Cuentas" "Cuentas con politica de contrasena relajada: $($pwdPolicyOff.Name -join ', ')" ` "Tienen contrasena, pero la politica permite quedarse sin ella si se cambia (PasswordRequired=false). Riesgo bajo si la contrasena es fuerte." ` "Reforzar la politica: Set-LocalUser -Name 'NOMBRE' -PasswordRequired:`$true" } # Administradores locales (por SID, vale en Windows en espanol) try { $admins = Get-LocalGroupMember -SID 'S-1-5-32-544' -ErrorAction Stop | ForEach-Object { $_.Name } if ($admins.Count -gt 3) { Add-Finding "Riesgo" "Cuentas" "Demasiados administradores locales ($($admins.Count))" ` ("Miembros: " + ($admins -join ', ')) ` "Reducir el grupo Administradores al minimo imprescindible." ` "Cualquiera de esas cuentas comprometida da control total del equipo." } else { Add-Finding "OK" "Cuentas" "Administradores locales acotados ($($admins.Count))" } } catch {} # ========================================================================== # 3 · EXPOSICION LOCAL (puertos a la escucha y recursos compartidos) # ========================================================================== Write-Title "3 - Exposicion local" $listen = Get-NetTCPConnection -State Listen | Where-Object { $_.LocalAddress -in '0.0.0.0','::' } | Select-Object -ExpandProperty LocalPort -Unique $riesgoLocal = $listen | Where-Object { $_ -in 445,3389,5985,5986,1433,3306,5432,139,135 } if ($riesgoLocal) { Add-Finding "Info" "Equipo" "Servicios sensibles a la escucha: $((($riesgoLocal | Sort-Object) -join ', '))" ` "Estos puertos abiertos al exterior amplian la superficie de ataque." ` "Cerrar o filtrar por firewall los que no se usen." } $shares = Get-SmbShare | Where-Object { $_.Name -notmatch '\$$' } foreach ($sh in $shares) { $acc = Get-SmbShareAccess -Name $sh.Name | Where-Object { $_.AccountName -match 'Everyone|Todos' -and $_.AccessRight -ne 'Deny' } if ($acc) { Add-Finding "Riesgo" "Red" "Recurso compartido abierto a 'Todos': $($sh.Name)" ` "Ruta: $($sh.Path)" ` "Restringir el acceso del recurso a usuarios concretos." ` "Leer o modificar los archivos de ese recurso desde cualquier equipo de la red." } } # ========================================================================== # 4 · WIFI # ========================================================================== Write-Title "4 - Redes wifi" $wifi = netsh wlan show networks mode=bssid 2>$null if ($wifi) { $abiertas = ($wifi | Select-String "Autenticaci|Authentication" | Where-Object { $_ -match "Abrir|Open" }).Count if ($abiertas -gt 0) { Add-Finding "Riesgo" "Wifi" "$abiertas red(es) wifi abiertas en el entorno" ` "Una wifi abierta o WEP es una puerta a la red." ` "Usar WPA2/WPA3 y separar la wifi de invitados de la de trabajo." ` "Conectarse a la red sin contrasena." } else { Add-Finding "OK" "Wifi" "Sin wifis abiertas en el entorno" } } # Claves wifi guardadas (texto plano) $perfiles = (netsh wlan show profiles 2>$null | Select-String ":\s*(.+)$") | ForEach-Object { $_.Matches.Groups[1].Value.Trim() } $conClave = 0 foreach ($pf in $perfiles) { $det = netsh wlan show profile name="$pf" key=clear 2>$null if ($det -match "Contenido de (la )?clave|Key Content") { $conClave++ } } if ($conClave -gt 0) { Add-Finding "Riesgo" "Wifi" "$conClave clave(s) wifi guardadas en claro en este equipo" ` "Cualquiera con acceso al equipo puede leer las contrasenas wifi." ` "Limitar quien usa el equipo; las claves se ven con 'netsh wlan show profile key=clear'." ` "Leer las contrasenas wifi guardadas y entrar en la red." } # ========================================================================== # 5 · DESCUBRIMIENTO DE RED # ========================================================================== if (-not $SkipDiscovery) { Write-Title "5 - Descubrimiento de dispositivos en la red" if (-not $Target) { $ipcfg = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -eq "Up" } | Select-Object -First 1 $miIp = $ipcfg.IPv4Address.IPAddress if ($miIp) { $base = ($miIp -split '\.')[0..2] -join '.'; $Target = "$base.0/24" } } else { $base = (($Target -split '/')[0] -split '\.')[0..2] -join '.' } Write-Host " [i] Subred objetivo: $Target" -ForegroundColor Gray # Barrido de hosts vivos (ping asincrono) Write-Host " [i] Barriendo 254 direcciones..." -ForegroundColor Gray $pings = 1..254 | ForEach-Object { $p = New-Object System.Net.NetworkInformation.Ping [pscustomobject]@{ Ip="$base.$_"; Task=$p.SendPingAsync("$base.$_", 500) } } [System.Threading.Tasks.Task]::WaitAll($pings.Task) | Out-Null $vivos = $pings | Where-Object { $_.Task.Result.Status -eq "Success" } | Select-Object -ExpandProperty Ip # Tabla ARP para MAC/fabricante $arp = @{} try { Get-NetNeighbor -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.LinkLayerAddress } | ForEach-Object { $arp[$_.IPAddress] = $_.LinkLayerAddress } } catch {} Write-Host " [i] $($vivos.Count) host(s) vivos. Escaneando puertos..." -ForegroundColor Gray $portList = if ($Quick) { $QUICK_PORTS } else { $PORTS.Keys } foreach ($ip in $vivos) { $abiertos = Scan-HostPorts -IpAddress $ip -Ports $portList $nombre = try { [System.Net.Dns]::GetHostEntry($ip).HostName } catch { "" } $mac = $arp[$ip] # --- Huella HTTP del panel web (para deducir marca) --- $banner = "" foreach ($wp in 80,8000,8080,5000) { if (($abiertos -contains $wp) -and -not $banner) { $banner = Get-HttpInfo -IpAddress $ip -Port $wp } } # --- Marca: primero por MAC (OUI), si no por la huella del panel --- $marca = Guess-Vendor $mac $conocidas = @($CAM_INTEL.Keys) + @($NAS_INTEL.Keys) + @($ROUTER_INTEL.Keys) + $ALARM_BRANDS if ($marca -notin $conocidas) { if ($banner -match 'Hikvision|App-webs|DNVRS') { $marca = 'Hikvision' } elseif ($banner -match 'Dahua') { $marca = 'Dahua' } elseif ($banner -match 'Axis') { $marca = 'Axis' } elseif ($banner -match 'Uniview') { $marca = 'Uniview' } elseif ($banner -match 'Reolink') { $marca = 'Reolink' } elseif ($banner -match 'uc-httpd|Hi3520|XiongMai|NETSurveillance') { $marca = 'XiongMai' } elseif ($banner -match 'Synology|DiskStation|DSM/') { $marca = 'Synology' } elseif ($banner -match 'QNAP|QTS|QuTS') { $marca = 'QNAP' } elseif ($banner -match 'MyCloud|WDMyCloud') { $marca = 'WD' } elseif ($banner -match 'RouterOS|MikroTik') { $marca = 'Mikrotik' } elseif ($banner -match 'TP-Link|tplinkwifi') { $marca = 'TP-Link' } elseif ($banner -match 'ASUSWRT|Asus Wireless') { $marca = 'Asus' } } $marcaConocida = ($marca -and $marca -notmatch '^MAC |^desconocido$') # --- Clasificacion del dispositivo --- $esVideo = ($abiertos -contains 554 -or $abiertos -contains 8000 -or $abiertos -contains 37777 -or $abiertos -contains 34567 -or $abiertos -contains 8899 -or $abiertos -contains 8554 -or $abiertos -contains 9000) $esGrabador = ($abiertos -contains 37777 -or $abiertos -contains 34567 -or $abiertos -contains 8899 -or ($esVideo -and ($abiertos -contains 8000))) $esAlarma = ($ALARM_BRANDS -contains $marca) $esNas = $NAS_INTEL.ContainsKey($marca) -or (($abiertos -contains 5000) -and ($abiertos -contains 5001)) $esRouter = $ROUTER_INTEL.ContainsKey($marca) $esVoIP = $abiertos -contains 5060 $tipo = "Equipo / desconocido" if ($esAlarma) { $tipo = "Panel de alarma / accesos" } elseif ($esNas) { $tipo = $(if ($marcaConocida) { "NAS $marca" } else { "NAS" }) } elseif ($esGrabador) { $tipo = "Videograbador (DVR/NVR)" } elseif ($esVideo) { $tipo = "Camara IP" } elseif ($esRouter) { $tipo = "Router $marca" } elseif ($esVoIP) { $tipo = "Centralita / VoIP (SIP)" } elseif ($abiertos -contains 1433 -or $abiertos -contains 3306 -or $abiertos -contains 5432 -or $abiertos -contains 27017) { $tipo = "Servidor de base de datos" } elseif ($abiertos -contains 3389 -or $abiertos -contains 5985) { $tipo = "Servidor / equipo Windows" } elseif ($abiertos -contains 9100 -or $abiertos -contains 631) { $tipo = "Impresora" } elseif (($abiertos -contains 80 -or $abiertos -contains 443) -and $abiertos.Count -le 3) { $tipo = "Router / dispositivo web" } [void]$script:Hosts.Add([pscustomobject]@{ Ip=$ip; Nombre=$nombre; Mac=$mac; Tipo=$tipo Marca=$(if ($marcaConocida) { $marca } else { "" }) Puertos=(($abiertos | ForEach-Object { "$_/$($PORTS[[string]$_])" }) -join ", ") Banner=$banner }) $etq = if ($nombre) { "$ip ($nombre)" } else { $ip } $marcaTxt = if ($marcaConocida) { " · $marca" } else { "" } Write-Host " $etq [$tipo$marcaTxt] -> $(($abiertos) -join ', ')" -ForegroundColor White # --- Hallazgos por host --- foreach ($dp in $DB_PORTS.Keys) { if ($abiertos -contains [int]$dp) { Add-Finding "Critico" "Bases de datos" "$($DB_PORTS[$dp]) accesible en red: $ip" ` "Una base de datos expuesta en la red es un objetivo directo de robo y ransomware." ` "Cerrar el puerto al exterior; acceso solo desde la app y con credenciales fuertes." ` "Conectarse a la base de datos para robarla, borrarla o cifrarla." } } if ($abiertos -contains 23) { Add-Finding "Critico" "Red" "Telnet abierto en $ip" ` "Telnet va sin cifrar; tipico en camaras/DVR antiguos." ` "Desactivar Telnet; usar SSH o panel seguro." ` "Capturar usuario y contrasena al vuelo (texto plano)." } if ($abiertos -contains 3389) { Add-Finding "Riesgo" "Red" "RDP (3389) abierto en $ip" "" ` "Limitar RDP a VPN y doble factor; cerrar la exposicion directa." ` "Fuerza bruta o BlueKeep contra el escritorio remoto." ` "CVE-2019-0708 (BlueKeep)" } if ($abiertos -contains 445 -and $abiertos -contains 139) { Add-Finding "Info" "Red" "Comparticion de archivos (SMB) en $ip" ` "Revisar que los recursos compartidos no esten abiertos a todos." "Auditar permisos de los recursos." } if ($esVideo -or $esGrabador) { $tipoTxt = if ($esGrabador) { "Videograbador (DVR/NVR)" } else { "Camara IP" } $como = if ($mac) { "MAC $mac" } else { "huella del panel web" } if ($CAM_INTEL.ContainsKey($marca)) { $intel = $CAM_INTEL[$marca] Add-Finding "Riesgo" "Videovigilancia" "$tipoTxt $marca en red: $ip" ` ("Detectado por $como. Credenciales de fabrica habituales de " + $marca + ": " + $intel.cred + ".") ` "Verificar que el firmware esta actualizado, cambiar las credenciales de fabrica, cerrar el acceso desde internet y aislar la videovigilancia en su propia VLAN." ` $intel.impacto ` $intel.cve } else { Add-Finding "Riesgo" "Videovigilancia" "$tipoTxt en red: $ip" ` "Detectado por $como. Marca no identificada con detalle." ` "Identificar marca/modelo, actualizar firmware, cambiar credenciales de fabrica y aislar en una VLAN propia." ` "Entrar a la camara/DVR con la contrasena de fabrica y ver o grabar imagenes; muchos modelos baratos son carne de botnet (Mirai)." } } if ($esAlarma) { Add-Finding "Riesgo" "Alarmas/Accesos" ("Posible panel de alarma o control de accesos (" + $marca + "): $ip") ` "Detectado por la MAC del fabricante." ` "Verificar credenciales y firmware, y que el panel no sea accesible desde fuera de la red." ` "Manipular o anular el sistema de alarma o de accesos si conserva las credenciales de fabrica." } if ($esNas) { if ($NAS_INTEL.ContainsKey($marca)) { $nasIntel = $NAS_INTEL[$marca] Add-Finding "Critico" "NAS / Almacenamiento" "NAS $marca en red: $ip" ` ("Credenciales de fabrica habituales de " + $marca + ": " + $nasIntel.cred + ". Los NAS son objetivo prioritario de ransomware.") ` "Actualizar firmware, cambiar credenciales de fabrica, deshabilitar el acceso del NAS desde internet y mantener una copia externa OFFLINE (no en el propio NAS)." ` $nasIntel.impacto ` $nasIntel.cve } else { Add-Finding "Riesgo" "NAS / Almacenamiento" "Posible NAS en red: $ip" ` "Puertos DSM/QTS abiertos pero marca no identificada con detalle." ` "Identificar el NAS, actualizar firmware, contrasenas fuertes y copia externa fuera del NAS." ` "Si el NAS guarda las copias de seguridad, un ataque borra a la vez los datos y las copias." } } if ($esRouter -and $ROUTER_INTEL.ContainsKey($marca)) { $rIntel = $ROUTER_INTEL[$marca] Add-Finding "Riesgo" "Red / Router" "Router $marca en red: $ip" ` ("Credenciales de fabrica habituales: " + $rIntel.cred + ". Cambiar el router compromete TODA la red.") ` "Actualizar firmware del router, cambiar la contrasena de administracion, deshabilitar el acceso desde WAN y revisar el DNS." ` $rIntel.impacto ` $rIntel.cve } if ($esVoIP) { Add-Finding "Riesgo" "VoIP / SIP" "Servicio SIP (5060) accesible en $ip" ` "Las centralitas SIP mal protegidas son blanco habitual de fraude telefonico (toll fraud)." ` "Restringir SIP a IPs conocidas, exigir credenciales fuertes y bloquear llamadas internacionales innecesarias por defecto." ` "Realizar llamadas internacionales a costa del cliente en pocas horas (factura de miles de euros)." } if ($abiertos -contains 21) { Add-Finding "Riesgo" "Red" "FTP (21) abierto en $ip" "FTP clasico no cifra credenciales." "Migrar a SFTP/FTPS." "Interceptar credenciales FTP." } if ($abiertos -contains 161) { Add-Finding "Riesgo" "Red" "SNMP abierto en $ip" "SNMP con 'community' por defecto filtra informacion del dispositivo." "Cambiar la community y restringir SNMP." "Leer la configuracion del dispositivo (usuarios, interfaces, rutas) con la community 'public'." } } if ($script:Hosts.Count -eq 0) { Write-Host " [i] Sin hosts (red aislada o firewall)." -ForegroundColor Gray } } else { Write-Host " [i] Descubrimiento de red omitido (-SkipDiscovery)." -ForegroundColor Gray } # ========================================================================== # 5b · COMANDOS DE SOLUCION POR HALLAZGO # Mapa "patron de titulo" -> comando PowerShell para arreglarlo. Se aplica # por Add-Member para no tocar Add-Finding ni cada llamada. Solo para # fallos del propio Windows con remedio por linea de comando. Para camaras, # NAS, routers, etc., el arreglo se hace en su propio panel. # ========================================================================== $FIXES = [ordered]@{ "SMBv1 ACTIVADO" = 'Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart' "Firewall desactivado" = 'Set-NetFirewallProfile -All -Enabled True' "Proteccion en tiempo real DESACTIVADA" = 'Set-MpPreference -DisableRealtimeMonitoring $false' "Firmas de antivirus desactualizadas" = 'Update-MpSignature' "SMB sin exigir firma" = 'Set-SmbServerConfiguration -RequireSecuritySigning $true -EnableSecuritySignature $true -Force' "WDigest guarda contrasenas" = "New-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest' -Name UseLogonCredential -PropertyType DWord -Value 0 -Force" "Hay un reinicio pendiente" = 'Restart-Computer' "Defender tiene rutas excluidas" = @' # Lista actual de exclusiones: Get-MpPreference | Select-Object -ExpandProperty ExclusionPath # Quitar una sospechosa (sustituye la ruta): Remove-MpPreference -ExclusionPath 'C:\ruta\sospechosa' '@ "UAC (Control de cuentas) DESACTIVADO" = @' Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLUA -Value 1 Write-Host 'Reinicia el equipo para que se aplique.' '@ "Contrasena de inicio de sesion guardada" = @' Remove-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword -Force Remove-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoAdminLogon -Force -ErrorAction SilentlyContinue '@ "LLMNR habilitado" = @' New-Item 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient' -Force | Out-Null New-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient' -Name EnableMulticast -PropertyType DWord -Value 0 -Force '@ "PowerShell v2 habilitado" = @' Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 -NoRestart Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart '@ "Disco del sistema SIN cifrar" = @' # BitLocker con TPM (anota bien la clave de recuperacion que devuelve): Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod XtsAes256 -UsedSpaceOnly -TpmProtector Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector '@ "Escritorio remoto (RDP) habilitado" = @' # Desactivar RDP del todo: Set-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 1 # O, si se mantiene, exigir NLA: Set-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name UserAuthentication -Value 1 '@ "Cuenta Invitado habilitada" = @' Disable-LocalUser -SID (Get-LocalUser | Where-Object { $_.SID -like '*-501' }).SID '@ "Contrasenas que nunca caducan" = @' # Para cada cuenta listada en el titulo (sustituye NOMBRE): Set-LocalUser -Name 'NOMBRE' -PasswordNeverExpires $false '@ "Cuentas SIN contrasena" = @' # Asigna una contrasena fuerte (sustituye NOMBRE): $p = Read-Host 'Nueva contrasena' -AsSecureString Set-LocalUser -Name 'NOMBRE' -Password $p '@ "Recurso compartido abierto a 'Todos'" = @' # Sustituye NOMBRE_RECURSO por el del titulo: Revoke-SmbShareAccess -Name 'NOMBRE_RECURSO' -AccountName 'Todos' -Force Revoke-SmbShareAccess -Name 'NOMBRE_RECURSO' -AccountName 'Everyone' -Force '@ "Sin actualizaciones desde hace" = @' # Instalar y usar PSWindowsUpdate: Install-Module PSWindowsUpdate -Force -Scope AllUsers Import-Module PSWindowsUpdate Get-WindowsUpdate -Install -AcceptAll -AutoReboot '@ "Servicios con ruta sin comillas" = @' # Para cada servicio listado (sustituye NOMBRE_SERVICIO y la ruta correcta): sc.exe config "NOMBRE_SERVICIO" binPath= "\"C:\Ruta Con Espacios\Servicio.exe\"" '@ } foreach ($pat in $FIXES.Keys) { foreach ($f in $script:Findings) { if ($f.Titulo -like "*$pat*") { $f | Add-Member -NotePropertyName Fix -NotePropertyValue $FIXES[$pat] -Force } } } # ========================================================================== # 6 · NOTA Y RESUMEN # ========================================================================== $crit = @($script:Findings | Where-Object { $_.Sev -eq "Critico" }).Count $ries = @($script:Findings | Where-Object { $_.Sev -eq "Riesgo" }).Count $oks = @($script:Findings | Where-Object { $_.Sev -eq "OK" }).Count $nota = [Math]::Max(0, 100 - ($crit * 15) - ($ries * 6)) Write-Title "Resumen" $col = if ($nota -ge 75) { "Green" } elseif ($nota -ge 50) { "Yellow" } else { "Red" } Write-Host " Nota de seguridad: $nota/100" -ForegroundColor $col Write-Host " $crit criticos · $ries riesgos · $oks correctos · $($script:Hosts.Count) dispositivos" -ForegroundColor Gray # ========================================================================== # 7 · INFORME HTML (en el Escritorio del usuario) # ========================================================================== $desktop = [Environment]::GetFolderPath('DesktopDirectory') if (-not $desktop -or -not (Test-Path $desktop)) { $desktop = Join-Path $env:USERPROFILE 'Desktop' } if (-not (Test-Path $desktop)) { $desktop = $env:USERPROFILE } $safe = ($Empresa -replace '[^\w\-]', '_') $OutFile = Join-Path $desktop ("informe-seguridad-$safe-$(Get-Date -Format 'yyyyMMdd-HHmm').html") $colNota = if ($nota -ge 75) { "#1f7a36" } elseif ($nota -ge 50) { "#c47a00" } else { "#b32424" } $veredicto = if ($nota -ge 75) { "Situacion razonable, con mejoras puntuales." } elseif ($nota -ge 50) { "Riesgos relevantes que conviene corregir pronto." } else { "Exposicion seria. Hay que actuar cuanto antes." } function HE($s) { if ($null -eq $s) { return "" }; [System.Web.HttpUtility]::HtmlEncode($s) } $sb = New-Object System.Text.StringBuilder [void]$sb.Append(@" Informe de seguridad - $(HE $Empresa)
OK·COMPUTER

Informe de auditoria de seguridad

$(HE $Empresa) · $(Get-Date -Format 'yyyy-MM-dd HH:mm')
Auditoria autorizada por: $(HE $autorizadoPor) · Red $(HE $Target) · Equipo auditado: $env:COMPUTERNAME
$nota/100
$veredicto
$crit criticos · $ries riesgos · $oks correctos · $($script:Hosts.Count) dispositivos
"@) # --- Que podria hacer un atacante (el bloque que da miedo) --- $atk = $script:Attacker | Select-Object -Unique if ($atk) { [void]$sb.Append('

⚠ Que podria hacer un atacante hoy

') } # --- Hallazgos por gravedad --- $grupos = @( @{ Sev="Critico"; H="⚠ Criticos - atencion inmediata"; cls="c" }, @{ Sev="Riesgo"; H="▲ Riesgos - corregir pronto"; cls="r" } ) foreach ($g in $grupos) { $fs = $script:Findings | Where-Object Sev -eq $g.Sev if ($fs) { [void]$sb.Append("
$($g.H)
") foreach ($f in $fs) { $d = if ($f.Detalle) { "
$(HE $f.Detalle)
" } else { "" } $imp = "" if ($f.Impacto) { $cve = if ($f.Ref) { "$(HE $f.Ref)" } else { "" } $imp = "
SI NO SE ARREGLA ▸ $(HE $f.Impacto)$cve
" } $s = if ($f.Solucion){ "
SOLUCION ▸ $(HE $f.Solucion)
" } else { "" } $cmd = "" if ($f.Fix) { $cmd = "
COMANDO PARA ARREGLARLO · PowerShell como Administrador
$(HE $f.Fix)
" } [void]$sb.Append("
$(HE $f.Area) - $(HE $f.Titulo)
$d$imp$s$cmd
") } } } # --- Inventario de dispositivos --- if ($script:Hosts.Count -gt 0) { [void]$sb.Append("
Inventario de dispositivos en red
") foreach ($hh in $script:Hosts) { [void]$sb.Append("") } [void]$sb.Append("
IPNombreTipoMarcaPuertos abiertosPista
$(HE $hh.Ip)$(HE $hh.Nombre)$(HE $hh.Tipo)$(HE $hh.Marca)$(HE $hh.Puertos)$(HE $hh.Banner)
") } # --- Lo correcto --- $oksF = $script:Findings | Where-Object Sev -eq "OK" if ($oksF) { [void]$sb.Append("
✓ Lo que esta correcto
") foreach ($f in $oksF) { [void]$sb.Append("
$(HE $f.Titulo)
") } [void]$sb.Append("
") } [void]$sb.Append(@"
Siguiente paso. Estos puntos se pueden dejar cerrados sin que tengas que llamar a varios proveedores. Reviso, te lo arreglo, y de paso te enseno como sacarle mas partido a tu sistema en el dia a dia. Con los primeros clientes la puesta en marcha la hago sin coste por adelantado: pagas cuando veas el resultado.
"@) [System.IO.File]::WriteAllText($OutFile, $sb.ToString(), (New-Object System.Text.UTF8Encoding($true))) Write-Host "`n Informe guardado en el Escritorio:" -ForegroundColor Cyan Write-Host " $OutFile`n" -ForegroundColor White $dur = [int]((Get-Date) - $inicio).TotalSeconds Write-Host " Auditoria completada en $dur s.`n" -ForegroundColor DarkGray try { Start-Process $OutFile } catch {}