Account Compromise Response
Automated incident response for compromised user accounts
Table of Contents
Automated response script for handling compromised user accounts including account lockdown, session revocation, and forensic data collection.
Overview
This script provides a structured incident response workflow for compromised user accounts. It automatically disables the account, revokes all active sessions, resets credentials, checks for malicious inbox rules and forwarding, and collects forensic information for investigation.
Use Case: Immediate response to account compromise, automated security lockdown, forensic evidence collection, compliance with incident response runbooks.
Platform: Windows Server 2012 R2+, Microsoft 365 (optional) Requirements: Domain Admin privileges, Active Directory module, Exchange Online module (for O365 checks) Execution Time: 1-3 minutes
The Script
1<#
2.SYNOPSIS
3 Automated response for compromised user accounts
4
5.DESCRIPTION
6 Performs immediate incident response actions for compromised accounts:
7 - Disables the user account in Active Directory
8 - Revokes all active sessions (Kerberos tickets, O365 sessions)
9 - Forces immediate password reset
10 - Checks for malicious inbox rules
11 - Checks for email forwarding configurations
12 - Reviews recent activity (logins, changes)
13 - Collects forensic data for investigation
14 - Generates incident response report
15
16.PARAMETER UserIdentity
17 Username, email, or SAM account name of the compromised account
18
19.PARAMETER SkipO365
20 Skip Office 365/Exchange Online checks (for on-prem only)
21
22.PARAMETER ReportPath
23 Path to save incident response report (default: current directory)
24
25.PARAMETER NoDisable
26 Collect forensics only without disabling account (investigation mode)
27
28.EXAMPLE
29 .\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith"
30 Full response for compromised user jsmith
31
32.EXAMPLE
33 .\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith@domain.com" -SkipO365
34 Response for on-premises account only
35
36.EXAMPLE
37 .\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith" -NoDisable
38 Investigation mode - collect data without disabling account
39
40.NOTES
41 Author: glyph.sh
42 Requires: Domain Administrator privileges
43 Reference: Incident Response Runbook
44 Last Updated: 2025-11-02
45#>
46
47[CmdletBinding(SupportsShouldProcess)]
48param(
49 [Parameter(Mandatory = $true)]
50 [string]$UserIdentity,
51
52 [switch]$SkipO365,
53 [string]$ReportPath = ".\CompromiseReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').html",
54 [switch]$NoDisable
55)
56
57#Requires -RunAsAdministrator
58#Requires -Modules ActiveDirectory
59
60# Initialize results collection
61$script:IncidentData = @{
62 Timestamp = Get-Date
63 TargetUser = $UserIdentity
64 Actions = @()
65 Findings = @()
66 Recommendations = @()
67}
68
69function Write-IncidentLog {
70 param(
71 [string]$Message,
72 [ValidateSet('Info', 'Success', 'Warning', 'Critical', 'Action')]
73 [string]$Level = 'Info'
74 )
75
76 $color = switch ($Level) {
77 'Success' { 'Green' }
78 'Warning' { 'Yellow' }
79 'Critical' { 'Red' }
80 'Action' { 'Cyan' }
81 default { 'White' }
82 }
83
84 $prefix = switch ($Level) {
85 'Success' { '[OK]' }
86 'Warning' { '[WARN]' }
87 'Critical' { '[ALERT]' }
88 'Action' { '[ACTION]' }
89 default { '[INFO]' }
90 }
91
92 $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
93 Write-Host "$timestamp $prefix $Message" -ForegroundColor $color
94
95 if ($Level -eq 'Action') {
96 $script:IncidentData.Actions += "$timestamp - $Message"
97 }
98 elseif ($Level -eq 'Critical' -or $Level -eq 'Warning') {
99 $script:IncidentData.Findings += "$timestamp - [$Level] $Message"
100 }
101}
102
103Write-Host "========================================" -ForegroundColor Red
104Write-Host " ACCOUNT COMPROMISE RESPONSE" -ForegroundColor Red
105Write-Host "========================================" -ForegroundColor Red
106Write-Host ""
107Write-IncidentLog "Incident response initiated for: $UserIdentity" -Level Action
108Write-Host ""
109
110# Step 1: Verify user exists and get details
111Write-Host "[1/8] Retrieving User Account Information..." -ForegroundColor Yellow
112Write-Host ""
113
114try {
115 $user = Get-ADUser -Identity $UserIdentity -Properties * -ErrorAction Stop
116
117 Write-IncidentLog "User found: $($user.UserPrincipalName)" -Level Success
118 Write-IncidentLog "Display Name: $($user.DisplayName)" -Level Info
119 Write-IncidentLog "SAM Account: $($user.SamAccountName)" -Level Info
120 Write-IncidentLog "Email: $($user.EmailAddress)" -Level Info
121 Write-IncidentLog "Current Status: $(if($user.Enabled){'ENABLED'}else{'DISABLED'})" -Level Info
122 Write-IncidentLog "Last Logon: $($user.LastLogonDate)" -Level Info
123 Write-IncidentLog "Password Last Set: $($user.PasswordLastSet)" -Level Info
124 Write-Host ""
125
126 # Check if account is already disabled
127 if (-not $user.Enabled) {
128 Write-IncidentLog "Account is already disabled" -Level Warning
129 }
130}
131catch {
132 Write-IncidentLog "Failed to retrieve user: $($_.Exception.Message)" -Level Critical
133 exit 1
134}
135
136# Step 2: Disable the account
137if (-not $NoDisable) {
138 Write-Host "[2/8] Disabling User Account..." -ForegroundColor Yellow
139 Write-Host ""
140
141 try {
142 if ($user.Enabled) {
143 if ($PSCmdlet.ShouldProcess($user.UserPrincipalName, "Disable Account")) {
144 Disable-ADAccount -Identity $user -ErrorAction Stop
145 Write-IncidentLog "Account disabled successfully" -Level Action
146
147 # Set description noting compromise
148 $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm"
149 $description = "COMPROMISED - Disabled by IR team on $timestamp. Original: $($user.Description)"
150 Set-ADUser -Identity $user -Description $description -ErrorAction SilentlyContinue
151 }
152 }
153 else {
154 Write-IncidentLog "Account already disabled - no action taken" -Level Info
155 }
156 }
157 catch {
158 Write-IncidentLog "Failed to disable account: $($_.Exception.Message)" -Level Critical
159 }
160
161 Write-Host ""
162}
163else {
164 Write-Host "[2/8] Skipping account disable (investigation mode)" -ForegroundColor Gray
165 Write-Host ""
166}
167
168# Step 3: Revoke all active sessions
169Write-Host "[3/8] Revoking Active Sessions..." -ForegroundColor Yellow
170Write-Host ""
171
172# Revoke Kerberos tickets
173try {
174 Write-IncidentLog "Purging Kerberos tickets..." -Level Action
175
176 # Force Kerberos ticket revocation by changing password
177 # This invalidates all existing tickets
178 $randomPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 32 | ForEach-Object {[char]$_})
179 $securePassword = ConvertTo-SecureString $randomPassword -AsPlainText -Force
180
181 if (-not $NoDisable) {
182 if ($PSCmdlet.ShouldProcess($user.UserPrincipalName, "Reset Password")) {
183 Set-ADAccountPassword -Identity $user -NewPassword $securePassword -Reset -ErrorAction Stop
184 Write-IncidentLog "Password reset - all Kerberos tickets invalidated" -Level Action
185 Write-IncidentLog "Temporary password generated (32 characters)" -Level Info
186 }
187 }
188}
189catch {
190 Write-IncidentLog "Failed to reset password: $($_.Exception.Message)" -Level Critical
191}
192
193# Revoke O365 sessions (if applicable)
194if (-not $SkipO365) {
195 try {
196 Write-IncidentLog "Checking for Office 365 sessions..." -Level Info
197
198 # Check if Exchange Online module is available
199 if (Get-Module -ListAvailable -Name ExchangeOnlineManagement) {
200 Import-Module ExchangeOnlineManagement -ErrorAction Stop
201
202 # Revoke all refresh tokens
203 if ($PSCmdlet.ShouldProcess($user.UserPrincipalName, "Revoke O365 Sessions")) {
204 Revoke-AzureADUserAllRefreshToken -ObjectId $user.UserPrincipalName -ErrorAction Stop
205 Write-IncidentLog "Office 365 refresh tokens revoked" -Level Action
206 }
207 }
208 else {
209 Write-IncidentLog "ExchangeOnlineManagement module not found - skipping O365 session revocation" -Level Warning
210 }
211 }
212 catch {
213 Write-IncidentLog "Could not revoke O365 sessions: $($_.Exception.Message)" -Level Warning
214 }
215}
216
217Write-Host ""
218
219# Step 4: Check for malicious inbox rules
220Write-Host "[4/8] Checking for Malicious Inbox Rules..." -ForegroundColor Yellow
221Write-Host ""
222
223if (-not $SkipO365) {
224 try {
225 # Check Exchange Online inbox rules
226 $inboxRules = Get-InboxRule -Mailbox $user.UserPrincipalName -ErrorAction SilentlyContinue
227
228 if ($inboxRules) {
229 Write-IncidentLog "Found $($inboxRules.Count) inbox rule(s)" -Level Info
230
231 foreach ($rule in $inboxRules) {
232 Write-Host " Rule: $($rule.Name)" -ForegroundColor Cyan
233 Write-Host " Enabled: $($rule.Enabled)" -ForegroundColor White
234
235 # Check for suspicious patterns
236 $suspicious = $false
237
238 if ($rule.ForwardTo -or $rule.ForwardAsAttachmentTo -or $rule.RedirectTo) {
239 Write-IncidentLog "SUSPICIOUS: Rule '$($rule.Name)' forwards/redirects email" -Level Critical
240 Write-Host " Forwards to: $($rule.ForwardTo)$($rule.ForwardAsAttachmentTo)$($rule.RedirectTo)" -ForegroundColor Red
241 $suspicious = $true
242 }
243
244 if ($rule.DeleteMessage) {
245 Write-IncidentLog "SUSPICIOUS: Rule '$($rule.Name)' deletes messages" -Level Critical
246 $suspicious = $true
247 }
248
249 if ($rule.MoveToFolder -and $rule.MoveToFolder -match "RSS|Junk|Archive") {
250 Write-IncidentLog "SUSPICIOUS: Rule '$($rule.Name)' moves to hidden folder" -Level Warning
251 $suspicious = $true
252 }
253
254 if ($suspicious) {
255 $script:IncidentData.Recommendations += "Investigate and remove inbox rule: $($rule.Name)"
256
257 # Optionally disable the rule
258 if (-not $NoDisable) {
259 if ($PSCmdlet.ShouldProcess($rule.Name, "Disable Inbox Rule")) {
260 Disable-InboxRule -Identity $rule.Identity -Confirm:$false -ErrorAction SilentlyContinue
261 Write-IncidentLog "Disabled suspicious rule: $($rule.Name)" -Level Action
262 }
263 }
264 }
265
266 Write-Host ""
267 }
268 }
269 else {
270 Write-IncidentLog "No inbox rules found" -Level Success
271 }
272 }
273 catch {
274 Write-IncidentLog "Could not check inbox rules: $($_.Exception.Message)" -Level Warning
275 Write-IncidentLog "Ensure Exchange Online module is connected" -Level Info
276 }
277}
278else {
279 Write-IncidentLog "Skipping inbox rule check (on-premises only)" -Level Info
280}
281
282Write-Host ""
283
284# Step 5: Check for email forwarding
285Write-Host "[5/8] Checking Email Forwarding Configuration..." -ForegroundColor Yellow
286Write-Host ""
287
288if (-not $SkipO365) {
289 try {
290 $mailbox = Get-Mailbox -Identity $user.UserPrincipalName -ErrorAction SilentlyContinue
291
292 if ($mailbox) {
293 # Check forwarding settings
294 if ($mailbox.ForwardingAddress -or $mailbox.ForwardingSmtpAddress) {
295 Write-IncidentLog "CRITICAL: Email forwarding is configured!" -Level Critical
296
297 if ($mailbox.ForwardingAddress) {
298 Write-Host " Forwarding Address: $($mailbox.ForwardingAddress)" -ForegroundColor Red
299 }
300
301 if ($mailbox.ForwardingSmtpAddress) {
302 Write-Host " Forwarding SMTP: $($mailbox.ForwardingSmtpAddress)" -ForegroundColor Red
303 }
304
305 Write-Host " Deliver to Mailbox and Forward: $($mailbox.DeliverToMailboxAndForward)" -ForegroundColor Yellow
306
307 $script:IncidentData.Recommendations += "Remove email forwarding configuration"
308
309 # Optionally remove forwarding
310 if (-not $NoDisable) {
311 if ($PSCmdlet.ShouldProcess($user.UserPrincipalName, "Remove Email Forwarding")) {
312 Set-Mailbox -Identity $user.UserPrincipalName -ForwardingAddress $null -ForwardingSmtpAddress $null -ErrorAction Stop
313 Write-IncidentLog "Email forwarding removed" -Level Action
314 }
315 }
316 }
317 else {
318 Write-IncidentLog "No email forwarding configured" -Level Success
319 }
320
321 # Check for delegates
322 $delegates = Get-MailboxPermission -Identity $user.UserPrincipalName | Where-Object {
323 $_.User -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false
324 }
325
326 if ($delegates) {
327 Write-IncidentLog "WARNING: Mailbox delegates found" -Level Warning
328 foreach ($delegate in $delegates) {
329 Write-Host " User: $($delegate.User) - Rights: $($delegate.AccessRights)" -ForegroundColor Yellow
330 }
331 $script:IncidentData.Recommendations += "Review and validate mailbox delegate permissions"
332 }
333 }
334 }
335 catch {
336 Write-IncidentLog "Could not check email forwarding: $($_.Exception.Message)" -Level Warning
337 }
338}
339else {
340 Write-IncidentLog "Skipping email forwarding check (on-premises only)" -Level Info
341}
342
343Write-Host ""
344
345# Step 6: Review recent account activity
346Write-Host "[6/8] Reviewing Recent Account Activity..." -ForegroundColor Yellow
347Write-Host ""
348
349try {
350 # Check recent password changes
351 if ($user.PasswordLastSet) {
352 $daysSincePasswordChange = ((Get-Date) - $user.PasswordLastSet).Days
353 Write-IncidentLog "Password last set: $($user.PasswordLastSet) ($daysSincePasswordChange days ago)" -Level Info
354
355 if ($daysSincePasswordChange -le 7) {
356 Write-IncidentLog "Recent password change detected within last 7 days" -Level Warning
357 }
358 }
359
360 # Check recent account modifications
361 Write-IncidentLog "Checking recent account modifications..." -Level Info
362
363 if ($user.WhenChanged) {
364 Write-Host " Last Modified: $($user.WhenChanged)" -ForegroundColor Cyan
365 }
366
367 # Get account lockout events
368 $lockoutEvents = Get-WinEvent -FilterHashtable @{
369 LogName = 'Security'
370 ID = 4740
371 StartTime = (Get-Date).AddDays(-7)
372 } -ErrorAction SilentlyContinue | Where-Object {
373 $_.Properties[0].Value -eq $user.SamAccountName
374 } | Select-Object -First 10
375
376 if ($lockoutEvents) {
377 Write-IncidentLog "Found $($lockoutEvents.Count) account lockout event(s) in last 7 days" -Level Warning
378 foreach ($event in $lockoutEvents | Select-Object -First 5) {
379 Write-Host " Lockout at: $($event.TimeCreated)" -ForegroundColor Yellow
380 }
381 }
382
383 # Check for recent logon failures
384 $failedLogons = Get-WinEvent -FilterHashtable @{
385 LogName = 'Security'
386 ID = 4625
387 StartTime = (Get-Date).AddDays(-7)
388 } -ErrorAction SilentlyContinue | Where-Object {
389 $_.Properties[5].Value -eq $user.SamAccountName
390 } | Select-Object -First 10
391
392 if ($failedLogons) {
393 Write-IncidentLog "Found $($failedLogons.Count) failed logon attempt(s) in last 7 days" -Level Warning
394
395 # Extract unique source IPs
396 $sourceIPs = $failedLogons | ForEach-Object {
397 $_.Properties[19].Value
398 } | Select-Object -Unique
399
400 Write-Host " Failed logons from IP addresses:" -ForegroundColor Yellow
401 $sourceIPs | ForEach-Object {
402 Write-Host " $_" -ForegroundColor Yellow
403 }
404
405 $script:IncidentData.Recommendations += "Investigate failed logon source IPs: $($sourceIPs -join ', ')"
406 }
407
408 # Check successful logons with unusual locations (Event ID 4624)
409 $recentLogons = Get-WinEvent -FilterHashtable @{
410 LogName = 'Security'
411 ID = 4624
412 StartTime = (Get-Date).AddDays(-7)
413 } -ErrorAction SilentlyContinue | Where-Object {
414 $_.Properties[5].Value -eq $user.SamAccountName
415 } | Select-Object -First 10
416
417 if ($recentLogons) {
418 Write-IncidentLog "Found $($recentLogons.Count) successful logon(s) in last 7 days" -Level Info
419 Write-Host " Recent successful logons:" -ForegroundColor Cyan
420 $recentLogons | Select-Object -First 5 | ForEach-Object {
421 Write-Host " $($_.TimeCreated) - Logon Type: $($_.Properties[8].Value)" -ForegroundColor White
422 }
423 }
424}
425catch {
426 Write-IncidentLog "Could not retrieve some activity logs: $($_.Exception.Message)" -Level Warning
427}
428
429Write-Host ""
430
431# Step 7: Check group memberships
432Write-Host "[7/8] Reviewing Group Memberships..." -ForegroundColor Yellow
433Write-Host ""
434
435try {
436 $groups = Get-ADPrincipalGroupMembership -Identity $user | Select-Object Name, SamAccountName
437
438 Write-IncidentLog "User is member of $($groups.Count) group(s)" -Level Info
439
440 # Check for privileged groups
441 $privilegedGroups = @(
442 'Domain Admins', 'Enterprise Admins', 'Schema Admins', 'Account Operators',
443 'Backup Operators', 'Server Operators', 'Administrators'
444 )
445
446 $userPrivilegedGroups = $groups | Where-Object { $privilegedGroups -contains $_.Name }
447
448 if ($userPrivilegedGroups) {
449 Write-IncidentLog "CRITICAL: User has privileged group membership!" -Level Critical
450 $userPrivilegedGroups | ForEach-Object {
451 Write-Host " Privileged Group: $($_.Name)" -ForegroundColor Red
452 }
453 $script:IncidentData.Recommendations += "Immediately review and potentially remove privileged group memberships"
454 $script:IncidentData.Recommendations += "Audit all actions taken by this privileged account"
455 $script:IncidentData.Recommendations += "Check for unauthorized changes to AD objects"
456 }
457 else {
458 Write-IncidentLog "No privileged group memberships found" -Level Success
459 }
460
461 # Display all groups in verbose mode
462 if ($VerbosePreference -eq 'Continue') {
463 Write-Host " All groups:" -ForegroundColor Cyan
464 $groups | ForEach-Object {
465 Write-Host " - $($_.Name)" -ForegroundColor White
466 }
467 }
468}
469catch {
470 Write-IncidentLog "Could not retrieve group memberships: $($_.Exception.Message)" -Level Warning
471}
472
473Write-Host ""
474
475# Step 8: Collect forensic data
476Write-Host "[8/8] Collecting Forensic Data..." -ForegroundColor Yellow
477Write-Host ""
478
479$forensicData = @{
480 UserDetails = $user | Select-Object UserPrincipalName, SamAccountName, DisplayName, EmailAddress,
481 Enabled, PasswordLastSet, LastLogonDate, WhenCreated, WhenChanged,
482 LockedOut, Description, Department, Title, Manager
483 GroupMemberships = $groups
484 ActionsTaken = $script:IncidentData.Actions
485 FindingsAndAlerts = $script:IncidentData.Findings
486 Recommendations = $script:IncidentData.Recommendations
487}
488
489Write-IncidentLog "Forensic data collected" -Level Success
490Write-Host ""
491
492# Generate incident report
493Write-Host "========================================" -ForegroundColor Cyan
494Write-Host " Incident Response Summary" -ForegroundColor Cyan
495Write-Host "========================================" -ForegroundColor Cyan
496Write-Host ""
497
498Write-Host "Target User: $($user.UserPrincipalName)" -ForegroundColor White
499Write-Host "Incident Time: $($script:IncidentData.Timestamp)" -ForegroundColor White
500Write-Host ""
501
502Write-Host "Actions Taken: $($script:IncidentData.Actions.Count)" -ForegroundColor Green
503$script:IncidentData.Actions | ForEach-Object {
504 Write-Host " - $_" -ForegroundColor Green
505}
506Write-Host ""
507
508Write-Host "Findings and Alerts: $($script:IncidentData.Findings.Count)" -ForegroundColor Yellow
509$script:IncidentData.Findings | ForEach-Object {
510 Write-Host " - $_" -ForegroundColor Yellow
511}
512Write-Host ""
513
514Write-Host "Recommendations:" -ForegroundColor Cyan
515if ($script:IncidentData.Recommendations.Count -gt 0) {
516 $script:IncidentData.Recommendations | Select-Object -Unique | ForEach-Object {
517 Write-Host " - $_" -ForegroundColor Cyan
518 }
519}
520else {
521 Write-Host " - Complete forensic analysis of user activity" -ForegroundColor Cyan
522 Write-Host " - Review related systems for indicators of compromise" -ForegroundColor Cyan
523 Write-Host " - Notify affected parties per incident response plan" -ForegroundColor Cyan
524}
525Write-Host ""
526
527# Export HTML report
528Write-Host "Generating incident report..." -ForegroundColor Cyan
529
530$html = @"
531<!DOCTYPE html>
532<html>
533<head>
534 <title>Account Compromise Incident Report</title>
535 <style>
536 body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; }
537 .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
538 h1 { color: #d9534f; border-bottom: 3px solid #d9534f; padding-bottom: 10px; }
539 h2 { color: #333; border-bottom: 2px solid #5bc0de; padding-bottom: 5px; margin-top: 30px; }
540 h3 { color: #555; margin-top: 20px; }
541 .critical { color: #d9534f; font-weight: bold; }
542 .warning { color: #f0ad4e; font-weight: bold; }
543 .success { color: #5cb85c; font-weight: bold; }
544 .info { color: #333; }
545 .timestamp { color: #666; font-size: 0.9em; background: #f9f9f9; padding: 10px; border-left: 4px solid #5bc0de; }
546 ul { line-height: 1.8; }
547 li { margin: 8px 0; }
548 table { width: 100%; border-collapse: collapse; margin: 15px 0; }
549 th { background: #5bc0de; color: white; padding: 12px; text-align: left; }
550 td { padding: 10px; border-bottom: 1px solid #ddd; }
551 tr:hover { background: #f9f9f9; }
552 .section { background: #f9f9f9; padding: 15px; margin: 15px 0; border-radius: 5px; }
553 .alert-box { background: #fcf8e3; border-left: 5px solid #f0ad4e; padding: 15px; margin: 15px 0; }
554 </style>
555</head>
556<body>
557 <div class="container">
558 <h1>🚨 Account Compromise Incident Report</h1>
559
560 <div class="timestamp">
561 <strong>Incident Response Timestamp:</strong> $($script:IncidentData.Timestamp)<br>
562 <strong>Target User:</strong> $($user.UserPrincipalName)<br>
563 <strong>Display Name:</strong> $($user.DisplayName)<br>
564 <strong>Response Mode:</strong> $(if($NoDisable){'Investigation Only'}else{'Full Lockdown'})
565 </div>
566
567 <h2>User Account Details</h2>
568 <table>
569 <tr><th>Property</th><th>Value</th></tr>
570 <tr><td>SAM Account Name</td><td>$($user.SamAccountName)</td></tr>
571 <tr><td>Email Address</td><td>$($user.EmailAddress)</td></tr>
572 <tr><td>Department</td><td>$($user.Department)</td></tr>
573 <tr><td>Title</td><td>$($user.Title)</td></tr>
574 <tr><td>Account Enabled</td><td>$(if($user.Enabled){'<span class="critical">YES</span>'}else{'<span class="success">NO (Disabled)</span>'})</td></tr>
575 <tr><td>Password Last Set</td><td>$($user.PasswordLastSet)</td></tr>
576 <tr><td>Last Logon</td><td>$($user.LastLogonDate)</td></tr>
577 <tr><td>Account Created</td><td>$($user.WhenCreated)</td></tr>
578 <tr><td>Last Modified</td><td>$($user.WhenChanged)</td></tr>
579 <tr><td>Account Locked</td><td>$(if($user.LockedOut){'<span class="critical">YES</span>'}else{'NO'})</td></tr>
580 </table>
581
582 <h2>Actions Taken</h2>
583 $(if ($script:IncidentData.Actions.Count -gt 0) {
584 "<ul class='success'>" +
585 ($script:IncidentData.Actions | ForEach-Object { "<li>$_</li>" }) -join "`n" +
586 "</ul>"
587 } else {
588 "<p class='info'>No automated actions taken (investigation mode)</p>"
589 })
590
591 <h2>Critical Findings and Alerts</h2>
592 $(if ($script:IncidentData.Findings.Count -gt 0) {
593 "<div class='alert-box'><ul>" +
594 ($script:IncidentData.Findings | ForEach-Object { "<li class='warning'>$_</li>" }) -join "`n" +
595 "</ul></div>"
596 } else {
597 "<p class='success'>No critical findings detected during automated checks</p>"
598 })
599
600 <h2>Group Memberships</h2>
601 <table>
602 <tr><th>Group Name</th><th>SAM Account Name</th></tr>
603 $(foreach ($group in $groups) {
604 "<tr><td>$($group.Name)</td><td>$($group.SamAccountName)</td></tr>"
605 })
606 </table>
607
608 <h2>Recommendations</h2>
609 <div class="section">
610 <ul>
611 $(if ($script:IncidentData.Recommendations.Count -gt 0) {
612 ($script:IncidentData.Recommendations | Select-Object -Unique | ForEach-Object { "<li>$_</li>" }) -join "`n"
613 } else {
614 @"
615 <li>Complete comprehensive forensic analysis of user activity</li>
616 <li>Review system and application logs for indicators of compromise</li>
617 <li>Check for lateral movement or privilege escalation attempts</li>
618 <li>Notify affected parties per incident response plan</li>
619 <li>Document all findings in incident management system</li>
620 <li>Conduct post-incident review and update security controls</li>
621"@
622 })
623 </ul>
624 </div>
625
626 <h2>Next Steps</h2>
627 <div class="section">
628 <ol>
629 <li><strong>Immediate:</strong> Verify account lockdown is complete</li>
630 <li><strong>Short-term:</strong> Complete forensic investigation (0-4 hours)</li>
631 <li><strong>Medium-term:</strong> Identify root cause and attack vector (4-24 hours)</li>
632 <li><strong>Long-term:</strong> Implement preventative measures (1-7 days)</li>
633 <li><strong>Follow-up:</strong> User security training and credential reset under supervision</li>
634 <li><strong>Documentation:</strong> Update incident response documentation with lessons learned</li>
635 </ol>
636 </div>
637
638 <h2>Investigation Checklist</h2>
639 <div class="section">
640 <ul>
641 <li>☐ Review authentication logs from all systems</li>
642 <li>☐ Check VPN/remote access logs for unusual connections</li>
643 <li>☐ Analyze email logs for suspicious activity</li>
644 <li>☐ Review file access and modification logs</li>
645 <li>☐ Check for data exfiltration indicators</li>
646 <li>☐ Interview user about compromise timeline and indicators</li>
647 <li>☐ Scan user devices for malware</li>
648 <li>☐ Review other accounts for similar compromise indicators</li>
649 <li>☐ Notify management and stakeholders per IR plan</li>
650 <li>☐ Document all findings and actions in ticket system</li>
651 </ul>
652 </div>
653
654 <hr style="margin: 30px 0;">
655 <p style="text-align: center; color: #666; font-size: 0.9em;">
656 Generated by Account Compromise Response Script | glyph.sh<br>
657 Report Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
658 </p>
659 </div>
660</body>
661</html>
662"@
663
664try {
665 $html | Out-File -FilePath $ReportPath -Encoding UTF8 -Force
666 Write-Host "Incident report saved to: $ReportPath" -ForegroundColor Green
667}
668catch {
669 Write-IncidentLog "Failed to save report: $($_.Exception.Message)" -Level Warning
670}
671
672Write-Host ""
673Write-Host "========================================" -ForegroundColor Cyan
674Write-Host "Incident response complete!" -ForegroundColor Cyan
675Write-Host "========================================" -ForegroundColor Cyan
676Write-Host ""
677
678# Return forensic data object
679return $forensicDataUsage
Full Incident Response
Perform complete lockdown and forensic collection:
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith"Investigation Mode
Collect forensics without disabling the account (for active monitoring):
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith@domain.com" -NoDisableOn-Premises Only
Skip Office 365 checks for on-premises environments:
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith" -SkipO365Custom Report Location
Specify custom path for incident report:
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith" -ReportPath "C:\IncidentReports\Compromise_JSmith.html"What It Does
1. Account Identification
- Retrieves complete user account details from Active Directory
- Displays current account status, last logon, and password age
- Validates user exists before proceeding
2. Account Lockdown
- Disables user account immediately to prevent further access
- Updates account description to note compromise and timestamp
- Prevents any new authentication attempts
3. Session Revocation
- Resets password to invalidate all Kerberos tickets
- Generates strong 32-character temporary password
- Revokes Office 365 refresh tokens (if O365 module available)
- Forces immediate logout from all active sessions
4. Malicious Inbox Rules Detection
Checks for suspicious inbox rules including:
- Email forwarding rules (external addresses)
- Auto-delete rules (covering tracks)
- Hidden folder rules (RSS, Junk, Archive)
- Automatically disables suspicious rules found
5. Email Forwarding Check
- Verifies mailbox forwarding configuration
- Checks both internal and external SMTP forwarding
- Reviews mailbox delegate permissions
- Removes unauthorized forwarding if found
6. Activity Review
Analyzes recent account activity:
- Recent password changes (potentially by attacker)
- Account lockout events (brute force indicators)
- Failed logon attempts with source IPs
- Successful logons and authentication patterns
- Account modification history
7. Privilege Analysis
- Reviews all group memberships
- Flags privileged groups (Domain Admins, Enterprise Admins, etc.)
- Identifies potential for lateral movement or privilege escalation
- Recommends immediate audit if privileged
8. Forensic Data Collection
- Compiles comprehensive incident report
- Documents all actions taken
- Lists all findings and alerts
- Provides actionable recommendations
- Exports HTML report for documentation
Response Actions Taken
| Action | When | Impact |
|---|---|---|
| Account Disable | Always (unless -NoDisable) | Immediate - blocks all access |
| Password Reset | Always (unless -NoDisable) | Immediate - invalidates tickets |
| O365 Token Revocation | If O365 detected | Immediate - logs out cloud sessions |
| Inbox Rule Disable | If suspicious rules found | Immediate - stops forwarding |
| Forwarding Removal | If forwarding configured | Immediate - stops email theft |
| Forensic Collection | Always | No impact - read-only |
Incident Report
The generated HTML report includes:
- Executive Summary: Incident timeline and target details
- User Account Details: Complete account profile
- Actions Taken: Automated response actions with timestamps
- Critical Findings: Security alerts and suspicious activities
- Group Memberships: All AD groups including privileged
- Recommendations: Specific next steps based on findings
- Investigation Checklist: Comprehensive follow-up tasks
Integration with Incident Response
Automated Alerting
Trigger from SIEM or security alerts:
1# From security alert system
2$user = $Alert.TargetUser
3.\Invoke-CompromiseResponse.ps1 -UserIdentity $user -ReportPath "\\IR\Reports\$user-$(Get-Date -Format 'yyyyMMdd').html"
4
5# Email report to SOC
6Send-MailMessage -From "ir-automation@domain.com" `
7 -To "soc@domain.com" `
8 -Subject "URGENT: Account Compromise Response - $user" `
9 -Attachments $reportPath `
10 -Body "Automated response completed for $user. See attached report." `
11 -SmtpServer "mail.domain.com"Ticketing System Integration
1# Create incident ticket
2$result = .\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith"
3
4New-Ticket -Title "Account Compromise: jsmith" `
5 -Priority "Critical" `
6 -Description "Automated response completed. $($result.ActionsTaken.Count) actions taken." `
7 -AssignedTo "Security Team"Post-Response Checklist
After running this script, complete the following:
Immediate (0-4 hours)
- Review all findings in incident report
- Verify account is fully disabled
- Check for unauthorized changes made by compromised account
- Review logs from systems accessed by user
- Scan user devices for malware
- Interview user to determine compromise vector
Short-term (4-24 hours)
- Complete forensic analysis of user activity
- Identify initial compromise vector
- Check for lateral movement or data exfiltration
- Review and strengthen related accounts
- Notify affected parties per compliance requirements
- Document all findings in incident management system
Long-term (1-7 days)
- Conduct root cause analysis
- Implement preventative controls
- Re-enable account with new credentials (if appropriate)
- Provide security awareness training to user
- Update incident response procedures
- Perform post-incident review
Prerequisites
Required Modules
1# Active Directory (included in RSAT)
2Import-Module ActiveDirectory
3
4# Exchange Online (for O365 checks)
5Install-Module -Name ExchangeOnlineManagement
6Connect-ExchangeOnline
7
8# Azure AD (for O365 session revocation)
9Install-Module -Name AzureAD
10Connect-AzureADRequired Permissions
- Domain Administrator or equivalent for AD operations
- Exchange Administrator for mailbox and inbox rule management
- Global Administrator or User Administrator for Azure AD operations
- Security Auditor for event log access
Common Scenarios
Phishing Victim
User clicked phishing link and entered credentials:
1# Full response with O365 checks
2.\Invoke-CompromiseResponse.ps1 -UserIdentity "phishing-victim@domain.com"
3
4# Check email logs for sent messages in last 24 hours
5# Review inbox rules created by attacker
6# Analyze email forwarding to external addressesBrute Force Success
Account compromised via password spray attack:
1# Review failed logon events
2.\Invoke-CompromiseResponse.ps1 -UserIdentity "bruteforce-target" -NoDisable
3
4# Analyze source IPs from failed logons
5# Check if other accounts targeted from same IPs
6# Implement IP blocking at firewallPrivileged Account Compromise
Domain admin or similar privileged account:
1# CRITICAL - immediate response required
2.\Invoke-CompromiseResponse.ps1 -UserIdentity "domain-admin"
3
4# URGENT additional steps:
5# 1. Audit all AD changes in last 24-48 hours
6# 2. Check for new privileged accounts created
7# 3. Review FSMO role changes
8# 4. Scan for malware on domain controllers
9# 5. Consider KRBTGT password reset
10# 6. Engage incident response team immediatelyInsider Threat
Suspicious activity from legitimate user:
1# Investigation mode - don't alert user
2.\Invoke-CompromiseResponse.ps1 -UserIdentity "suspicious-insider" -NoDisable
3
4# Collect evidence while maintaining access
5# Review data access patterns
6# Check for data exfiltration indicators
7# Coordinate with HR and legalLimitations
- Cloud-only Accounts: Azure AD-only accounts require different approach
- MFA Tokens: Script cannot revoke hardware token registrations
- Mobile Devices: May need separate MDM wipe commands
- Third-party Apps: OAuth tokens for external apps not revoked
- Cached Credentials: Local cached credentials may still work offline
- Certificate Authentication: Smart card certificates require separate revocation
Best Practices
- Act Fast: Run within minutes of detecting compromise
- Document Everything: Save all reports and logs
- Follow Up: Automation is first step, not complete response
- Test Regularly: Practice on test accounts quarterly
- Update Runbooks: Incorporate lessons learned
- Communicate: Notify stakeholders per IR plan
- Verify: Always confirm account is truly compromised before disabling
See Also
- Incident Response Runbook
- Active Directory Security
- Email Security Best Practices
- Forensic Investigation Guide
Download
1# Download the script
2Invoke-WebRequest -Uri "https://glyph.sh/scripts/Invoke-CompromiseResponse.ps1" -OutFile "Invoke-CompromiseResponse.ps1"
3
4# Run full incident response
5.\Invoke-CompromiseResponse.ps1 -UserIdentity "compromised-user"
6
7# Investigation mode only
8.\Invoke-CompromiseResponse.ps1 -UserIdentity "suspicious-user" -NoDisable