-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathhibp-audit.ps1
199 lines (177 loc) · 8.37 KB
/
hibp-audit.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
param (
$domain = $(Get-ADDomain | Select-Object -ExpandProperty name),
# Get the link for the HIBP hash file at https://haveibeenpwned.com/Passwords
$hibpurl = "https://downloads.pwnedpasswords.com/passwords/pwned-passwords-ntlm-ordered-by-hash-v5.7z",
$eventsource = "HIBP-Audit",
$workingdir = "C:\hibp-audit",
$excludes_file = "$workingdir\excludes.txt"
)
$requiredmodules = @("DSInternals", "ActiveDirectory")
$downloaddir = "$($workingdir)\download"
$outputdir = "$($workingdir)\output"
$outputfile = "$($outputdir)\compromised-accounts-$domain.txt"
$hibpfile = "$($workingdir)\download\pwned-passwords-ntlm-ordered-by-hash.7z"
$namingcontext = $(Get-ADDomain $domain -ErrorAction Stop | Select-Object -ExpandProperty DistinguishedName)
$server = $(Get-ADDomainController -Discover -Domain $domain -ErrorAction Stop | Select-Object -ExpandProperty Hostname)
function Get-FileFromURL {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0)]
[System.Uri]$URL,
[Parameter(Mandatory, Position = 1)]
[string]$Filename
)
process {
try {
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11"
$request = [System.Net.HttpWebRequest]::Create($URL)
$request.set_Timeout(5000) # 5 second timeout
$request.IfModifiedSince = ([System.IO.FileInfo]$Filename).LastWriteTime
try {
$response = $request.GetResponse()
} catch [System.Net.WebException] {
# Check for a 304, indicating we have the latest version
if ($_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotModified) {
Write-Host " $Filename not modified, not downloading..."
return
} else {
#Unexpected error
$Status = $_.Exception.Response.StatusCode
$msg = $_.Exception
Write-Host " Error dowloading $URL, Status code: $Status - $msg"
exit 1
}
}
$total_bytes = $response.ContentLength
$response_stream = $response.GetResponseStream()
try {
# 256KB works better on my machine for 1GB and 10GB files
# See https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/tr-2004-136.pdf
# Cf. https://stackoverflow.com/a/3034155/10504393
$buffer = New-Object -TypeName byte[] -ArgumentList 256KB
$target_stream = [System.IO.File]::Create($Filename)
$timer = New-Object -TypeName timers.timer
$timer.Interval = 1000 # Update progress every second
$timer_event = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
$Global:update_progress = $true
}
$timer.Start()
do {
$count = $response_stream.Read($buffer, 0, $buffer.length)
$target_stream.Write($buffer, 0, $count)
$downloaded_bytes = $downloaded_bytes + $count
if ($Global:update_progress) {
$percent = $downloaded_bytes / $total_bytes
$status = @{
completed = "{0,6:p2} Completed" -f $percent
downloaded = "{0:n0} MB of {1:n0} MB" -f ($downloaded_bytes / 1MB), ($total_bytes / 1MB)
speed = "{0,7:n0} KB/s" -f (($downloaded_bytes - $prev_downloaded_bytes) / 1KB)
eta = "eta {0:hh\:mm\:ss}" -f (New-TimeSpan -Seconds (($total_bytes - $downloaded_bytes) / ($downloaded_bytes - $prev_downloaded_bytes)))
}
$progress_args = @{
Activity = "Downloading $URL"
Status = "$($status.completed) ($($status.downloaded)) $($status.speed) $($status.eta)"
PercentComplete = $percent * 100
}
Write-Progress @progress_args
$prev_downloaded_bytes = $downloaded_bytes
$Global:update_progress = $false
}
} while ($count -gt 0)
} finally {
if ($timer) { $timer.Stop() }
if ($timer_event) { Unregister-Event -SubscriptionId $timer_event.Id }
if ($target_stream) { $target_stream.Dispose() }
# If file exists and $count is not zero or $null, than script was interrupted by user
if ((Test-Path $Filename) -and $count) {
Remove-Item -Path $Filename
$Filename = $false
}
}
} finally {
if ($response) { $response.Dispose() }
if ($response_stream) { $response_stream.Dispose() }
}
return $Filename
}
}
# See if our event log source exists
try {
$sourceexists = [System.Diagnostics.EventLog]::SourceExists($eventsource)
} catch [System.Security.SecurityException] {
} finally {
if (!($sourceexists)) {
Write-Host "The $eventsource source doesn't exist yet. Please run the following in an elevated powershell:"
Write-Host "New-EventLog -LogName Application -Source `"$eventsource`""
exit 1
}
}
# Check our modules
foreach ($module in $requiredmodules) {
if (!(Get-Module -ListAvailable -Name $module)) {
Write-Host "$module module not installed. Run `"Install-Module $module`" as an administrator."
exit 1
}
}
# Set up our directories
foreach ($d in $workingdir, $downloaddir, $outputdir) {
If (!(Test-Path $d)) {
[void](New-Item -ItemType Directory -Force -Path $d)
}
}
# Download compressed HIBP if needed
Write-Host "Starting transfer of HIBP hashes, this could take some time."
$new = Get-FileFromURL $hibpurl $hibpfile
if ($new) {
Write-Host "Done transfering hashes."
# Remove everything from the download directory except for the original 7z file
Remove-Item -Recurse $downloaddir -Exclude *.7z
} else {
Write-Host "Previously downloaded file is up to date."
}
# See if 7zip is installed
If (!(Test-Path "$env:ProgramFiles\7-Zip\7z.exe")) {
Write-Host "7zip not found. Please install it and re-run this script."
exit 1
}
if (!(Test-Path "$downloaddir\pwned-passwords-ntlm-ordered-by-hash-*.txt" )) {
# Unzip our file
$unzipcommand = '"$env:ProgramFiles\7-Zip\7z.exe" x -o"$downloaddir" "$hibpfile" -r'
Invoke-Expression "& $unzipcommand" -ErrorAction Stop
}
$hashes = Get-ChildItem $downloaddir\*.txt -ErrorAction Stop
try {
$results = Get-ADReplAccount -All -Server $server -NamingContext "$namingcontext" -ErrorAction Stop |
Test-PasswordQuality -WeakPasswordHashesSortedFile $hashes -IncludeDisabledAccounts
$results > $outputfile
} catch [System.UnauthorizedAccessException] {
Write-Host "Get-ADReplAccount failed, you probably don't have the `"Replicating Directory Changes All`" right."
exit 1
} catch {
Write-Host "An unexpected error occurred."
exit 1
}
# Build our exclude list
$excludes = New-Object System.Collections.ArrayList
if (Test-Path $excludes_file) {
$excluded = Get-Content $excludes_file
foreach ($ou in $excluded) {
foreach ($user in Get-ADUser -Filter * -SearchBase "$ou") {
$excludes.Add($user.SamAccountName)
}
}
}
# Log vulnerable users
$compromisedcount = 0
foreach ($account in $results | Select-Object -ExpandProperty WeakPassword) {
if ($account -notin $excludes) {
Write-EventLog -LogName Application -Source $eventsource -EntryType Information -EventId 3 -Message "HIBP Audit found the password for $domain\$account in a breach database."
$compromisedcount++
}
}
if ($compromisedcount -gt 0) {
Write-EventLog -LogName Application -Source $eventsource -EntryType Information -EventId 2 -Message "HIBP Audit found $($compromisedcount) compromised accounts in the $domain domain."
Write-Host "HIBP Audit found $($compromisedcount) compromised accounts in the $domain domain."
}
Write-Host "HIBP Audit completed. Please see $outputfile for results."
Write-EventLog -LogName Application -Source $eventsource -EntryType Information -EventId 1 -Message "HIBP Audit script completed successfully against the domain $domain."