-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path09-Hydra-Collect.ps1
More file actions
375 lines (316 loc) · 18.5 KB
/
09-Hydra-Collect.ps1
File metadata and controls
375 lines (316 loc) · 18.5 KB
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# Bitpusher
# \`._,'/
# (_- -_)
# \o/
# The Digital
# Fox
# https://theTechRelay.com
# https://github.com/bitpusher2k
#
# Hydra-Collect.ps1 - By Bitpusher/The Digital Fox
# v3.1.1 last updated 2025-09-17
# Script to trigger set of oft-used investigation scripts with default options at the outset of an investigation.
# "Hydra" because each sub-script "head" is independent (a failure of one will not impact others), and because it's memorable.
#
# Sets output to default "investigation" desktop folder and sets scope to past seven days by default.
#
# Runs:
# * 10-Get-BasicTenantInformation.ps1
# * 18-Search-InboxRuleChanges.ps1 (first pass)
# * 13-Get-AllM365EmailAddresses.ps1
# * 14-Get-AllUserPasswordReport.ps1
# * 17-Search-MailboxSuspiciousRules.ps1
# * 19-Get-AllInboxRules.ps1
# * 22-Get-EnterpriseApplications.ps1
# * 20-Get-ForwardingSettings.ps1
# * 21-Get-MailboxPermissions.ps1
# * 23-Get-DefenderInformation.ps1 # Get-MpThreatDetection not currently working - skipping
# * 24-Get-EntraIDRisk.ps1
# * 90-Get-MFAReport.ps1
# * 91-Get-CAPReport-P1.ps1
# * 93-Get-SecureScoreInformation.ps1
# * OPTIONALLY: 15-Search-UnifiedAuditLogIR.ps1
# * OPTIONALLY: Get-UnifiedAuditLogEntries.ps1
# * 18-Search-InboxRuleChanges.ps1 (second pass)
# * 11-Get-EntraIDAuditAndSignInLogs30-P1.ps1
# * 12-Search-UnifiedAuditLogSignIn.ps1
# * OPTIONALLY: Run several Invictus IR Microsoft Extractor Suite cmdlets for additional reports
# * OPTIONALLY: Run CrowdStrike Reporting Tool for Azure (Get-CRTReport.ps1) for additional reports
#
# Usage:
# powershell -executionpolicy bypass -f .\Hydra-Collect.ps1
#
# powershell -executionpolicy bypass -f .\Hydra-Collect.ps1 -OutputPath "Default" -DaysAgo 7
#
# powershell -executionpolicy bypass -f .\Hydra-Collect.ps1 -OutputPath "Default" -StartDate "2025-06-01" -EndDate "2025-06-10"
#
# Run with already existing connection to M365 tenant through
# PowerShell modules.
#
# Uses ExchangePowerShell, AzureAD, Microsoft Graph commands.
#
#comp #m365 #security #bec #script #info #hydra #collect #tenant
#Requires -Version 5.1
param(
[string]$OutputPath = "Default",
[int]$DaysAgo,
[datetime]$StartDate,
[datetime]$EndDate,
[string]$scriptName = "Hydra-Collect",
[string]$Priority = "Normal",
[string]$DebugPreference = "SilentlyContinue",
[string]$VerbosePreference = "SilentlyContinue",
[string]$InformationPreference = "Continue",
[string]$logFileFolderPath = "C:\temp\log",
[string]$ComputerName = $env:computername,
[string]$ScriptUserName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name,
[string]$logFilePrefix = "$scriptName" + "_" + "$ComputerName" + "_",
[string]$logFileDateFormat = "yyyyMMdd_HHmmss",
[int]$logFileRetentionDays = 30,
[string]$Encoding = "utf8bom" # PS 5 & 7: "Ascii" (7-bit), "BigEndianUnicode" (UTF-16 big-endian), "BigEndianUTF32", "Oem", "Unicode" (UTF-16 little-endian), "UTF32" (little-endian), "UTF7", "UTF8" (PS 5: BOM, PS 7: NO BOM). PS 7: "ansi", "utf8BOM", "utf8NoBOM"
)
#region initialization
if ($PSVersionTable.PSVersion.Major -eq 5 -and ($Encoding -eq "utf8bom" -or $Encoding -eq "utf8nobom")) { $Encoding = "utf8" }
function Get-TimeStamp {
param(
[switch]$NoWrap,
[switch]$Utc
)
$dt = Get-Date
if ($Utc -eq $true) {
$dt = $dt.ToUniversalTime()
}
$str = "{0:yyyy-MM-dd} {0:HH:mm:ss}" -f $dt
if ($NoWrap -ne $true) {
$str = "[$str]"
}
return $str
}
if ($logFileFolderPath -ne "") {
if (!(Test-Path -PathType Container -Path $logFileFolderPath)) {
Write-Output "$(Get-TimeStamp) Creating directory $logFileFolderPath" | Out-Null
New-Item -ItemType Directory -Force -Path $logFileFolderPath | Out-Null
} else {
$DatetoDelete = $(Get-Date).AddDays(- $logFileRetentionDays)
Get-ChildItem $logFileFolderPath | Where-Object { $_.Name -like "*$logFilePrefix*" -and $_.LastWriteTime -lt $DatetoDelete } | Remove-Item | Out-Null
}
$logFilePath = $logFileFolderPath + "\$logFilePrefix" + (Get-Date -Format $logFileDateFormat) + ".LOG"
}
$sw = [Diagnostics.StopWatch]::StartNew()
Write-Output "$scriptName started on $ComputerName by $ScriptUserName at $(Get-TimeStamp)" | Tee-Object -FilePath $logFilePath -Append
$process = Get-Process -Id $pid
Write-Output "Setting process priority to `"$Priority`"" | Tee-Object -FilePath $logFilePath -Append
$process.PriorityClass = $Priority
$modules = @("Microsoft.Graph", "Microsoft.Graph.Beta", "ExchangeOnlineManagement", "Microsoft-Extractor-Suite")
foreach ($module in $modules) {
if (Get-Module -ListAvailable -Name $module) {
Write-Output "$(Get-TimeStamp) $module already installed"
} else {
Write-Output "$(Get-TimeStamp) Installing $module"
Install-Module $module -Force -SkipPublisherCheck -Scope CurrentUser -ErrorAction Stop | Out-Null
}
}
$modules = @("Microsoft.Graph", "ExchangeOnlineManagement", "Microsoft-Extractor-Suite")
foreach ($module in $modules) {
if (Get-Module -Name $module -ListAvailable) {
Write-Output "$(Get-TimeStamp) $module already loaded"
} else {
Write-Output "$(Get-TimeStamp) Loading $module"
Import-Module $module -Force -Scope Local | Out-Null
}
}
#endregion initialization
$date = Get-Date -Format "yyyyMMddHHmmss"
$EXOInfo = Get-ConnectionInformation
$GraphInfo = Get-MgContext
if ($EXOInfo) {
Write-Output "Exchange Online connection status:" | Tee-Object -FilePath $logFilePath -Append
$EXOInfo.State | Tee-Object -FilePath $logFilePath -Append
$EXOInfo.TenantID | Tee-Object -FilePath $logFilePath -Append
$EXOInfo.UserPrincipalName | Tee-Object -FilePath $logFilePath -Append
} else {
Write-Output "Exchange Online Management module not connected." | Tee-Object -FilePath $logFilePath -Append
Write-Output "Run .\01-Connect-M365Modules.ps1 or Connect-ExchangeOnline to connect." | Tee-Object -FilePath $logFilePath -Append
Write-Output "Ending." | Tee-Object -FilePath $logFilePath -Append
exit
}
if ($GraphInfo) {
Write-Output "Graph connection status:" | Tee-Object -FilePath $logFilePath -Append
$GraphInfo.AuthType | Tee-Object -FilePath $logFilePath -Append
$GraphInfo.TenantID | Tee-Object -FilePath $logFilePath -Append
$GraphInfo.Account | Tee-Object -FilePath $logFilePath -Append
$GraphInfo.Scopes | Tee-Object -FilePath $logFilePath -Append
} else {
Write-Output "Exchange Online Management module not connected." | Tee-Object -FilePath $logFilePath -Append
Write-Output "Run .\01-Connect-M365Modules.ps1 or Connect-MgGraph with proper scopes to connect." | Tee-Object -FilePath $logFilePath -Append
Write-Output "Ending." | Tee-Object -FilePath $logFilePath -Append
exit
}
## If OutputPath variable is not defined, prompt for it
if (!$OutputPath) {
Write-Output ""
$OutputPath = Read-Host "Enter the output base path, e.g. $($env:userprofile)\Desktop\Investigation (default)" | Tee-Object -FilePath $logFilePath -Append
If ($OutputPath -eq '') { $OutputPath = "$($env:userprofile)\Desktop\Investigation" }
Write-Output "Output base path will be in $OutputPath" | Tee-Object -FilePath $logFilePath -Append
} elseif ($OutputPath -eq 'Default') {
Write-Output ""
$OutputPath = "$($env:userprofile)\Desktop\Investigation"
Write-Output "Output base path will be in $OutputPath" | Tee-Object -FilePath $logFilePath -Append
}
## If OutputPath does not exist, create it
$CheckOutputPath = Get-Item $OutputPath -ErrorAction SilentlyContinue
if (!$CheckOutputPath) {
Write-Output ""
Write-Output "Output path does not exist. Directory will be created." | Tee-Object -FilePath $logFilePath -Append
mkdir $OutputPath
}
## Get Primary Domain Name for output subfolder
# $PrimaryDomain = Get-AcceptedDomain | Where-Object Default -eq $true
# $DomainName = $PrimaryDomain.DomainName
$PrimaryDomain = Get-MgDomain | Where-Object { $_.isdefault -eq $True } | Select-Object -Property ID
if ($PrimaryDomain) {
$DomainName = $PrimaryDomain.ID
} else {
$DomainName = "DefaultOutput"
}
$CheckSubDir = Get-Item $OutputPath\$DomainName -ErrorAction SilentlyContinue
if (!$CheckSubDir) {
Write-Output ""
Write-Output "Domain sub-directory does not exist. Sub-directory `"$DomainName`" will be created." | Tee-Object -FilePath $logFilePath -Append
mkdir $OutputPath\$DomainName
}
## Get valid starting end ending dates
if (!$DaysAgo -and (!$StartDate -or !$EndDate)) {
Write-Output ""
$DaysAgo = Read-Host 'Enter how many days back to retrieve relevant UAL entries (default: 10, maximum: 180)'
if ($DaysAgo -eq '') { $DaysAgo = "10" } elseif ($DaysAgo -gt 180) { $DaysAgo = "180" }
}
if ($DaysAgo) {
if ($DaysAgo -gt 180) { $DaysAgo = "180" }
Write-Output "`nScript will search UAC $DaysAgo days back from today for relevant events." | Tee-Object -FilePath $logFilePath -Append
$StartDate = (Get-Date).touniversaltime().AddDays(-$DaysAgo)
$EndDate = (Get-Date).touniversaltime()
Write-Output "StartDate: $StartDate (UTC)" | Tee-Object -FilePath $logFilePath -Append
Write-Output "EndDate: $EndDate (UTC)" | Tee-Object -FilePath $logFilePath -Append
} elseif ($StartDate -and $EndDate) {
$StartDate = ($StartDate).touniversaltime()
$EndDate = ($EndDate).touniversaltime()
if ($StartDate -lt (Get-Date).touniversaltime().AddDays(-180)) { $StartDate = (Get-Date).touniversaltime().AddDays(-180) }
if ($StartDate -ge $EndDate) { $EndDate = ($StartDate).AddDays(1) }
Write-Output "`nScript will search UAC between StartDate and EndDate for relevant events." | Tee-Object -FilePath $logFilePath -Append
Write-Output "StartDate: $StartDate (UTC)" | Tee-Object -FilePath $logFilePath -Append
Write-Output "EndDate: $EndDate (UTC)" | Tee-Object -FilePath $logFilePath -Append
} else {
Write-Output "Neither DaysAgo nor StartDate/EndDate specified. Ending." | Tee-Object -FilePath $logFilePath -Append
exit
}
Write-Output "Opening Edge browser window to Entra ID sign-ins for retrieval of sign-in logs (manually download for Entra ID Free tenants)..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/SignIns"
Write-Output "Opening Edge browser window to Entra ID admin home page..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview"
Write-Output "Opening Edge browser window to M365 Admin portal..."
Start-Process msedge.exe -ArgumentList "https://admin.cloud.microsoft/?#/homepage"
Write-Output "Opening Edge browser window to Entra ID Risky Users..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/SecurityMenuBlade/~/RiskyUsers"
Write-Output "Opening Edge browser window to Entra ID Risky sign-ins..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/SecurityMenuBlade/~/RiskySignIns"
Write-Output "Opening Edge browser window to Risk Detections..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/SecurityMenuBlade/~/RiskDetections"
Write-Output "Opening additional Edge browser admin windows..."
Start-Process msedge.exe -ArgumentList "https://portal.azure.com/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AppAppsPreview/applicationType/All"
Start-Process msedge.exe -ArgumentList "https://entra.microsoft.com/"
Start-Process msedge.exe -ArgumentList "https://admin.exchange.microsoft.com/#/homepage"
Start-Process msedge.exe -ArgumentList "https://security.microsoft.com/"
Start-Process msedge.exe -ArgumentList "https://purview.microsoft.com/"
Start-Process msedge.exe -ArgumentList "https://admin.microsoft.com/sharepoint"
Write-Output "`nRunning 10-Get-BasicTenantInformation.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\10-Get-BasicTenantInformation.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 18-Search-InboxRuleChanges.ps1... First pass..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\18-Search-InboxRuleChanges.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate
Write-Output "`nRunning 13-Get-AllM365EmailAddresses.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\13-Get-AllM365EmailAddresses.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 14-Get-AllUserPasswordReport.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\14-Get-AllUserPasswordReport.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 17-Search-MailboxSuspiciousRules.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\17-Search-MailboxSuspiciousRules.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 19-Get-AllInboxRules.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\19-Get-AllInboxRules.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 22-Get-EnterpriseApplications.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\22-Get-EnterpriseApplications.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 20-Get-ForwardingSettings.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\20-Get-ForwardingSettings.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 21-Get-MailboxPermissions.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\21-Get-MailboxPermissions.ps1" -OutputPath $OutputPath
# Write-Output "`nRunning 23-Get-DefenderInformation.ps1..." | Tee-Object -FilePath $logFilePath -Append # currently Get-MpThreatDetection cmdlet is failing
# & "$PSScriptRoot\23-Get-DefenderInformation.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 24-Get-EntraIDRisk.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\24-Get-EntraIDRisk.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 90-Get-MFAReport.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\90-Get-MFAReport.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 91-Get-CAPReport-P1.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\91-Get-CAPReport-P1.ps1" -OutputPath $OutputPath
Write-Output "`nRunning 93-Get-SecureScoreInformation.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\93-Get-SecureScoreInformation.ps1" -OutputPath $OutputPath
$Response = Read-Host "`nRetrieve often relevant UAL entries between StartDate and EndDate? (Y/N - default N)"
if ($Response -eq 'Y') {
Write-Output "`nRunning 15-Search-UnifiedAuditLogIR.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\15-Search-UnifiedAuditLogIR.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate
}
$Response = Read-Host "`nRetrieve all available UAL entries for between StartDate and EndDate? (Y/N - default N)"
if ($Response -eq 'Y') {
Write-Output "`nRunning Get-UnifiedAuditLogEntries.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\16-Get-UnifiedAuditLogEntries.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate
}
Write-Output "`nRunning 18-Search-InboxRuleChanges.ps1... Second pass (often gets info when first is blank)..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\18-Search-InboxRuleChanges.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate
Write-Output "`nRunning 12-Search-UnifiedAuditLogSignIn.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\12-Search-UnifiedAuditLogSignIn.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate -UserIds "ALL"
Write-Output "`nRunning 11-Get-EntraIDAuditAndSignInLogs30-P1.ps1..." | Tee-Object -FilePath $logFilePath -Append
& "$PSScriptRoot\11-Get-EntraIDAuditAndSignInLogs30-P1.ps1" -OutputPath $OutputPath -StartDate $StartDate -EndDate $EndDate
Write-Output "`nRun a set of Invictus IR Microsoft Extractor Suite cmdlets to:"
Write-Output "* Generate CSV report Security Defaults settings"
Write-Output "* Generate CSV report of transport rules"
Write-Output "* Generate CSV report of security alerts"
Write-Output "* Generate reports of Admin users"
Write-Output "* Generate CSV report of mailbox audit status"
Write-Output "* Generate CSV report of OAuth permissions"
Write-Output "* Generate CSV report of user's MFA settings"
$Response = Read-Host "Run cmdlets? (Y/N - default N)"
if ($Response -eq 'Y') {
Write-Output "`nRunning Invictus IR cmdlets..." | Tee-Object -FilePath $logFilePath -Append
Get-EntraSecurityDefaults -OutputDir $IRoutput
Get-TransportRules -OutputDir $IRoutput
Get-SecurityAlerts -OutputDir $IRoutput -DaysBack $DaysAgo
Get-AdminUsers -OutputDir $IRoutput
Get-MailboxAuditStatus -OutputDir $IRoutput
Get-OAuthPermissionsGraph -OutputDir $IRoutput
Get-MFA -OutputDir $IRoutput
# Get-ConditionalAccessPolicies -OutputDir $IRoutput
# Get-MailboxPermissions -OutputDir $IRoutput
}
Write-Output "`nRun CrowdStrike Reporting Tool for Azure to generate reports related to:"
Write-Output "* Federation Configuration"
Write-Output "* Federation Trust"
Write-Output "* Client Access Settings Configured on Mailboxes"
Write-Output "* Mail Forwarding Rules for Remote Domains"
Write-Output "* Mailbox SMTP Forwarding Rules"
Write-Output "* Mail Transport Rules"
Write-Output "* Delegates with 'Full Access' and those with Any Permissions Granted"
Write-Output "* Delegates with 'Send As' or 'SendOnBehalf' Permissions"
Write-Output "* Exchange Online PowerShell Enabled Users"
Write-Output "* Users with 'Audit Bypass' Enabled"
Write-Output "* Mailboxes Hidden from the Global Address List (GAL)"
Write-Output "* Administrator audit logging configuration settings"
$Response = Read-Host "`nRun CrowdStrike Reporting Tool for Azure (CRT - will prompt for Exchange Online and Azure AD auth and disconnect after)? (Y/N - default N)"
if ($Response -eq 'Y') {
Write-Output "`nDownloading and Running Get-CRTReport.ps1..." | Tee-Object -FilePath $logFilePath -Append
Invoke-WebRequest "https://github.com/CrowdStrike/CRT/raw/refs/heads/main/Get-CRTReport.ps1" -OutFile $PSScriptRoot\Get-CRTReport.ps1
& "$PSScriptRoot\Get-CRTReport.ps1" -WorkingDirectory $IRoutput -Interactive
}
Write-Output "`nScript complete." | Tee-Object -FilePath $logFilePath -Append
Write-Output "Seconds elapsed for script execution: $($sw.elapsed.totalseconds)" | Tee-Object -FilePath $logFilePath -Append
Write-Output "`nDone! Check output path for results." | Tee-Object -FilePath $logFilePath -Append
Write-Output "After identifying suspect IP addresses recommend pulling all logged actions with:" | Tee-Object -FilePath $logFilePath -Append
Write-Output ".\36-Search-UALActivityByIPAddress.ps1" | Tee-Object -FilePath $logFilePath -Append
Invoke-Item "$OutputPath\$DomainName"
exit