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

Lang: powershell
  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 $forensicData

Usage

Full Incident Response

Perform complete lockdown and forensic collection:

Lang: powershell
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith"

Investigation Mode

Collect forensics without disabling the account (for active monitoring):

Lang: powershell
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith@domain.com" -NoDisable

On-Premises Only

Skip Office 365 checks for on-premises environments:

Lang: powershell
1.\Invoke-CompromiseResponse.ps1 -UserIdentity "jsmith" -SkipO365

Custom Report Location

Specify custom path for incident report:

Lang: powershell
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

ActionWhenImpact
Account DisableAlways (unless -NoDisable)Immediate - blocks all access
Password ResetAlways (unless -NoDisable)Immediate - invalidates tickets
O365 Token RevocationIf O365 detectedImmediate - logs out cloud sessions
Inbox Rule DisableIf suspicious rules foundImmediate - stops forwarding
Forwarding RemovalIf forwarding configuredImmediate - stops email theft
Forensic CollectionAlwaysNo 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:

Lang: powershell
 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

Lang: powershell
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

Lang: powershell
 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-AzureAD

Required 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:

Lang: powershell
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 addresses

Brute Force Success

Account compromised via password spray attack:

Lang: powershell
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 firewall

Privileged Account Compromise

Domain admin or similar privileged account:

Lang: powershell
 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 immediately

Insider Threat

Suspicious activity from legitimate user:

Lang: powershell
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 legal

Limitations

  • 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

  1. Act Fast: Run within minutes of detecting compromise
  2. Document Everything: Save all reports and logs
  3. Follow Up: Automation is first step, not complete response
  4. Test Regularly: Practice on test accounts quarterly
  5. Update Runbooks: Incorporate lessons learned
  6. Communicate: Notify stakeholders per IR plan
  7. Verify: Always confirm account is truly compromised before disabling

See Also

Download

Lang: powershell
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