Active Directory Fundamentals

Alerting on ‘password never expires’ violations

Alerting on “Password Never Expires” Violations (Active Directory)

This article explains what the “Password never expires” setting actually means in Active Directory, why it is risky, and how to build reliable detection and alerting with minimal noise.

Why this matters?

A password is a shared secret. Over time, shared secrets tend to leak: phishing, credential stuffing, malware, password reuse, accidental exposure, or a backup dump that gets copied somewhere it shouldn’t. When you enforce periodic rotation (or at least require resets after risk signals), you reduce the time window during which a leaked secret remains valid.

The “Password never expires” flag removes one of the few automatic controls that forces rotation. For many environments, it becomes a long-lived credential with no built-in expiry pressure, which increases:

  • Blast radius: compromised credentials remain usable for months/years.
  • Stealth: old service accounts often go unnoticed, making them attractive targets.
  • Compliance gaps: password aging policies are bypassed for those identities.
Key idea: alerting is not about “finding” the flag once. It is about preventing drift: detecting new accounts or newly changed accounts that become non-expiring, and ensuring every exception is explicit, justified, and reviewed.

What counts as a “violation”

In on-prem Active Directory, “Password never expires” is implemented by setting the DONT_EXPIRE_PASSWORD bit in userAccountControl. Operationally, treat it as a violation unless the account is:

  • a managed identity alternative exists (preferred),
  • a gMSA or modern credential mechanism is used (preferred for services), or
  • there is a documented, time-bounded exception with compensating controls.

Common exception buckets (handle deliberately)

Account type Typical reality Safer direction
Service account (legacy) Set to never expire to avoid outages Migrate to gMSA / managed secrets; restrict logon; monitor usage
Break-glass admin Sometimes set to never expire for availability Use long but rotating credentials, MFA where possible, locked-down usage and monitoring
Vendor / integration account Hard-coded password in an app Secret vault + rotation; scoped permissions; alert on interactive logon

Detection strategies

You can detect non-expiring accounts in two broad ways. Use the one that fits your tooling maturity, but keep the same policy logic and exception handling.

Option A: Periodic inventory scan (simple, robust)

Run a scheduled query (hourly/daily) that enumerates all enabled users with non-expiring passwords. Then alert on net new findings or changes since last run. This approach is reliable even if auditing is imperfect.

Option B: Event-driven alerting (fast, but depends on auditing)

Alert immediately when the attribute changes. This requires:

  • Directory Services auditing for attribute changes (or a change feed via your identity platform).
  • A pipeline (SIEM/SOAR) that can reliably parse “who changed what” and route alerts.
Recommendation: start with a periodic scan to establish baseline and reduce risk quickly. Add event-driven alerts later to shorten time-to-detect.

Alert design: reduce noise, increase actionability

The most common failure mode is generating a daily list that nobody acts on. A good alert must tell an operator exactly what to do next.

What to include in the alert

  • Account identifiers: sAMAccountName, UPN, display name, DN, and SID (if available).
  • Where it lives: OU path (often maps to ownership).
  • Risk hints: memberOf (privileged groups), lastLogonTimestamp, pwdLastSet.
  • Change context: if event-driven, include “changed by” + timestamp.
  • Playbook link: the remediation steps and the exception request process.

Severity model (practical)

Severity Condition Expected response
High Privileged user or service account with broad access is set to never expire Same-day triage; rollback if unauthorized; open security incident if suspicious
Medium Standard user newly set to never expire Investigate owner; revert unless approved exception
Low Known exception still present, due for review window Review ticket; confirm compensating controls; renew or retire

Implementation: PowerShell periodic scan (baseline + drift detection)

Below is a practical pattern:

  • Query AD for enabled users with non-expiring passwords.
  • Store a snapshot (JSON/CSV).
  • Alert only on additions (or on changes you define).

Prerequisites: RSAT ActiveDirectory module on the host running this job; rights to read directory attributes; a secure location to store the snapshot.

#requires -Modules ActiveDirectory
# Alert-PasswordNeverExpires.ps1
# Periodically scan AD for accounts with "Password never expires" and alert on net-new findings.

param(
  [string]$SnapshotPath = "C:\Security\Snapshots\pne_snapshot.json",
  [string]$ReportPath   = "C:\Security\Reports\pne_new_findings.csv",
  [string]$SmtpServer   = "smtp.yourdomain.local",
  [string]$MailFrom     = "ad-alerts@yourdomain.local",
  [string]$MailTo       = "secops@yourdomain.local",
  [string]$MailSubject  = "[AD] New 'Password never expires' accounts detected"
)

# 1) Pull current state
$now = Get-Date
$users = Get-ADUser -LDAPFilter "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(userAccountControl:1.2.840.113556.1.4.803:=65536))" `
  -Properties DisplayName,UserPrincipalName,Enabled,DistinguishedName,MemberOf,LastLogonTimestamp,pwdLastSet,whenCreated,whenChanged |
  Select-Object @{
      Name="SamAccountName"; Expression={$_.SamAccountName}
    }, @{
      Name="UPN"; Expression={$_.UserPrincipalName}
    }, DisplayName, Enabled, DistinguishedName, whenCreated, whenChanged, pwdLastSet,
    @{
      Name="LastLogonApprox"; Expression={
        if ($_.LastLogonTimestamp) { [DateTime]::FromFileTime($_.LastLogonTimestamp) } else { $null }
      }
    },
    @{
      Name="IsPrivileged"; Expression={
        # simple heuristic: membership contains common admin groups; tailor to your env
        ($_.MemberOf -match "Domain Admins|Enterprise Admins|Administrators|Account Operators") -as [bool]
      }
    }

# 2) Load previous snapshot
$prev = @()
if (Test-Path $SnapshotPath) {
  try { $prev = Get-Content $SnapshotPath -Raw | ConvertFrom-Json } catch { $prev = @() }
}

# 3) Diff: new accounts in current that were not in previous
$prevSet = @{}
foreach ($p in $prev) { $prevSet[$p.SamAccountName] = $true }

$new = $users | Where-Object { -not $prevSet.ContainsKey($_.SamAccountName) }

# 4) Save current snapshot (always)
$dir = Split-Path $SnapshotPath -Parent
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
$users | ConvertTo-Json -Depth 4 | Set-Content -Path $SnapshotPath -Encoding UTF8

# 5) If new findings, export and email
if ($new.Count -gt 0) {
  $rdir = Split-Path $ReportPath -Parent
  if (-not (Test-Path $rdir)) { New-Item -ItemType Directory -Path $rdir -Force | Out-Null }
  $new | Export-Csv -NoTypeInformation -Path $ReportPath -Encoding UTF8

  $body = @"
New 'Password never expires' accounts detected: $($new.Count)

Review actions:
1) Confirm legitimacy (change request / owner).
2) If unauthorized: revert flag immediately and investigate.
3) If exception required: document justification, compensating controls, and review date.

CSV report: $ReportPath
Run time: $now
"@

  Send-MailMessage -SmtpServer $SmtpServer -From $MailFrom -To $MailTo -Subject $MailSubject -Body $body
}
Why this works: it shifts the alert from “here’s the whole universe every day” to “something changed.” That is what humans can reliably operationalize.

Scheduling

  • Run hourly for fast drift detection, or daily if your environment changes slowly.
  • Use Windows Task Scheduler under a service identity with least privilege.
  • Protect the snapshot directory (integrity matters; treat as security telemetry).

Implementation: SIEM/SOAR patterns

If you already centralize identity data in a SIEM, use it. Typical approaches:

  • Directory inventory ingestion: ship a daily LDAP export into the SIEM; alert on deltas.
  • Change events: alert on attribute change events for userAccountControl (or the equivalent in your platform).
  • Policy correlation: escalate severity if the account is privileged, recently used, or has interactive logons.
Operational tip: separate alerts into two streams:
  • New violations (immediate action)
  • Exception reviews due (governance cadence)

Remediation playbook (what your alert should drive)

  1. Verify intent: was there an approved change request?
  2. Assess account type: human user, service, integration, break-glass.
  3. Check privilege: group memberships; delegated rights; local admin usage.
  4. Decide:
    • Revert “never expires” and enforce policy, or
    • Approve exception with compensating controls and an expiry/review date.
  5. Harden if exception: long random secret, vault storage, restricted logon, monitored usage, no interactive logon, and ideally migrate to gMSA/managed auth.
  6. Document: owner, reason, controls, review date, and a rollback plan.

Hardening and prevention

Alerting is the safety net. Prevention reduces how often you need it.

  • Prefer non-password identities for services: gMSA or platform-managed identities where available.
  • Restrict who can set the flag: minimize delegated rights to modify sensitive attributes.
  • Use tiering: separate admin/service identities from user OUs and apply tighter controls.
  • Compensating controls for exceptions: vault + rotation process, limited logon, strong monitoring.

Metrics to track

  • Count of non-expiring accounts (target: trending down).
  • New violations per week (target: near zero; indicates process gaps if high).
  • Mean time to remediate (target: hours/days, not weeks).
  • Exception review compliance (target: 100% within window).

Related posts
Active Directory Fundamentals

Migrating from AD FS to Azure AD SSO

Active Directory FundamentalsActive Directory PoliciesUncategorized

Role-based access control (RBAC) in Azure

Active Directory Fundamentals

Federation strategies using Entra

Active Directory Fundamentals

Tracking privilege escalation in Azure AD

×

There are over 8,500 people who are getting towards perfection in Active Directory, IT Management & Cyber security through our insights from Identitude.

Wanna be a part of our bimonthly curation of IAM knowledge?

  • -Select-
  • By clicking 'Become an insider', you agree to processing of personal data according to the Privacy Policy.