# Audit your Active Directory user passwords against haveibeenpwned.com safely using PowerShell

In this article, I will show you, how to **easily** and **securely** check, whether some of your users are using leaked passwords. All of this by running one PowerShell function 👍.

Why you should care you may ask? Leaked passwords shouldn't be used, because attackers will with high probability try to use them in their brute force attacks (like in last [Colonial Pipeline attack](https://it.slashdot.org/story/21/06/05/0245238/hackers-breached-colonial-pipeline-using-compromised-password?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+Slashdot%2Fslashdot+%28Slashdot%29)). Plus attackers are in general exploiting human laziness i.e. usage of the same password across different services.

What does it mean **securely**? There are [free tools](https://specopssoft.com/product/specops-password-auditor/) or [builtin paid one](https://docs.microsoft.com/en-us/azure/active-directory/authentication/concept-password-ban-bad) that do quite the same thing as my solution. But when dealing with AD passwords (or generally anything that needs Domain Admin rights) I am very cautious. Therefore I do all sensitive actions in **VM without a network connection**, use **secure delete**, plus data never leaves high tier servers. So no sensitive information can leak.

---

# Disclaimer 
What is safe in my environment can be unsafe in yours, so think this through before implementing!

---

# Table of Contents
- [Prerequisites:](#prerequisites)
- [Break down of the PowerShell function](#break-down-of-the-powershell-function)
  - [Interesting parts of the function](#interesting-parts-of-the-function)
    - [1. Extracting Active Directory passwords](#1-extracting-active-directory-passwords)
    - [2. Running DSInternals with haveibeenpwned database against extracted passwords](#2-running-dsinternals-with-haveibeenpwned-database-against-extracted-passwords)
- [OK so how to use it?](#ok-so-how-to-use-it)
- [Summary](#summary)

---

# Prerequisites:
- **Domain Admin rights**, for extracting user password hashes from your DC
- **Hyper-V server cluster managed by SCVMM**, for hosting **VM without network connectivity** (as a safe playground that cannot leak any sensitive information)
 - (you can just extract the interesting parts from my function and use them as you wish)
- **PowerShell** for automation and [Powershell Direct](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/powershell-direct) for controlling VM
- Downloaded [database of leaked NTLM hashes from haveibeenpwned.com](https://haveibeenpwned.com/Passwords), for comparison with your user hashes
 - even [FBI is going to feed this list :)](https://therecord.media/the-fbi-will-feed-hacked-passwords-directly-into-have-i-been-pwned/#:~:text=The%20FBI%20will%20feed%20hacked%20passwords%20directly%20into%20Have%20I%20Been%20Pwned,-By%20Catalin%20Cimpanu&text=Australian%20security%20researcher%20Troy%20Hunt,indexes%20data%20from%20security%20breaches.)
- Awesome [DSInternals PowerShell module](https://github.com/MichaelGrafnetter/DSInternals), for comparing passwords from your AD against the downloaded database

---

# Break down of the PowerShell function

Function [Invoke-ADPasswordsAudit](https://github.com/ztrhgf/useful_powershell_functions/blob/master/Active%20Directory/Invoke-ADPasswordsAudit.ps1) will:
- Check if there is a newer version of haveibeenpwnd database
- Export ntds.dit and syskey on random DC
- Creates VM on Hyper-V cluster node (without network connection)
- To cluster node, where VM will be hosted copy
    - exported ntds and syskey
    - DSInternals and haveibeenpwnd database
- Delete ntds.dit and syskey on DC
- From cluster node to VM copy
    - exported ntds and syskey
    - DSInternals and haveibeenpwnd database
- Delete this copied data on the cluster node
    - ntds.dit and syskey
    - deprecated haveibeenpwnd databases
- Run DSInternals function Test-PasswordQuality inside the VM, to test AD passwords
- Get the results
- Remove VM

---

## Interesting parts of the function

### 1. Extracting Active Directory passwords
```
"Exporting NTDS and syskey on $DC"

# where the exported data will be stored on DC
$IFMPath = "C:\Windows\whatever"
# pick DC with PDCEmulator role (it doesn't matter, but why not)
$DC = ((Get-ADDomain | Select-Object -exp PDCEmulator) -split "\.")[0] 

Invoke-Command -ComputerName $DC {
    param ($IFMPath)

    if (Test-Path $IFMPath -ea SilentlyContinue) { Remove-Item $IFMPath -Recurse -Force }

    $IFM = ntdsutil "activate instance ntds" ifm "create full `"$IFMPath`"" q q

    if (!($IFM -like "*IFM media created successfully*")) {
        Remove-Item $IFMPath -Recurse -Force # just for sure
        throw "Export failed`n`n$IFM"
    }
} -ArgumentList $IFMPath
```

### 2. Running DSInternals with haveibeenpwned database against extracted passwords
Just for sure, on VM **without network** connection run:
```
"Getting leaked passwords used in your Active Directory domain"

Import-Module "DSinternals" -ErrorAction Stop

#region important variables
# path to downloaded ntlm ordered database of passwords from https://haveibeenpwned.com/Passwords
$pwnedPasswords = "C:\whatever\pwned-passwords-ntlm-ordered-by-hash.txt" 

# path to txt file with manually entered passwords you want to check too (one password at a line)
$weakPasswords = "C:\whatever\weakPasswordsFile.txt"

# path to exported SYSTEM registry that you've got on DC by running ntdsutil command in previous step
$key = Get-BootKey -SystemHivePath 'C:\whatever\registry\SYSTEM'
if (!$key) { throw "Couldn't get syskey" }

# path to exported ntds.dit password database that you've got on DC by running ntdsutil command in previous step
$ntds = 'C:\whatever\Active Directory\ntds.dit'
#endregion important variables

# parameters for Test-PasswordQuality
$params = @{
    WeakPasswordHashesSortedFile = $pwnedPasswords
}
if ($weakPasswords) {
    $params.WeakPasswordsFile = $weakPasswords
}

# run passwords audit
$result = Get-ADDBAccount -All -DatabasePath $ntds -BootKey $key | Test-PasswordQuality @params

# to get nice human readable output
return ($result | Out-String) 
```

---

# OK so how to use it?

1. To `C:\ADAudit` download:
 - DSInternals module
```
mkdir C:\ADAudit
Save-Module DSInternals C:\ADAudit
```
 - Newest [NTLM ordered passwords](https://haveibeenpwned.com/Passwords) file
 - Both PowerShell scripts ( [Invoke-ADPasswordsAudit](https://github.com/ztrhgf/useful_powershell_functions/blob/master/Active%20Directory/Invoke-ADPasswordsAudit.ps1), [New-VMFromTemplate](https://github.com/ztrhgf/useful_powershell_functions/blob/master/SCVMM/New-VMFromTemplate.ps1)) 
2. Check the content of both scripts to know, what they are doing
3. Test & modify the `New-VMFromTemplate` function to work in your environment
4. In PowerShell console that runs under Domain Admin   
 - Dot source both scripts to load defined functions
 - Run
```
Invoke-ADPasswordsAudit -pwnedPasswordsNTLMOrdered "C:\ADAudit\pwned-passwords-ntlm-ordered-by-hash-v7.txt" -DSInternals "C:\ADAudit\DSInternals"
```
5. Wait :)
 - It can take dozens of minutes to finish because a new VM has to be created, 20GB passwords file has to be copied to node cluster and then to VM itself, and of course, your AD hashes has to be compared with it...

---

# Summary
It's good practice to run this function on **regular basis**, because passwords of your users are going to change, so as leaked password database :)

Enjoy! 👍

