viernes, 17 de abril de 2026

WINDOWS. Admincount. Analisis

 

Propósito:

El objetivo es garantizar que no existan cuentas de administrador no autorizadas en Active Directory.

Se realiza una comprobación en las cuentas que no son de administrador para identificar si tienen el atributo `admincount` activado. Si lo tienen, significa que esta cuenta, que no debería ser de administrador, ha recibido derechos de administrador en el pasado. Esto suele ocurrir cuando un administrador otorga derechos temporales a una cuenta normal fuera de proceso.

Se deben revisar estas cuentas, especialmente en lo que respecta a su actividad anterior, y eliminar el atributo `admincount`. Para identificar las cuentas detectadas por esta regla, recomendamos ejecutar el siguiente comando de PowerShell: `get-adobject -ldapfilter "(admincount=1)"`.

Pasos

 PASO 0: Listar quien cumple esta condición

COMMANDO:  get-adobject -ldapfilter "(admincount=1)"


 PASO 1 — Confirmar que el usuario YA NO es admin

COMMANDO:  Get-ADUser usuarioX -Properties MemberOf | Select -ExpandProperty MemberOf


 Verifica que NO esté en:
  • Domain Admins
  • Enterprise Admins
  • Schema Admins
  • Account Operators
  • Backup Operators
  • Administrators (builtin)
  • Print Operators
  • Server Operators
PASO 2 — Quitar el atributo adminCount

COMMANDO: Set-ADUser usuarioX -Clear adminCount


Otra opción interesante es que te llegue un email de vez en cuando con este información útil




 Script: 

<#

Script: Audit_Usuarios_Privilegiados_AdminCount_ProtectedUsers.ps1

Descripcion:

Este script audita cuentas de Active Directory con AdminCount=1,

indicando usuarios que actualmente o en el pasado han tenido

privilegios elevados. Adicionalmente valida si dichas cuentas

pertenecen al grupo de seguridad "Protected Users" y si tienen

activada la opcion "La cuenta es importante y no se puede delegar".

Uso previsto:

  • Auditoria de seguridad
  • Validacion de hardening de cuentas privilegiadas
  • Base para Protected Users y Tier Model

Requisitos:

  • Modulo ActiveDirectory
  • Permisos de lectura en AD

Impacto:

  • Solo lectura
  • Sin cambios en Active Directory
  • Seguro para entornos enterprise

#>

Import-Module ActiveDirectory

==========================

VARIABLES DEL USUARIO

==========================

$SMTPServer = "smtp01.midomino.com"

$SMTPPort = 25

$From = "srvdc01@midomino.com"

$To = "miemail@midomino.com"

$DateStamp = Get-Date -Format "yyyy-MM-dd - HHmm"

$ReportPath = "C:\Temp\AdminCount_Users_ProtectedUsers_$DateStamp.csv"

==========================

OBTENCION DE MIEMBROS DE PROTECTED USERS

==========================

$ProtectedUsersGroup = Get-ADGroup "Protected Users"

$ProtectedUsers = Get-ADGroupMember $ProtectedUsersGroup -Recursive |

Where-Object { $_.objectClass -eq "user" } |

Select-Object -ExpandProperty SamAccountName

==========================

OBTENCION DE USUARIOS ADMINCOUNT

==========================

$Users = Get-ADUser -Filter { AdminCount -eq 1 } -Properties `

WhenCreated,

Description,

Enabled,

AccountNotDelegated,

SamAccountName |

Select-Object `

Name,

SamAccountName,

WhenCreated,

Description,

Enabled,

@{Name="NoDelegable";Expression={

if ($_.AccountNotDelegated) {

"SI"

}

else {

"NO"

}

}},

@{Name="ProtectedUsers";Expression={

if ($ProtectedUsers -contains $_.SamAccountName) {

"SI"

}

else {

"NO"

}

}}

$UserCount = $Users.Count

$ProtectedCount = ($Users |

Where-Object { $_.ProtectedUsers -eq "SI" }).Count

$NoDelegableCount = ($Users |

Where-Object { $_.NoDelegable -eq "SI" }).Count

==========================

EXPORT CSV

==========================

$Users | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8

==========================

CUERPO HTML

==========================

$HtmlBody = @"

<html>

<head>

<style>

body {

font-family: Segoe UI, Arial;

font-size: 12px;

}

table {

border-collapse: collapse;

width: 100%;

}

th, td {

border: 1px solid #999;

padding: 6px;

text-align: left;

}

th {

background-color: #dedede;

}

</style>

</head>

<body>

<h2>Auditoria de cuentas privilegiadas (AdminCount=1)</h2>

<p>

Este informe identifica cuentas con AdminCount=1 y valida si estan

protegidas mediante el grupo "Protected Users" y si tienen activada

la opcion "La cuenta es importante y no se puede delegar".

</p>

<ul>

<li>Total cuentas privilegiadas: <strong>$UserCount</strong></li>

<li>Cuentas en Protected Users: <strong>$ProtectedCount</strong></li>

<li>Cuentas NO protegidas: <strong>$($UserCount - $ProtectedCount)</strong></li>

<li>Cuentas NO delegables: <strong>$NoDelegableCount</strong></li>

</ul>

<table>

<tr>

<th>Nombre</th>

<th>SamAccountName</th>

<th>Fecha de creacion</th>

<th>Descripcion</th>

<th>Habilitado</th>

<th>No delegable</th>

<th>Protected Users</th>

</tr>

$(

foreach ($User in $Users)

{

@"

<tr>

<td>$($User.Name)</td>

<td>$($User.SamAccountName)</td>

<td>$($User.WhenCreated)</td>

<td>$($User.Description)</td>

<td>$($User.Enabled)</td>

<td>$($User.NoDelegable)</td>

<td>$($User.ProtectedUsers)</td>

</tr>

"@

}

)

</table>

<p>

Recomendaciones:

</p>

<ul>

<li>Evaluar inclusion progresiva en Protected Users para administradores humanos.</li>

<li>Revisar las cuentas privilegiadas que no estan protegidas.</li>

<li>Revisar las cuentas privilegiadas susceptibles de delegacion Kerberos.</li>

<li>No incluir cuentas de servicio sin validacion previa.</li>

<li>Todas las cuentas privilegiadas deberian tener el campo "No delegable" a SI.</li>

</ul>

</body>

</html>

"@

==========================

ASUNTO Y ENVIO

==========================

$Subject = "$DateStamp - Auditoria AdminCount y Protected Users - $UserCount cuentas"

Send-MailMessage `

-From $From `

-To $To `

-Subject $Subject `

-Body $HtmlBody `

-BodyAsHtml `

-SmtpServer $SMTPServer `

-Port $SMTPPort `

-Attachments $ReportPath

 


by GoN | Published: April 2026 | Last Updated: Oct 2026

jueves, 16 de abril de 2026

WINDOWS. GPO. Grupo administradores locales

 Propósito


Hacer un GPO que se aplique en las estaciones de trabajo y que elimine/vacíe todos los usuarios y grupos  que hay en el grupo de administradores locales, y en este caso solo deje al usuario administrador local del PC. 
 
Yo tengo combinada esta GPO con el LAPS para reforzar su seguridad, junto con la GPO que los administradores de dominio no pueden hacer logon en los PCs.

Pasos


Las GPO se aplican cada 90 minutos, si alguien consiguiese añadirse al grupo de administradores locales del PC, a los 90 minutos se borraría.


by GoN | Published: April 2026 | Last Updated:

miércoles, 15 de abril de 2026

WINDOWS. GPO. Denegar logon y RDP a los administradores de dominio en las estaciones de trabajo

 

Siguiendo las BP, vamos a crear una GPO que bloquea el acceso de los administradores de dominio para que puedan hacer RDP o logon en los PCS, así evitamos movimientos laterales y que la contraseña de un administrador se pueda quedar en el cache de alguna estación de trabajo.


Como seguro, para evitar impactos no deseado, por si se mete en la OU un servidor o se asigna la GPO a una OU de Servidores, añado un filtro para que solo se aplique a estaciones de trabajo.






Con que combinarlo: Yo tengo LAPS en todos los PCs, es una red de unos 1000 PCs, el único usuario que tiene permisos de administrador en los PCs es el administrador local, si lo unimos a lo que comento en este artículo y los tenemos bien parcheados el nivel de protección en nuestros PCs se incrementa considerablemente.

by GoN | Published: April 2026 | Last Updated:
 

lunes, 13 de abril de 2026

WINDOWS. LAPS. Check PCs


Propósito

A veces, viendo que en nuestro AD nuestros PCs tienen asignada una contraseña de LAPS creemos erróneamente que todo está funcionando bien. Hay que verificar alguna cosas más. que aquí explicaré, para tener más control
 
Vamos a planificar un script que no solo compruebe si un PC tiene contraseña de LAPS en el AD, sino que esta contraseña esté rotando cada 30 días como marca la GPO.

Pasos

La ejecución será sencilla, el resultado lo enviará por email.

 

Nos llegará un email con este formato:

 

No solo pone en el cuerpo del email los PCs encontrados, sino que los envía en un fichero adjunto para poder manipularlo. Es recomendable ejecutarlo entre 15 días y un mes, para ver si salen algún caso, tal como muestro en los pantallazos. Es evidente que si el PC no se enciende o no contacta con el AD, no se le puede cambiar la contraseña.

 

El código: 

<#
.DESCRIPCION
Script de auditoria LAPS en Active Directory.
Detecta equipos con LAPS activo cuya contrasena local no se ha rotado
en mas de 30 dias. El resultado se muestra en el cuerpo del email en
formato HTML  y se adjunta en CSV.

.DISENO
Entorno enterprise, solo lectura, sin impacto.
#>

Write-Host "=== AUDITORIA LAPS ===" -ForegroundColor Cyan

# ==========================
#  VARIABLES DEL USUARIO
# ==========================
$SMTPServer = "smtp.midominio.com"
$SMTPPort   = 25
$From       = "HD@midominio.com"
$To         = "Infra@midominio.com"
$Subject    = ""

# ==========================
#  RUTAS Y FECHAS
# ==========================
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$timestamp  = Get-Date -Format "yyyyMMdd_HHmm"
$csvFile    = "$scriptPath\LAPS_Auditoria_$timestamp.csv"

# ==========================
#  OBTENER EQUIPOS AD
# ==========================
$computers = Get-ADComputer -Filter * -Properties `
    msLAPS-PasswordExpirationTime,
    LastLogonDate,
    DistinguishedName,
    Enabled

$results = @()

foreach ($pc in $computers) {

    if (-not $pc."msLAPS-PasswordExpirationTime") { continue }

    $exp = [DateTime]::FromFileTimeUtc($pc."msLAPS-PasswordExpirationTime")
    $dias = (New-TimeSpan -Start $exp -End (Get-Date)).Days
    if ($dias -le 30) { continue }

    $ou = ($pc.DistinguishedName -split ",",2)[1]
    $estado = if ($pc.Enabled) { "Habilitado" } else { "Deshabilitado" }

    $results += [PSCustomObject]@{
        Equipo             = $pc.Name
        OU                 = $ou
        Equipo_Habilitado  = $estado
        LAPS_Activo        = "Si"
        Ultima_Rotacion    = $exp
        Dias_Sin_Cambiar   = $dias
        Ultima_Conexion    = $pc.LastLogonDate
    }
}

$resultsSorted = $results | Sort-Object Dias_Sin_Cambiar -Descending
$TotalEquipos = $resultsSorted.Count

# ==========================
#  EXPORTAR CSV
# ==========================
$resultsSorted | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8

# ==========================
#  GENERAR TABLA HTML
# ==========================
$HtmlRows = ""

foreach ($r in $resultsSorted) {

    $FechaRotacionHtml = if ($r.Ultima_Rotacion) {
        $r.Ultima_Rotacion.ToString("dd/MM/yyyy")
    } else {
        "N/D"
    }

    $FechaConexionHtml = if ($r.Ultima_Conexion) {
        $r.Ultima_Conexion.ToString("dd/MM/yyyy")
    } else {
        "Nunca"
    }

    $HtmlRows += "<tr>
        <td>$($r.Equipo)</td>
        <td>$($r.Equipo_Habilitado)</td>
        <td>$($r.Dias_Sin_Cambiar)</td>
        <td>$FechaRotacionHtml</td>
        <td>$FechaConexionHtml</td>
        <td>$($r.OU)</td>
    </tr>"
}

# ==========================
#  EMAIL HTML
# ==========================
$Subject = "$(Get-Date -Format 'yyyy-MM-dd - HHmm') - Listado equipos LAPS sin rotacion ($TotalEquipos)"

$Body = @"
<html>
<head>
<style>
body { font-family: Arial; font-size: 12px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #999; padding: 5px; }
th { background-color: #eeeeee; }
</style>
</head>
<body>

<h2>Auditoria LAPS Active Directory</h2>

<p>
Equipos con LAPS activo y mas de 30 dias sin cambio de contrasena local.
</p>

<ul>
<li>Total equipos listados: <b>$TotalEquipos</b></li>
<li>Fecha ejecucion: <b>$(Get-Date -Format "dd/MM/yyyy")</b></li>
</ul>

<table>
<tr>
<th>Equipo</th>
<th>Estado</th>
<th>Dias sin cambiar</th>
<th>Ultima rotacion</th>
<th>Ultima conexion</th>
<th>OU</th>
</tr>
$HtmlRows
</table>

<p>
Se adjunta el fichero CSV con el detalle completo.
</p>

</body>
</html>
"@

Send-MailMessage `
    -From $From `
    -To $To `
    -Subject $Subject `
    -Body $Body `
    -BodyAsHtml `
    -SmtpServer $SMTPServer `
    -Port $SMTPPort `
    -Attachments $csvFile

Write-Host "Email enviado correctamente" -ForegroundColor Green
Write-Host "=== FIN DE AUDITORIA ===" -ForegroundColor Cyan 
 
 
by GoN | Published: April 2026 | Last Updated: