Saltar al contenido

PowerShell para auditoría de seguridad: scripts que todo admin debería tener a mano

    PowerShell para auditoría de seguridad:

    scripts que todo admin debería tener a mano

    Administración de sistemas Windows  |  unfantasmaenelsistema.com

    Un administrador de sistemas pasa la mayor parte del tiempo comprobando que todo está donde debería: los parches aplicados, los grupos privilegiados sin sorpresas, los servicios que arrancan solos justificados, y ninguna tarea programada creada a las tres de la mañana por nadie. Hacer esa comprobación clic a clic por la interfaz gráfica es lento y no deja rastro. Hacerla con PowerShell es cuestión de segundos, es repetible y se puede comparar con la foto de la semana pasada.

    Este artículo recopila los bloques de PowerShell que conviene tener guardados para auditar tu propio sistema o el parque que administras: inventario, cuentas, persistencia, red, políticas de auditoría y registro de eventos. No son herramientas de intrusión: son la linterna con la que revisas tu casa. Todos los ejemplos son de solo lectura salvo donde se indique lo contrario.

    Qué aprenderás en este artículo

    Cómo dejar PowerShell preparado para auditar sin bajar la seguridad del equipo · Inventario de software instalado y estado de parches del sistema · Revisión de usuarios locales, grupos privilegiados y cuentas dormidas · Detección de servicios y tareas programadas sospechosas · Conexiones de red activas correlacionadas con el proceso que las abre · Comprobar la política de auditoría y exprimir el registro de eventos de seguridad · Guardar líneas base y comparar el estado actual con una foto anterior

    1. Preparar el terreno sin bajar la guardia

    Antes de ejecutar nada conviene entender dos cosas: qué permite tu política de ejecución y cómo dejar constancia de lo que haces. La ExecutionPolicy de PowerShell no es un mecanismo de seguridad frente a un atacante —se salta de diez formas distintas—, pero sí evita ejecuciones accidentales de scripts sin firmar. Para trabajar con tus propios scripts de auditoría, lo razonable es `RemoteSigned` a nivel de máquina y firmar los que uses de forma recurrente.

    # Ver la politica efectiva y de donde viene cada nivel
    Get-ExecutionPolicy -List
     
    # Permitir scripts locales sin firma, exigir firma a los descargados de internet
    Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned
     
    # Version de PowerShell: muchas cmdlets de seguridad piden 5.1 o 7+
    $PSVersionTable.PSVersion

    Segundo: activa la transcripción. Con ella, cada comando que ejecutes y su salida quedan escritos en un fichero de texto con marca de tiempo. Es la diferencia entre «creo que revisé eso» y poder demostrar qué comprobaste y cuándo. En un entorno gestionado se despliega por GPO (*Administrative Templates → Windows Components → Windows PowerShell*), pero también se puede activar para la sesión actual.

    # Transcripcion de la sesion actual a un fichero con fecha
    $log = "C:\Auditoria\transcript_$(Get-Date -Format yyyyMMdd_HHmmss).txt"
    Start-Transcript -Path $log -NoClobber
     
    # ... aqui va toda la auditoria ...
     
    Stop-Transcript

    Registro que de verdad ayuda en una investigación

    Script Block Logging (Event ID 4104) registra el código que PowerShell ejecuta, incluso ofuscado · Module Logging (4103) registra las llamadas a cmdlets con sus parámetros · Ambos se activan por GPO en ‘Windows PowerShell’ y escriben en ‘Microsoft-Windows-PowerShell/Operational’ · Sin estos tres registros (transcripción, script block, module) una intrusión vía PowerShell es casi invisible a posteriori

    2. Inventario de software y estado de parches

    Lo primero de cualquier auditoría es saber qué hay instalado. Evita `Win32_Product` de WMI: es lentísimo y dispara una reconfiguración de MSI en cada equipo que consultas. La fuente fiable y rápida es el registro de desinstaladores.

    # Software instalado (64 y 32 bits) desde el registro, sin tocar WMI
    $rutas = @(
      'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
      'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )
    Get-ItemProperty $rutas -ErrorAction SilentlyContinue |
      Where-Object DisplayName |
      Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
      Sort-Object DisplayName |
      Export-Csv C:\Auditoria\software.csv -NoTypeInformation -Encoding UTF8

    Para el estado de parches interesan dos ángulos: qué actualizaciones están puestas y, sobre todo, cuánto hace de la última. Un equipo sin parchear desde hace tres meses es el hallazgo, no la lista de KB.

    # Ultimas actualizaciones instaladas y antiguedad de la mas reciente
    $hotfix = Get-HotFix | Sort-Object InstalledOn -Descending
    $hotfix | Select-Object -First 10 HotFixID, Description, InstalledOn
     
    $dias = (New-TimeSpan -Start $hotfix[0].InstalledOn -End (Get-Date)).Days
    if ($dias -gt 45) { Write-Warning "Ultimo parche hace $dias dias" }
     
    # Estado del servicio de actualizaciones y ultima busqueda correcta
    Get-Service wuauserv | Select-Object Status, StartType

    3. Usuarios, grupos y privilegios

    Las cuentas son el terreno donde más rápido se acumulan riesgos: administradores locales que nadie recuerda haber añadido, cuentas de ex-empleados sin deshabilitar, contraseñas que no caducan nunca. En Windows 10/11 y Server 2016+ el módulo `Microsoft.PowerShell.LocalAccounts` cubre lo local.

    # Cuentas locales habilitadas, con foco en las que nunca caducan
    Get-LocalUser | Where-Object Enabled |
      Select-Object Name, LastLogon, PasswordLastSet, PasswordExpires,
                    @{n='PwdNuncaCaduca';e={ -not $_.PasswordExpires }}
     
    # Miembros del grupo Administradores locales (el hallazgo clasico)
    Get-LocalGroupMember -Group 'Administradores' |
      Select-Object Name, PrincipalSource, ObjectClass

    En un dominio, el equivalente con el módulo `ActiveDirectory` es más revelador todavía. Estas tres consultas destapan la mayoría de los problemas de higiene de cuentas.

    # Cuentas activas sin iniciar sesion en 90 dias
    $limite = (Get-Date).AddDays(-90)
    Search-ADAccount -AccountInactive -DateTime $limite -UsersOnly |
      Where-Object Enabled | Select-Object Name, LastLogonDate
     
    # Cuentas con contrasena que no caduca
    Get-ADUser -Filter 'PasswordNeverExpires -eq $true -and Enabled -eq $true' -Properties PasswordNeverExpires
     
    # Pertenencia a los grupos mas sensibles
    'Domain Admins','Enterprise Admins','Schema Admins' | ForEach-Object {
      Get-ADGroupMember $_ | Select-Object @{n='Grupo';e={$_}}, name, objectClass }

    Interpreta los resultados con contexto

    Una cuenta de servicio con ‘PasswordNeverExpires’ puede ser correcta; una cuenta de persona casi nunca lo es · Un administrador local extra puede ser una herramienta de despliegue legítima: contrástalo antes de alarmar · El valor de una auditoría no es la lista cruda, sino comparar ‘lo que hay’ con ‘lo que debería haber’

    4. Servicios y tareas programadas sospechosas

    La persistencia —que el código vuelva a ejecutarse tras un reinicio— se apoya casi siempre en servicios, tareas programadas o claves de arranque. Auditarlas es buscar lo que se sale de lo normal: rutas raras, binarios sin firmar, ejecutables en carpetas de usuario o temporales.

    # Servicios cuyo binario NO esta en Windows\System32 (revisar uno a uno)
    Get-CimInstance Win32_Service |
      Where-Object { $_.PathName -and $_.PathName -notmatch 'System32' } |
      Select-Object Name, StartMode, State, StartName, PathName |
      Sort-Object Name
     
    # Binarios de servicio sin firma digital valida
    Get-CimInstance Win32_Service | ForEach-Object {
      $exe = ($_.PathName -replace '^"([^"]+)".*','$1') -replace '\s+/.*$',''
      if (Test-Path $exe) {
        $sig = Get-AuthenticodeSignature $exe
        if ($sig.Status -ne 'Valid') { [pscustomobject]@{ Servicio=$_.Name; Exe=$exe; Firma=$sig.Status } }
      }
    }
    # Tareas programadas activas que ejecutan powershell, cmd, scripts o rutas de usuario
    Get-ScheduledTask | Where-Object State -ne 'Disabled' | ForEach-Object {
      $acc = $_.Actions.Execute -join ' '
      if ($acc -match 'powershell|cmd|wscript|cscript|mshta|\\Users\\|\\Temp\\|\\AppData\\') {
        [pscustomobject]@{
          Tarea   = $_.TaskName
          Ruta    = $_.TaskPath
          Autor   = $_.Author
          Ejecuta = $acc
          Args    = $_.Actions.Arguments -join ' '
        }
      }
    } | Format-Table -AutoSize

    Los puntos de ejecución automática (claves `Run`, carpeta *Startup*, etc.) los cubre bien Autoruns de Sysinternals, que además tiene salida por línea de comandos (`autorunsc.exe -a * -c -h`) fácil de canalizar a un CSV. Para una revisión rápida sin dependencias, las claves más habituales:

    $run = @(
      'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
      'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
      'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
    )
    $run | ForEach-Object { Get-Item $_ -ErrorAction SilentlyContinue } |
      Select-Object -ExpandProperty Property -ErrorAction SilentlyContinue

    5. Red: qué está escuchando y quién lo abrió

    Un puerto a la escucha que no reconoces, o una conexión saliente establecida hacia una IP externa desde un proceso que no debería hablar con internet, son señales que merecen un vistazo. La clave es correlacionar la conexión con el proceso y su ruta en disco, no quedarse en el número de puerto.

    # Puertos TCP a la escucha, con el proceso y su ejecutable
    Get-NetTCPConnection -State Listen |
      Select-Object LocalAddress, LocalPort,
        @{n='Proceso';e={ (Get-Process -Id $_.OwningProcess).ProcessName }},
        @{n='Ruta';e={ (Get-Process -Id $_.OwningProcess).Path }} |
      Sort-Object LocalPort
     
    # Conexiones salientes establecidas hacia direcciones publicas
    Get-NetTCPConnection -State Established |
      Where-Object { $_.RemoteAddress -notmatch '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|::1|fe80)' } |
      Select-Object RemoteAddress, RemotePort,
        @{n='Proceso';e={ (Get-Process -Id $_.OwningProcess).ProcessName }}

    Complementa con el estado del firewall: perfiles activos, y sobre todo reglas de entrada que permiten tráfico y que alguien pudo añadir para abrir una puerta.

    Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction
     
    Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True |
      Where-Object { $_.DisplayName -notmatch 'Core Networking|mDNS' } |
      Select-Object DisplayName, Profile,
        @{n='Programa';e={ ($_ | Get-NetFirewallApplicationFilter).Program }}

    6. Política de auditoría y registro de eventos

    De nada sirve revisar los logs si el sistema no está registrando lo que importa. `auditpol` muestra la política de auditoría avanzada efectiva; las subcategorías mínimas que deberían estar en *Success and Failure* son inicio de sesión, gestión de cuentas, cambios de política y creación de procesos.

    # Volcado completo de la politica de auditoria efectiva
    auditpol /get /category:* 
     
    # Comprobacion rapida de subcategorias clave
    'Logon','Logoff','User Account Management','Audit Policy Change','Process Creation' |
      ForEach-Object { auditpol /get /subcategory:"$_" }

    Con la auditoría bien configurada, el registro de eventos de seguridad responde a la mayoría de preguntas de una revisión. Estos son los Event ID que más rinden:

    Event IDQué indicaUso en la auditoría
    4624 / 4625Inicio de sesión correcto / fallidoFuerza bruta, accesos fuera de horario, tipo de logon (10 = RDP)
    4720 / 4726Cuenta de usuario creada / eliminadaAltas y bajas de cuentas no autorizadas
    4728 / 4732 / 4756Miembro añadido a grupo (global / local / universal)Escalada de privilegios vía pertenencia a grupo
    4672Sesión iniciada con privilegios especialesUso de cuentas administrativas: ¿cuántas y cuándo?
    4688Proceso nuevo creadoRequiere auditoría de creación de procesos; con línea de comandos, oro puro
    7045 / 4697Servicio nuevo instaladoPersistencia por servicio
    1102El registro de auditoría fue borradoSeñal de alarma casi inequívoca
    # Inicios de sesion fallidos de las ultimas 24 h, agrupados por cuenta y origen
    $desde = (Get-Date).AddDays(-1)
    Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4625; StartTime=$desde } -ErrorAction SilentlyContinue |
      ForEach-Object {
        $x = [xml]$_.ToXml()
        [pscustomobject]@{
          Cuenta = $x.Event.EventData.Data.Where({$_.Name -eq 'TargetUserName'}).'#text'
          Origen = $x.Event.EventData.Data.Where({$_.Name -eq 'IpAddress'}).'#text'
        }
      } | Group-Object Cuenta, Origen | Sort-Object Count -Descending |
      Select-Object Count, Name
     
    # Comprobar si el log de seguridad se ha borrado alguna vez
    Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=1102 } -MaxEvents 5 -ErrorAction SilentlyContinue

    7. Líneas base: auditar es comparar

    Una auditoría aislada te dice cómo está el sistema hoy. El valor real aparece cuando comparas esa foto con una línea base tomada en un momento en que sabías que el equipo estaba limpio. PowerShell hace esto trivial: serializa el estado a disco y compáralo después con `Compare-Object`.

    # Tomar la linea base (equipo recien aprovisionado y verificado)
    $base = [ordered]@{
      Admins   = (Get-LocalGroupMember 'Administradores').Name
      Servicios= (Get-Service | Where-Object Status -eq 'Running').Name
      Tareas   = (Get-ScheduledTask | Where-Object State -ne 'Disabled').TaskName
      Puertos  = (Get-NetTCPConnection -State Listen).LocalPort | Sort-Object -Unique
    }
    $base | ConvertTo-Json -Depth 4 | Set-Content C:\Auditoria\baseline.json -Encoding UTF8
    # Semanas despues: comparar el estado actual con la linea base
    $base   = Get-Content C:\Auditoria\baseline.json -Raw | ConvertFrom-Json
    $ahora  = (Get-LocalGroupMember 'Administradores').Name
     
    Compare-Object $base.Admins $ahora |
      ForEach-Object {
        $lado = if ($_.SideIndicator -eq '=>') { 'NUEVO (no estaba en la base)' } else { 'FALTA (estaba en la base)' }
        "{0}: {1}" -f $lado, $_.InputObject
      }

    El mismo patrón sirve para servicios, tareas, puertos, software o reglas de firewall. Programa el script de comparación como tarea diaria que te envíe por correo solo las diferencias, y habrás convertido una auditoría manual en una alerta de desviación de configuración.

    Ámbito de uso y sentido común

    Estos scripts son para auditar sistemas propios o de una organización que te ha encargado su administración; ejecutarlos contra equipos ajenos sin autorización puede constituir acceso ilícito a sistemas informáticos (art. 197 bis del Código Penal español) · Si durante la auditoría encuentras indicios de compromiso real, no sigas «trasteando» en el equipo: documenta, aísla y activa el procedimiento de respuesta a incidentes; cada comando que ejecutas altera marcas de tiempo y memoria · Guarda los CSV y transcripciones en una ubicación con acceso restringido: contienen un mapa detallado de tu infraestructura

    Conclusión

    La auditoría de seguridad con PowerShell no consiste en memorizar cien comandos, sino en tener media docena de bloques fiables —inventario, cuentas, persistencia, red, política de auditoría y eventos— guardados, versionados y listos para lanzar. La primera vez que los ejecutas obtienes una foto; a partir de la segunda, obtienes tendencias y desviaciones, que es donde están los hallazgos de verdad.

    Empieza por lo barato y de alto impacto: activa transcripción y script block logging, toma una línea base de administradores locales, servicios y tareas, y programa una comparación diaria. Con eso solo ya detectarías la mayoría de las técnicas de persistencia más comunes antes de que un atacante tenga tiempo de asentarse.

    Referencias y recursos

    Microsoft Learn — ‘PowerShell logging’ y ‘Advanced security audit policy settings’ · Sysinternals — Autoruns, Sysmon y Process Explorer, complementos naturales de estos scripts · CIS Benchmarks para Windows — la lista de referencia de qué debería valer cada ajuste · PowerShell ♥ the Blue Team (blog de PowerShell Team) — endurecimiento y registro · Repositorio del proyecto ‘PSGumshoe’ y los módulos de auditoría de la comunidad DFIR

    unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)

    Resumen de privacidad

    Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.