Principales conclusiones
- Protocolo de seguridad automatizado: el script automatiza la identificación de cuentas de usuario inactivas, lo que mejora la seguridad.
- Umbral personalizable: ajusta el umbral de inactividad a las políticas de la organización.
- Compatibilidad con múltiples entornos: capaz de gestionar cuentas locales, de Active Directory y de Azure AD.
- Eficaz en tiempo y recursos: automatiza lo que sería un largo proceso manual.
- Gestión proactiva del riesgo: ayuda a prevenir las brechas de seguridad gestionando las vulnerabilidades potenciales.
- Transparencia y flexibilidad: al ser un script PowerShell, es transparente y personalizable para necesidades específicas.
- Auditorías programadas: se puede configurar para que funcione a intervalos regulares para una supervisión continua.
- Informes exhaustivos: genera informes detallados sobre las cuentas inactivas, lo que facilita la toma de decisiones.
- Integración con herramientas de gestión: complementa y se integra con soluciones de gestión de TI como NinjaOne.
En el dinámico panorama de la gestión de TI, la seguridad y la eficacia de la gestión de cuentas de usuario siguen siendo primordiales. Supervisar y tratar periódicamente las cuentas de usuario inactivas no es solo una buena práctica, sino una necesidad para mantener un entorno de TI seguro. Aquí es donde los scripts de PowerShell, como el que estamos analizando hoy, se convierten en herramientas imprescindibles para los profesionales de TI y los Proveedores de Servicios Gestionados (MSP).
Contexto
El script en cuestión está diseñado para identificar y alertar a los administradores sobre cuentas inactivas o no utilizadas en un entorno Windows. Las cuentas inactivas pueden suponer un riesgo importante para la seguridad, ya que, en caso de verse comprometidas, podrían convertirse en puntos de entrada para accesos no autorizados. Para los profesionales de TI y los MSP, estos scripts son cruciales para abordar preventivamente las vulnerabilidades de seguridad y garantizar el cumplimiento de diversas políticas y normativas de TI.
El script:
<#
.SYNOPSIS
Alerts when there is an inactive / unused account that has not logged in or has not had their password set in the specified number of days.
.DESCRIPTION
Alerts when there is an inactive / unused account that has not logged in or has not had their password set in the specified number of days.
.EXAMPLE
-IncludeDisabled
Action completed: Run Monitor Account Last Logon Result: FAILURE Output: Action: Run Monitor Account Last Logon, Result: Failed
WARNING: Inactive accounts detected!
Username PasswordLastSet LastLogon Enabled
-------- --------------- --------- -------
Administrator 4/12/2023 9:05:18 AM 11/28/2023 10:31:06 AM True
Guest 12/31/1600 4:00:00 PM False
DefaultAccount 12/31/1600 4:00:00 PM False
kbohlander 11/29/2023 1:49:51 PM 12/5/2023 1:09:43 PM True
tuser1 12/1/2023 5:59:58 PM 12/4/2023 9:34:32 AM True
krbtgt 11/27/2023 3:40:20 PM 12/31/1600 4:00:00 PM False
tuser2 12/4/2023 3:40:27 PM 12/31/1600 4:00:00 PM True
.EXAMPLE
-Days 60
Action completed: Run Monitor Account Last Logon Result: FAILURE Output: Action: Run Monitor Account Last Logon, Result: Failed
WARNING: Inactive accounts detected!
Username PasswordLastSet LastLogon Enabled
-------- --------------- --------- -------
Administrator 4/12/2023 9:05:18 AM 11/28/2023 10:31:06 AM True
kbohlander 11/29/2023 1:49:51 PM 12/5/2023 1:09:43 PM True
tuser1 12/1/2023 5:59:58 PM 12/4/2023 9:34:32 AM True
tuser2 12/4/2023 3:40:27 PM 12/31/1600 4:00:00 PM True
.OUTPUTS
None
.NOTES
Minimum OS Architecture Supported: Windows 7, Windows Server 2012
Exit code 1: Found users that haven't logged in over X days and are enabled.
Exit code 2: Calling "net.exe user" or "Get-LocalUser" failed.
Release Notes: Renamed script, added Script Variable support, improved ad support, removed requires statement.
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider.
Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>
[CmdletBinding()]
param (
[Parameter()]
[int]$Days = 90,
[Parameter()]
[switch]$IncludeDisabled = [System.Convert]::ToBoolean($env:includeDisabled)
)
begin {
# Use script variables if available.
if ($env:days -and $env:days -notlike "null") {
$Days = $env:Days
}
# Change negative days to the expected positive days
if ($Days -lt 0) {
$Days = 0 - $Days
}
# Date where an account is considered inactive.
$InactivityCutOff = (Get-Date).AddDays(-$Days)
function Test-IsDomainJoined {
if ($PSVersionTable.PSVersion.Major -lt 5) {
return $(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
}
else {
return $(Get-CimInstance -Class Win32_ComputerSystem).PartOfDomain
}
}
function Test-IsDomainController {
$OS = if ($PSVersionTable.PSVersion.Major -lt 5) {
Get-WmiObject -Class Win32_OperatingSystem
}
else {
Get-CimInstance -ClassName Win32_OperatingSystem
}
if ($OS.ProductType -eq "2") {
return $true
}
}
# We'll want to warn that we're unable to check the actual Azure AD account
# (it requires O365 credentials and a powershell module both of which are not supported by this script).
function Test-IsAzureJoined {
try {
$dsreg = dsregcmd.exe /status | Select-String "AzureAdJoined : YES"
}
catch {
return $False
}
if ($dsreg) { return $True }
}
# For Non-Domain Controllers we'll check net user for the information we need.
function Get-NetAccountInfo {
[CmdletBinding()]
param(
[Parameter()]
[string]$User,
[Parameter()]
[switch]$Domain
)
process {
# Get user info from net.exe user
$netuser = if ($Domain) { net.exe user $user /Domain }else { net.exe user $user }
$success = $netuser | Select-String 'The command completed successfully.'
if (-not $success) {
throw "Failed to retrieve account info for user $user!"
}
#Pre-formatted Object
$Object = New-Object psobject -Property @{
Username = $User
Name = "$(($netuser | Select-String 'Full Name') -split ' ' | Select-Object -Last 1)".Trim()
Enabled = "$(($netuser | Select-String 'Account active') -split ' ' | Select-Object -Last 1)".Trim()
LastLogon = "$(($netuser | Select-String 'Last logon') -split ' ' | Select-Object -Last 1)".Trim()
PasswordLastSet = "$(($netuser | Select-String 'Password last set') -split ' ' | Select-Object -Last 1)".Trim()
}
# Formatted object using PowerShell datatypes (for easier parsing later).
New-Object psobject -Property @{
Username = $Object.Username
Name = $Object.Name
Enabled = if ($Object.Enabled -eq "Yes") { $True }elseif ($Object.Enabled -eq "No") { $False }else { $Object.Enabled }
LastLogon = try { Get-Date $Object.LastLogon }catch { $Object.LastLogon };
PasswordLastSet = try { Get-Date $Object.PasswordLastSet }catch { $Object.PasswordLastSet }
}
}
}
}
process {
# If it's an azure joined machine we should warn that we're not checking any of the Azure AD Accounts.
if (Test-IsAzureJoined) {
Write-Warning -Message "This script is unable to check Azure AD accounts however this script will check the local accounts on this machine."
}
$Report = New-Object System.Collections.Generic.List[object]
# Warn if ad accounts are not able to be checked.
if (-not (Test-IsDomainController) -and (Test-IsDomainJoined) -and -not (Test-ComputerSecureChannel)) {
Write-Warning "Domain is not reachable! We'll be unable to check active directory accounts!"
}
# There are two ways this script will check for an inactive account one of which uses the Active Directory PowerShell module which is only availalbe on Domain Controllers / machines with RSAT installed.
if (-not (Test-IsDomainController)) {
# Compile a list of users to check.
$Users = if ($PSVersionTable.PSVersion.Major -lt 5) {
Get-WmiObject -Class "win32_UserAccount"
}
else {
Get-CimInstance -Class "win32_UserAccount"
}
# Grab the account info using net user (which is slightly different if it's a domain account or not).
$Accounts = foreach ($User in $Users) {
if ($User.Domain -ne $env:COMPUTERNAME ) {
Get-NetAccountInfo -User $User.Name -Domain
}
else {
Get-NetAccountInfo -User $User.Name
}
}
}
else {
# Older OS's need to have the module manually imported.
try {
Import-Module ActiveDirectory
}
catch {
Write-Error -Message "[Error] Failed to import PowerShell Active Directory Module. Is RSAT installed?" -Category DeviceError -Exception (New-Object System.Exception)
}
# Compile a list of users with the relevant attributes
$Users = Get-AdUser -Filter * -Properties SamAccountName, DisplayName, PasswordLastSet, lastLogonTimestamp, lastLogon, Enabled
# Convert that into a more parseable object
$Accounts = foreach ($User in $Users) {
$LastLogon = [datetime]::FromFileTime($User.lastLogon)
$LastLogonTimeStamp = [datetime]::FromFileTime($User.LastLogonTimestamp)
New-Object psobject -Property @{
Username = $User.SamAccountName
Name = $User.DisplayName
Enabled = $User.Enabled
LastLogon = if ($LastLogon -gt $LastLogonTimeStamp) { $LastLogon }else { $LastLogonTimeStamp }
PasswordLastSet = $User.PasswordLastSet
}
}
}
# Compile a list of accounts that could be considered inactive
$InactiveAccounts = $Accounts | Where-Object { ($_.LastLogon -eq "Never" -or $_.LastLogon -lt $InactivityCutOff) -and $_.PasswordLastSet -lt $InactivityCutOff }
# Filter out disabled accounts if we're asked to.
if ($IncludeDisabled) {
$InactiveAccounts | ForEach-Object { $Report.Add($_) }
}
else {
$InactiveAccounts | Where-Object { $_.Enabled -notlike $False } | ForEach-Object { $Report.Add($_) }
}
# If no inactive accounts exit
if (-not $Report) {
Write-Host "No inactive accounts detected!"
exit 0
}
Write-Warning "Inactive accounts detected!"
$Report | Format-Table -AutoSize -Property Username, PasswordLastSet, LastLogon, Enabled | Out-String | Write-Host
exit 1
}
end {
}
Accede a más de 300 scripts en el Dojo de NinjaOne
Análisis detallado
El script funciona en un proceso de varios pasos:
- Inicialización de parámetros: comienza definiendo parámetros, incluido el número de días para considerar inactiva una cuenta y una opción para incluir cuentas desactivadas.
- Controles del entorno: el script comprueba si la máquina está unida a un dominio, es un controlador de dominio o está unida a Azure AD. Determina el alcance de las comprobaciones de cuentas: local, Active Directory o Azure.
- Recuperación de la cuenta: en el caso de los controladores que no son de dominio, recupera la información de las cuentas mediante net.exe user o instancias WMI/CIM, mientras que en el caso de los controladores de dominio, utiliza el módulo PowerShell de Active Directory.
- Evaluación de la actividad: la función principal consiste en evaluar las cuentas de usuario en función de sus últimas fechas de inicio de sesión y de establecimiento de la contraseña con respecto al umbral de inactividad.
- Generación de informes: las cuentas inactivas se recopilan en un informe, con la opción de excluir las cuentas desactivadas.
- Alertas y códigos de salida: el script concluye alertando a los administradores de cualquier cuenta inactiva y saliendo con un código que indica la presencia o ausencia de dichas cuentas.
Posibles casos de uso
Considera un escenario en el que un administrador de TI en una gran empresa utiliza este script para auditar rutinariamente las cuentas de usuario. Podría programar el script para que se ejecute mensualmente, alertándole de las cuentas que no han estado activas durante más de 90 días. Esto permite una gestión proactiva de las cuentas de usuario, reduciendo el riesgo de brechas de seguridad a través de cuentas inactivas.
Comparaciones
Existen enfoques alternativos, como las auditorías manuales o el uso de herramientas de terceros. Sin embargo, este script de PowerShell ofrece una solución más directa, personalizable y rentable. A diferencia de los controles manuales, automatiza el proceso, ahorrando tiempo y reduciendo los errores humanos. En comparación con herramientas de terceros, ofrece transparencia y flexibilidad, ya que los profesionales de TI pueden modificar el script para adaptarlo a sus necesidades específicas.
FAQ
P1: ¿Puede el script diferenciar entre tipos de cuentas de usuario?
R1: Sí, puede distinguir entre cuentas locales, de Active Directory y de Azure AD.
P2: ¿Es posible programar este script para que se ejecute automáticamente?
R2: Por supuesto, esto se puede hacer utilizando el Programador de Tareas de Windows o herramientas de automatización similares.
P3: ¿Este script gestiona cuentas en un entorno de nube?
R3: Está diseñado principalmente para cuentas locales y de Active Directory; las cuentas de Azure AD requieren módulos y credenciales adicionales.
Implicaciones
Las implicaciones de utilizar este script son de gran alcance. Al identificar las cuentas inactivas, se reduce significativamente la superficie de ataque de posibles brechas de seguridad. Sin embargo, los profesionales de TI deben gestionar la salida de forma responsable, asegurándose de que la desactivación o eliminación de cuentas se ajusta a las políticas de la organización y a las necesidades de los usuarios.
Recomendaciones
- Auditorías periódicas: programa el script para que se ejecute a intervalos regulares.
- Personalización: adapta los parámetros del script a las políticas de la organización.
- Acciones de seguimiento: establece un protocolo para gestionar las cuentas inactivas.
Reflexiones finales
En conclusión, este tipo de scripts, cuando se utilizan eficazmente, contribuyen de forma significativa a la seguridad y eficacia generales de las operaciones de TI. Complementan soluciones como NinjaOne, que proporciona herramientas de gestión de TI completas, ayudando a los administradores a mantener el control sobre sus entornos informáticos. La capacidad de NinjaOne para integrarse con scripts PowerShell aumenta su utilidad, por lo que es una opción ideal para la gestión proactiva de TI.