Best Practices for Active Directory Security: Because Hackers Don’t Take Coffee Breaks

October is Cybersecurity Awareness Month — that time of year when we remind everyone that “Password123” isn’t clever, and clicking suspicious links doesn’t count as continuing education.

As part of this month’s awareness focus, I’m turning the spotlight on one of the most critical and misunderstood pillars of enterprise IT: Active Directory (AD) — the brain of your IT environment. It decides who gets access, who doesn’t, and whether that intern really needs Domain Admin privileges (spoiler: they don’t).

But this brain, while brilliant, is also a favorite target for cybercriminals. One misconfigured policy, one neglected account, and suddenly your network’s “brain” is handing out secrets like a gossiping chatbot.

So, in the spirit of staying one step ahead of the bad guys, let’s talk about how to lock down AD like the digital Fort Knox it was meant to be, without losing your mind or your weekend.


Privileged Account Management: Because Power Needs Boundaries

1. Use Dedicated Admin Accounts

Admins checking email with Domain Admin privileges? That’s like using your company credit card to buy lunch at Taco Bell — risky and unnecessary.
Create separate accounts for admin and daily work. One for privilege, one for productivity. Keep your credentials (and tacos) compartmentalized.


2. Disable Local Administrator Accounts

Every domain-joined system comes with a local admin account and attackers love them more than free Wi-Fi.
Rename or disable them, and use Microsoft LAPS to automatically rotate passwords. It’s simple, effective, and dramatically limits lateral movement.


3. Lock Down the Built-In Administrator Account

The built-in admin is basically the “root god mode” of AD and that’s why attackers target it first.
Mark it as sensitive and cannot be delegated, enforce smart card logon, and deny RDP/network access. Think of it as your digital crown jewels, under lock, key, and 24/7 surveillance.


4. Limit Privileged Group Membership

Groups like Domain Admins and Enterprise Admins should be exclusive clubs, not open mic nights.
Review memberships regularly, remove anyone who doesn’t belong, and make sure each role has a purpose. The smaller the group, the smaller the blast radius.


5. Implement a Tiered Administration Model

Treat your environment like layers of a secure fortress:

  • Tier 0: Domain controllers and critical identity assets
  • Tier 1: Servers and infrastructure
  • Tier 2: User devices and workstations

This approach prevents one compromised credential from leading to full-scale domain doom. It’s containment but stylish.


Identity Hygiene: Clean Directory, Clear Conscience

6. Remove Inactive Accounts

Nothing screams “free real estate” to hackers like dormant user accounts.
Audit and disable old or unused accounts, human or computer. The less clutter in AD, the fewer entry points for attackers.


7. Secure Service Accounts with Managed Identities

Static passwords for service accounts are digital kryptonite. Switch to Managed Service Accounts (MSAs) that rotate passwords automatically and minimize privilege creep.
They’re the automation win your AD never knew it needed.


8. Disable Guest and Anonymous Access

Guest and anonymous access are relics from a more trusting age — like floppy disks and Clippy.
If you must enable them, restrict, monitor, and set expiration dates. Better yet, don’t.


Passwords & Authentication: Fortify the Front Door

9. Enforce Strong Password Policies

Strong passwords are still your best first defense. Set a minimum of 14 characters, enforce complexity, and keep a 24-password history.
It’s not rocket science, it’s digital hygiene.


10. Fine-Grained Password Policies (FGPP)

Admins and regular users don’t need the same rules. Apply FGPP to enforce stricter requirements for privileged accounts without creating user revolt.


11. Account Lockout Policies

Configure smart lockout thresholds to stop brute-force attempts without locking out half your team every Monday morning. Balance security with sanity.


Auditing & Monitoring: Because “Trust But Verify” Is a Security Lifestyle

12. Enable Advanced Audit Policies

Turn on Advanced Audit Policy Configuration to track logons, password changes, and directory access. Logs are your crystal ball — if you bother to read them.


13. Deploy Honeypot Accounts

Honeypots are fake accounts that no one should touch.
If someone does, you’ve got a live one. These tripwire accounts are an elegant way to catch intruders red-handed.


14. Configure User Rights Assignments

Too many permissions? Too many problems.
Regularly review what users can do (logon, shut down, change time, etc.). Less privilege = less chaos.


Maintenance, Patching & Recovery: The Boring Stuff That Saves You

15. Patch Domain Controllers Regularly

Unpatched DCs are like leaving your front door open with a neon “Welcome Hackers” sign.
Patch early, patch often, and keep your DCs dedicated to identity services only.


16. Reset the KRBTGT Account Password (Twice!)

If you’ve never reset your KRBTGT password, consider this your reminder.
It prevents Golden Ticket attacks, which let attackers mint fake Kerberos tickets like it’s Black Friday. Reset it twice during maintenance, it’s tedious but vital.


17. Use Secure Admin Workstations (SAWs)

Admins shouldn’t manage AD from general-use machines.
Deploy isolated, hardened SAWs — no web browsing, no random installs, no nonsense. Treat them like clean rooms for your digital operations.


18. Perform and Test Active Directory Backups

Backups are useless until tested. Run regular AD backups, verify integrity, and document recovery procedures. When disaster strikes, you’ll thank your past self.


Final Thoughts: Security Is a Journey, Not a Checkbox

Securing Active Directory isn’t a one-time project, it’s a habit. You review, patch, audit, and repeat. Because the moment you stop, the adversaries don’t.

So tighten your policies, clean up those stale accounts, and check your audit logs like your paycheck depends on it because it just might.


Wrapping It Up: Stay Cyber-Aware, Not Cyber-Weary

As Cybersecurity Awareness Month reminds us, security isn’t about fear, it’s about habit. The small, consistent things you do today like rotating passwords, trimming privileges, and auditing regularly, stop tomorrow’s breach before it starts.

Active Directory may be the brain of your IT environment, but you are its conscience. Keep it clean, disciplined, and alert, and it’ll serve your business faithfully without gossiping to strangers on the internet.

So take a few minutes this month to revisit your AD setup, fine-tune your defenses, and maybe even drop a honeypot or two. Because security isn’t a checklist — it’s a mindset.

Stay sharp, stay patched, and may your logs always tell the truth.

Good AD security doesn’t just keep attackers out, it keeps you out of the next incident postmortem.

Thank you for stopping by. ✌️

Automatically Roll Over Kerberos Decryption Key with Azure AD SSO

In Azure AD, Seamless Single Sign-on can be configured when Password Hash Sync or Pass through authentication is configured. Azure AD Seamless SSO automatically signs in users when they are in their corporate owned devices and one the corporate network. This helps save a lot of time without repeatedly typing credentials in to Azure AD and other Azure AD integrated applications.

When we enable Azure AD Seamless SSO, a computer account named AZUREADSSOACC is created in on-premises AD in each AD forest. This computer account represent Azure AD in the on-premises AD. Considering how important this computer object is, it is better to take the following steps to protect it,

  • Move it into a OU,
    • Where they are safe from accidental deletions
    • Where only domain admins have access
  • Ensure that Kerberos delegation on the computer account is disabled
  • No other accounts in AD have delegation permissions
    • This can be easy to miss if you have permissions sprawl

The Kerberos decryption key for this computer account is securely shared with Azure AD. Microsoft recommends to roll over the Kerberos decryption Key at least every30 days. You will notice a warning when the key has not been updated in the past 30 days.

Azure AD warning
Kerberos decryption key

As I mentioned earlier, Microsoft recommends to roll over the keys at least every 30 days. Obviously, I can remember it promptly or even put a meeting invite in Outlook to recur 30 days, keep snoozing it to eventually dismiss the alert because I’m stuck doing something else on that day. I’m sure this feels very familiar to most of you reading this. 😂

This is a great scenario where automation can come in handy and I’ll go through the steps on how I implemented this.

Prerequisites

Below are the requirements for implementing this solution.

  • Global admin account
    • With MFA exception (preferably by origin IP)
  • Administrator account on AD Connect server with logon as a batch job
  • On-Premises AD domain admin account
  • Domain account that has permissions to ‘login as batch service’ on the Azure AD connect server

I covered in later half of another post about securely storing credentials using PowerShell into a file. Run these below lines to store encrypted credentials into a text file. This is far better than storing the password in plain text.

Remember, this method is secure but not 100% safe.

$ADCred = Get-Credential
$ADCred.Password | ConvertFrom-SecureString | Out-File "C:\temp\ADCred_sec.txt"
$AzureAD = Get-Credential
$AzureAD.Password | ConvertFrom-SecureString | Out-File "C:\temp\AzureAD_sec.txt"

Enter the domain user and Azure AD user which has necessary permissions in this below script and save it.

$ADUser = 'domain.com\user' #Must be entered in the SAM account name format contoso.com\jdoe
$ADUserEncrypted = Get-Content "C:\temp\ADCred_sec.txt" | ConvertTo-SecureString
$OnPremADCred = New-Object System.Management.Automation.PsCredential($ADUser,$OnPremADCred)

$AzureADUser = 'admin@tenant.onmicrosoft.com'
$AzureADUserEncrypted = Get-Content "C:\temp\AzureAD_sec.txt" | ConvertTo-SecureString
$AzureADCred = New-Object System.Management.Automation.PsCredential($AzureADUser,$AzureADUserEncrypted)

Import-Module 'C:\Program Files\Microsoft Azure Active Directory Connect\AzureADSSO.psd1'
New-AzureADSSOAuthenticationContext -CloudCredentials $AzureADCred
Update-AzureADSSOForest -OnPremCredentials $OnPremADCred

Schedule to automate

Once the script is stored, create a basic scheduled task to run it every month. Below are my settings,

Scheduled Task – General
Scheduled Task – Trigger
Scheduled Task – Actions

After making sure all the setting, Click OK and you will be prompted for credentials. If you receive the below error,

Task Scheduler Error “A specified logon session does not exist” - Microsoft  Tech Community
  1. Run gpedit.msc
  2. Look in Computer Configuration | Windows Settings | Security Settings | Local Policies | Security Options
  3. Double-click Network access: Do not allow storage of passwords and credentials for network authentication
  4. Set the policy to Disabled
  5. Go back to scheduled task to enter your credentials and click OK

Hope this helped you out in automating the Kerberos decrypting key roll over for the AZUREADSSOACC computer account.

Thank you for stopping by. ✌

Azure AD – Password Hash Synchronization – Non-Expiring Password Service Accounts

Recently I worked on implementing password hash synchronization with Azure AD Connect sync in one of the tenants I manage. This interested me on so many levels but especially the lengths that Microsoft has gone to protect this hash sync process fascinated me.

To synchronize a password, Azure AD Connect sync extracts password’s hash from on-premises AD. Extra security processing (meaning, When a user attempts to sign in to Azure AD and enters their password, the password is run through MD4+salt+PBKDF2+HMAC-SHA256 process) is applied to the password hash before it is synchronized to the Azure AD authentication service. Passwords are synchronized on a per-user basis and in chronological order.

When password hash synchronization is enabled, by default the cloud account password is set to ‘Never Expire’. This is a bit scary because if left in default state, users can still login to applications with their password that is expired in on-premise AD. Also meaning that the on-premise AD password expiration policy is not in sync with Azure AD. Users can be forced to comply with your Azure AD password expiration policy by enabling the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature.

When EnforceCloudPasswordPolicyForPasswordSyncedUsers is disabled (which is the default setting), Azure AD Connect sets the PasswordPolicies attribute of synchronized users to “DisablePasswordExpiration”

Get-MsolDirSyncFeatures

To enable the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature, run the following command using the MSOnline PS module

Set-MsolDirSyncFeature -Feature EnforceCloudPasswordPolicyForPasswordSyncedUsers -Enable $True

Once enabled, Azure AD does not go to each synchronized user to remove the “DisablePasswordExpiration” value from the ’PasswordPolicies’ attribute. But waits till the user’s next password change to the “DisablePasswordExpiration” from the ‘PasswordPolicies’ which is when the next password sync happens.

For this reason it is recommended to enable EnforceCloudPasswordPolicyForPasswordSyncedUsers prior to enabling password hash sync, this way the initial sync of password hashes does not add the “DisablePasswordExpiration” value to the ‘PasswordPolicies’ attribute for the users. But if you miss enabling this it is not the end of the world.

Use the below cmdlet to determine a user’s Azure AD password policy,

$user = Read-host "Enter user's UPN:"
Get-AzureADUser -objectID $user | Select DisplayName, passwordpolicies

The issue we need to address are the service accounts that live in on-premise AD with non-expiring password and their identity is synced to Azure AD so these accounts can be used in various applications. So, if you enable EnforceCloudPasswordPolicyForPasswordSyncedUsers feature and then enable password hash sync, your service accounts with non-expiring password will not have any password policy attached to it in Azure AD. These accounts will need the “DisablePasswordExpiration” policy set to them explicitly.

You can set this policy for all the non-expiring password account using the below script,

$ou1 = Get-ADUser -SearchBase 'OU=Users,OU=OU1,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName
$ou2 = Get-ADUser -SearchBase 'OU=Users,OU=OU2,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName #if you are syncing only certain OUs, this helps

$AllOu = $ou1 + $ou2

foreach ($account in $AllOU) {

            $t = (Get-AzureADUser -ObjectID $account.userPrincipalName).passwordpolicies
            if ($t -ne "DisablePasswordExpiration") {
               Set-AzureADUser -ObjectID $account.userPrincipalName -PasswordPolicies "DisablePasswordExpiration"                
    }
}

The Azure AD password policy for these account is empty when it is created in on-premise AD and the administrator creating the account can set the “DisablePasswordExpiration” policy on a per-account basis by running this below,

$user = Read-host "Enter the user's UPN"
Set-AzureADUser -ObjectID $user -PasswordPolicies "DisablePasswordExpiration"

Another caveat here is, when these account’s password is changed on-premises for whatever reason the ‘PasswordPolicies’ value switched to ‘None’

This can happen when,

  • You allow helpdesk resets service account passwords
  • You allow service account owners reset account password
  • Application admins who use these service accounts quit or change job positions and the password needs to be changed
  • Administrator creating the service account on-premise forgot to set the password policy by running the Set-AzureADUser

When the ‘PasswordPolicies’ value gets set to ‘None’ as I mentioned earlier, the account sign-ins to Azure AD will fail with error code ‘50055 — InvalidPasswordExpiredPassword — The password is expired’.

To avoid this, you can create a scheduled task on an on-premise server which run the PS script from above maybe once a week. An issue here will be for these script to run the Connect-AzureAD cmdlet needs to be run. There are probably a thousand different ways to accomplish this but for the sake of simplicity, you can consider these two options,

  • Store your credentials in plan text in the script
  • Create an encrypted, secure string password file and use it in the script

Store credentials in plain text

This is not recommended practice and never be used but there might be scenarios where you may have to use it for some quick tests. In such a scenario, you can just do something like this,

$user = "adminaccount@domain.com"
$pwd = "MySeCur3P@$$w0rd"
$secpwd = ConvertTo-SecureString $pwd -AsPlainText -Force 
$cred = New-Object System.Management.Automation.PSCredential ($user, $secpwd)
Connect-AzureAD -Credential $cred | Out-Null

All this being said, don’t use this method and if you do, please remember to delete the script after testing.

Use secure string password file

This method is far better to securely store password for automation scripts. The idea is, you create password file which has the password stored encrypted. It goes without saying that it is not a good idea to save this file as password.txt.

To create password file,

(Get-Credential).Password | ConvertFrom-SecureString | Out-File "C:\temp\sec.txt"

To silently connect to Azure AD using stored credentials,

$User = "adminaccount@domain.com"
$File = "C:\temp\sec.txt"
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $File | ConvertTo-SecureString)
Connect-AzureAD -credential $credential | Out-Null

Keep in mind that you can only use this file on the computer or server where you created it. This happens because of how Windows Data Protection API encrypts the information from current computer or user account. If you try the file on a different computer you’ll get a ‘Key not valid..’ error. I think this is great and adds another layer of security.

Also, this won’t the password being decrypted or from reusing the encrypted password if it falls into wrong hands. The basic idea here is not to store password in plaintext. This method is not foolproof but good enough.

If you need a secure password file that needs to be used in multiple scripts and on different machines, AES encryption algorithm can be used and covering that will take this post way off the Azure AD non-expiring password accounts topic..too late for that..I know. 😁

Hope this helped you setup your environment before those password expired in Azure AD.

Thank you for stopping by..✌

O365 – Prevent Users from Signing Up for Trials and Purchasing Their Own Pro license

I’ve updated this post with the newer MSCommerce PowerShell module

Self-service is a great idea in several instances but in my opinion when it comes to users signing up for trials and purchasing licenses, it seems to be causing unexpected issues especially while answering to the purchasing team.

Most organizations these days have procurement processes in place to meet regulatory, security, compliance and governance needs. And the need to ensure that all licenses are approved and managed according to defined processes is very important. And when you take expenses, privacy or security into consideration, it is a good idea to disable self-service sign-up and purchases.

To disable all self-service sign-ups

This can be achieved using the MSOL PowerShell module. Type below command to check current settings:

Get-MsolCompanyInformation | fl AllowAdHocSubscriptions
current settings

To disable all self-service sign-ups,

Set-MsolCompanySettings -AllowAdHocSubscriptions:$false

When users try to sign-up, they’ll see the below message,

Your IT department has turned off signup

Prevent users from purchasing their own Pro license

To install and connect the MSCommerce module, start PowerShell as an administrator to install the module,

Install-Module -Name MSCommerce
Import-Module -Name MSCommerce
Connect-MSCommerce
Get-Command *-mscommerce*

To determine current setting,

Get-MSCommercePolicy -PolicyId AllowSelfServicePurchase
Get-MSCommercePolicy

View a list of all available self-service purchase products and the status,

Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase
Get-MSCommerceProductPolicies

Disable the policy setting for a specific product, Below example: ‘Power BI Pro’

Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId CFQ7TTC0L3PB -Enabled $False

Below is an example script on how your can disable AllowSelfServicePurchase by getting the ProductID for ‘Power BI Pro’

$p = Read-Host "Enter product name"
$product = Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | where {$_.ProductName -match $p }
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product.ProductID -Enabled $false
Update-MSCommerceProductPolicy

In scenarios where there are more than one values for the product, Below example: ‘Power Automate’

$p = Read-Host "Enter product name"
$product = Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | where {$_.ProductName -match $p }
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product[0].ProductID -Enabled $false
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product[1].ProductID -Enabled $false
Update-MSCommerceProductPolicy # more than one value

To disable AllowSelfServicePurchase for all products,

Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | ? {$_.PolicyValue -eq "Enabled" } | ForEach {Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $_.ProductId -Enabled $False }
To disable for all products

Thanks for stopping by.✌

Active Directory – Set ‘Manager Can Update Membership List’ with PowerShell

AD group management usually is delegated in most organizations. Usually one of helpdesk’s responsibility or sometime a department manager decides who is member of her or his team at any given time. These users can be non-IT users in several situations.

To serve this purpose, a user can be designated as manager in the Managed By tab of the security group using Active Directory Users and Computers (ADUC) console. Placing a checkmark next to Manager can update membership list allows the user to update the group member by adding or removing users from the security group.

The Manager can update membership list option modifies the Access Control List (ACL) of the security group which we can see in the Security tab, in Advanced.

Setting the Managed by is fairly straight-forward. We can use the Set-ADGroup cmdlet to update the value. We can use the below code to update manager for a group:

$grpname = Read-Host "Enter AD Group's name"
$Managername = Read-Host "Enter AD user's name"
Get-ADGroup -Identity $grpname | Set-ADGroup -ManagedBy "$Managername"

But this doesn’t enable the Manager can update membership list option. To allow this user who is set as this group’s manager, we need to add an AccessRule for this user to the ACL of the group. I’m using this below process to put together a script,

In this below script, We get the user and current ACL of the group. And then the user who will be the manager. And to build the new ACL, we obtain the SID string, then set control type and AD permission. We also need to specify the System ID GUID of the Member attribute and this ACL need to be applied to this specific attribute (Member). Use all the variables to build a new rule, add the rule to the existing rule. And finally use Set-Acl cmdlet to apply new ACL to the Group. Yup!.. that easy!! 🤷‍♂️

$grpname = Read-Host "Enter AD Group's name"
$Grp = Get-ADGroup -Identity $grpname
$GroupACL = Get-Acl AD:\$Grp

$Managername = Read-Host "Enter AD user's name"
$Manager = Get-ADUser -Identity $Managername
$UserSid = New-Object System.Security.Principal.NTAccount ($Manager.SamAccountName)

$Rights = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
$Control = [System.Security.AccessControl.AccessControlType]::Allow
$Guid = [guid]"bf9679c0-0de6-11d0-a285-00aa003049e2"
$Rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($UserSid, $Rights, $Control, $Guid)
$GroupACL.AddAccessRule($Rule)
Set-Acl -AclObject $GroupACL -path AD:\$Grp
Get-ADGroup -Identity $Grp | Set-ADGroup -ManagedBy "$Manager"
Write-Host $Manager.Name set as manager on $group.groupname

What if you have to update multiple groups with a user as manager to each. I didn’t have enough time to make a detailed one and the below script will serve the purpose. I’d probably include checks to make sure the group and the user exists in AD.

Note: csv file has groupname,managerSamname as column headers

$groups = import-csv "C:\Scripts\list.csv"

foreach ($group in $groups){
        $Grp = Get-ADGroup -Identity $group.groupname
        $GroupACL = Get-Acl AD:\$Grp
        $Manager = Get-ADUser -Identity $group.managerSamname
        $UserSid = New-Object System.Security.Principal.NTAccount ($Manager.SamAccountName)
        $Rights = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
        $Control = [System.Security.AccessControl.AccessControlType]::Allow
        $Guid = [guid]"bf9679c0-0de6-11d0-a285-00aa003049e2"
        $Rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($UserSid, $Rights, $Control, $Guid)
        $GroupACL.AddAccessRule($Rule)
        Set-Acl -AclObject $GroupACL -path AD:\$Grp
        Get-ADGroup -Identity $Grp | Set-ADGroup -ManagedBy "$Manager"
        Write-Host $Manager.Name set as manager on $group.groupname
}

Hope this post helped you in bulk setting group managers in AD.

Thank you for stopping by.✌