A single domain password policy is a blunt instrument. Domain admins need longer passwords and tighter lockout than regular users. Service accounts need no lockout at all — a misconfigured service triggering lockout at 2am causes an outage. FGPP solves this via Password Settings Objects (PSOs), applied to users or global security groups, not OUs.
When multiple PSOs apply to a user, the one with the lowest precedence number wins. A PSO applied directly to a user always beats any group-based PSO, regardless of precedence number.
# Strict PSO for Domain Admins New-ADFineGrainedPasswordPolicy ` -Name "PSO-DomainAdmins" ` -Precedence 10 ` -MinPasswordLength 20 ` -PasswordHistoryCount 24 ` -MaxPasswordAge "365.00:00:00" ` -ComplexityEnabled $true ` -LockoutThreshold 3 ` -LockoutDuration "00:30:00" ` -LockoutObservationWindow "00:30:00" Add-ADFineGrainedPasswordPolicySubject ` -Identity "PSO-DomainAdmins" -Subjects "Domain Admins" # PSO for service accounts — long password, ZERO lockout New-ADFineGrainedPasswordPolicy ` -Name "PSO-ServiceAccounts" ` -Precedence 20 ` -MinPasswordLength 30 ` -MaxPasswordAge "0" ` -ComplexityEnabled $true ` -LockoutThreshold 0 # Check which PSO applies to a specific user Get-ADUserResultantPasswordPolicy -Identity "svc-sql"
The iron rule: credentials from a higher tier must never authenticate to a lower-tier system. A Domain Admin logging into a compromised workstation exposes their TGT — an attacker with that ticket owns every Tier 0 asset.
# Create a silo for T0 accounts New-ADAuthenticationPolicySilo ` -Name "Tier0Silo" ` -Description "T0 admins and DCs only" # Create a policy: T0 accounts can only authenticate FROM DCs/PAWs New-ADAuthenticationPolicy ` -Name "Tier0-RestrictedLogon" ` -UserAllowedToAuthenticateFrom ` 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Tier0Silo"))' ` -Enforce $true # Assign a T0 admin account to the silo Grant-ADAuthenticationPolicySiloAccess -Identity "Tier0Silo" -Account "adm-t0-jsmith" Set-ADUser -Identity "adm-t0-jsmith" -AuthenticationPolicySilo "Tier0Silo"
A PAW is a dedicated, hardened workstation used only for administrative tasks. No email, no browsing, no Office. It reduces the attack surface that high-privilege credentials are exposed to.
Adding an account to Protected Users automatically enforces a hardened set of Kerberos restrictions that cannot be overridden by any local configuration:
| Restriction | Attack it defeats |
|---|---|
| No NTLM authentication | Pass-the-Hash — NTLM hash is useless without NTLM auth |
| No Kerberos DES or RC4 | Forces AES-only; defeats RC4-based ticket cracking |
| No Kerberos unconstrained delegation | Prevents TGT forwarding exploitation |
| No credential caching | Creds not cached locally; requires DC for every auth |
| TGT lifetime reduced to 4 hours | Stolen tickets expire far faster than the default 10 hours |
# Add all Domain Admins to Protected Users Get-ADGroupMember "Domain Admins" | ForEach-Object { Add-ADGroupMember -Identity "Protected Users" -Members $_.SamAccountName }
Credential Guard uses Virtualization-Based Security (VBS) to isolate LSASS into a hypervisor-protected enclave. Even an attacker with SYSTEM-level access cannot dump credentials — the secrets live in a VM the OS literally cannot read.
# Enable via GPO (recommended): # Computer Config > Admin Templates > System > Device Guard # "Turn On Virtualization Based Security" = Enabled # "Credential Guard Configuration" = Enabled with UEFI lock # Verify Credential Guard status Get-CimInstance -ClassName Win32_DeviceGuard ` -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object SecurityServicesRunning # Value 1 in array = Credential Guard running
## STEP 1: Root CA (standalone, NOT domain-joined) ## Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools Install-AdcsCertificationAuthority ` -CAType StandaloneRootCA ` -CACommonName "Corp Root CA" ` -KeyLength 4096 -HashAlgorithmName "SHA256" ` -ValidityPeriod Years -ValidityPeriodUnits 20 -Force ## STEP 2: Issuing CA (domain-joined) ## Install-WindowsFeature ADCS-Cert-Authority, ADCS-Web-Enrollment -IncludeManagementTools # Generate CSR from Issuing CA, submit to Root CA out-of-band, # install the signed cert, then configure the CA: Install-AdcsCertificationAuthority ` -CAType EnterpriseSubordinateCA ` -CACommonName "Corp Issuing CA 01" ` -KeyLength 2048 -HashAlgorithmName "SHA256" -Force
SpecterOps research identified 8+ critical AD CS misconfigurations. ESC1 is the most common and dangerous:
| Misconfiguration | Impact | Fix |
|---|---|---|
| ESC1 — SAN in request + auth EKU | Any user can get a cert as any identity including Domain Admin | Disable "Supply in the request" for SAN |
| ESC2 — Any Purpose EKU | Cert usable for any purpose including auth | Restrict EKUs to specific purposes |
| ESC4 — Writable template ACL | Attacker modifies template to introduce ESC1 | Audit and restrict template ACLs |
| ESC6 — CA-level SAN flag | SAN override allowed on all templates | Disable EDITF_ATTRIBUTESUBJECTALTNAME2 on CA |
| ESC8 — NTLM relay to CA web enrollment | Relay yields a DC certificate | Require HTTPS + Extended Protection on CA web enrollment |
# Audit for vulnerable templates (run as any domain user) Certify.exe find /vulnerable # Or with Certipy from Linux: certipy find -u user@corp.example.com -p password -dc-ip 10.0.0.1
# GPO path (set for both Computer and User config): # Windows Settings > Security Settings > Public Key Policies # > Certificate Services Client - Auto-Enrollment # Set to: Enabled # Check: Renew expired certificates, update pending, remove revoked # Check: Update certificates that use certificate templates # Trigger auto-enrollment immediately on a machine certutil -pulse # View certificate enrollment events Get-WinEvent ` -LogName "Microsoft-Windows-CertificateServicesClient-Lifecycle-User/Operational" | Select-Object TimeCreated, Message -First 15
| Method | How it works | Latency | Best for |
|---|---|---|---|
| CRL | CA publishes a list of revoked serial numbers; clients download and cache it | Hours to days (cache lifetime) | Bulk revocation, low-churn environments |
| OCSP | Client queries OCSP responder in real-time for a single cert's status | Real-time (seconds) | Time-sensitive revocation |
| OCSP Stapling | Server pre-fetches OCSP response and includes in TLS handshake | Real-time, no client round-trip | HTTPS web servers — best option |
# Revoke a certificate and publish the updated CRL immediately Revoke-CACertificate -SerialNumber "1a2b3c4d" -Reason KeyCompromise Publish-CACrl # Test CRL accessibility from a client certutil -verify -urlfetch "C:\path o\cert.cer"
Without ADFS, giving staff access to a SaaS app means creating and managing separate accounts — separate passwords, separate MFA, separate offboarding. With ADFS, the SaaS app trusts your AD to authenticate users. Staff log in with their AD credentials and access is granted automatically. Offboarding is instant — disable the AD account and all federated access is revoked.
ADFS uses claims-based identity: instead of the app querying your AD directly, ADFS issues a signed security token containing claims about the user — name, email, group memberships. The relying party trusts these claims because the token is signed by ADFS.
| Term | Meaning |
|---|---|
| Claims Provider | The identity source issuing claims — typically your AD |
| Relying Party (RP) | The application trusting ADFS to authenticate its users |
| Claim | An assertion about the user: name, email, UPN, group memberships, custom attributes |
| Claim Rules | Transform rules that map AD attributes to token claims; control what each RP receives |
| SAML 2.0 | Dominant web SSO federation protocol — XML-based tokens |
| WS-Federation | Microsoft-centric protocol used by Microsoft 365 and older Microsoft apps |
| WAP | Web Application Proxy — DMZ reverse proxy for ADFS external access |
| Certificate | Purpose | Requirements |
|---|---|---|
| SSL / TLS | HTTPS for the ADFS endpoint | Must be trusted by all clients. CN/SAN must match the federation service name (e.g. sts.corp.example.com). |
| Token Signing | Signs all tokens issued by ADFS | Self-signed by default, auto-renewed by ADFS. Every RP must have this public cert or use the metadata URL for auto-update. |
| Token Decryption | Decrypts incoming encrypted tokens | Self-signed; less commonly required in basic deployments. |
# Install ADFS role Install-WindowsFeature ADFS-Federation -IncludeManagementTools # Configure the first server in the farm Install-AdfsFarm ` -CertificateThumbprint "A1B2C3..." ` -FederationServiceName "sts.corp.example.com" ` -FederationServiceDisplayName "Corp Identity" ` -GroupServiceAccountIdentifier "CORP\svc-adfs$" ` -OverwriteConfiguration # Add a second server for redundancy Add-AdfsFarmNode ` -CertificateThumbprint "A1B2C3..." ` -GroupServiceAccountIdentifier "CORP\svc-adfs$" ` -PrimaryComputerName "ADFS-01.corp.example.com"
# Add RP from the app's published SAML metadata Add-AdfsRelyingPartyTrust ` -Name "Salesforce" ` -MetadataURL "https://salesforce.com/saml/metadata" ` -AutoUpdateEnabled $true -Enabled $true # Verify the trust Get-AdfsRelyingPartyTrust -Name "Salesforce" | Select-Object Name, Enabled, Identifier
| Port | Protocol | Notes |
|---|---|---|
| 389 | LDAP (plaintext or STARTTLS) | Default. Must use STARTTLS before sending credentials. Unsigned binds now rejected by default on Server 2022+. |
| 636 | LDAPS (TLS from connect) | Encrypted from the start. Requires a DC certificate. Preferred for all application integrations. |
| 3268 | Global Catalog LDAP | Cross-domain queries across the whole forest. Returns a subset of attributes. |
| 3269 | Global Catalog LDAPS | Encrypted GC queries. |
# Distinguished Names — read right to left CN=Alice Smith,OU=Finance,OU=Users,DC=corp,DC=example,DC=com CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com # LDAP filter syntax: (attr=value), logical operators: &(AND) |(OR) !(NOT) # All enabled user accounts (&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) # Users in a specific group (&(objectClass=user)(memberOf=CN=Finance,OU=Groups,DC=corp,DC=example,DC=com)) # Kerberoastable accounts (user objects with SPNs set) (&(objectClass=user)(servicePrincipalName=*)) # AS-REP Roastable accounts (no Kerberos pre-auth) (userAccountControl:1.2.840.113556.1.4.803:=4194304) # Use LDAP filters in PowerShell Get-ADUser -LDAPFilter "(&(objectClass=user)(department=Finance))" ` -Properties Department, LastLogonDate
Test-NetConnection DC-01 -Port 636## PRIVILEGED ACCOUNT HYGIENE ## Get-ADGroupMember "Domain Admins" -Recursive | Select-Object Name, SamAccountName Get-ADGroupMember "Enterprise Admins" -Recursive | Select-Object Name # Privileged accounts inactive for 90+ days Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | Where-Object { (Get-ADUser $_ -Properties MemberOf).MemberOf -match "Admin" } # Accounts with password never expires Get-ADUser -Filter {PasswordNeverExpires -eq $true} -Properties PasswordNeverExpires | Select-Object Name, SamAccountName, DistinguishedName ## KERBEROS ATTACK SURFACE ## # Kerberoastable: user objects with SPNs — any user can request a crackable TGS Get-ADUser -Filter {ServicePrincipalName -ne "$null"} ` -Properties ServicePrincipalName, PasswordLastSet | Select-Object Name, SamAccountName, ServicePrincipalName, PasswordLastSet # AS-REP Roastable: accounts with pre-authentication disabled Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} | Select-Object Name, SamAccountName ## DANGEROUS DELEGATION ## # Unconstrained delegation — TGTs stored in memory, full impersonation if compromised Get-ADComputer -Filter {TrustedForDelegation -eq $true} | Select-Object Name, DNSHostName
| Event ID | Log | What it signals |
|---|---|---|
| 4625 | Security | Failed logon. Alert on repeated failures for privileged accounts. |
| 4740 | Security (PDC Emulator) | Account locked out. All lockout events centralise here. |
| 4728 / 4732 | Security | User added to a privileged security group. |
| 4769 | Security (DCs) | Kerberos service ticket requested. Spike with RC4 encryption (0x17) = Kerberoasting in progress. |
| 4662 | Security | Object access on AD — triggers on DCSync if auditing is configured on domain NC. |
| 4776 | Security | NTLM authentication attempt. Volume spike may indicate Pass-the-Hash or relay attack. |