OVERVIEW
This playbook covers the end-to-end phishing incident response workflow from first alert to ticket closure. Use PHISHOPS alongside this document to track case state and collect IOCs.
| Level | Criteria | Response SLA | Owner |
|---|---|---|---|
| P1 CRITICAL | Link clicked or attachment opened, auth anomaly confirmed, active session compromise | 15 min | L3 + IR team |
| P2 HIGH | Delivered to exec/finance/IT, credential harvesting page, targeted spear phish | 30 min | L2 |
| P3 MEDIUM | Delivered, mass campaign, no confirmed click, low-quality payload | 2 hr | L1 |
| P4 LOW | Quarantined or blocked before delivery, no user exposure | 4 hr | L1 |
INITIAL TRIAGE
Establish context before any analysis. Collect envelope fields, confirm auth results at a high level, assess scope, and set initial severity.
| Signal | Pass means | Fail / Missing means | Next action |
|---|---|---|---|
| SPF | Sender IP is authorized by the domain's DNS record | IP not listed in SPF, or no SPF record exists | Flag; cross-check X-Originating-IP in headers |
| DKIM | Cryptographic signature valid and domain-aligned | Signature invalid, missing, or domain-misaligned | Flag; check for email replay or modification |
| DMARC | Passes at least one of SPF or DKIM with alignment | Both fail, or no DMARC record on sender domain | High suspicion; note policy: none/quarantine/reject |
HEADER ANALYSIS
Trace the full relay chain, verify authentication results line by line, and identify the true sending IP. Anomalies here often confirm spoofing or infrastructure abuse.
| Result | Meaning | Risk level |
|---|---|---|
| SPF pass | Sending IP matches the domain's SPF record | Low (but can still be spoofed via subdomain) |
| SPF softfail (~all) | IP not authorized but owner prefers no hard block | Medium, flag for review |
| SPF fail (-all) | IP explicitly not authorized, should be rejected | High |
| DKIM pass | Signature valid, message unmodified in transit | Low |
| DKIM fail / none | No signature, or signature invalid | High if combined with SPF fail |
| DMARC pass | At least one aligned mechanism passes | Low |
| DMARC fail | Neither SPF nor DKIM aligns with From domain | High, especially with p=reject policy |
URL ANALYSIS
Extract, defang, and triage all URLs from the email body, headers, and any HTML source. Analyze redirect chains and domain infrastructure without visiting links directly.
http with hxxp and wrap dots: domain[.]com.| Signal | Threshold | Interpretation |
|---|---|---|
| Domain age | < 30 days | High risk. Newly registered domains are a primary indicator for phishing infrastructure. |
| Domain age | 30-90 days | Medium risk. Combine with other signals before escalating. |
| Typosquatting | 1-3 char difference from known brand | High risk if targeting executives or vendor domains. |
| Free hosting TLD | .xyz, .top, .click, .tk, .ml | Medium-high. Common in commodity phishing kits. |
| No valid cert | Self-signed or expired TLS | Medium. Legitimate phishing often uses valid Let's Encrypt certs. |
| Landing page type | Login form, fake O365/Google | High confidence credential harvester. |
ATTACHMENT ANALYSIS
Hash-check before detonation. Detonate in an isolated sandbox. Extract all process, network, and file IOCs from the report before any other action.
| File type | Primary risk | Common technique |
|---|---|---|
| .docm / .xlsm / .pptm | VBA macro execution | PowerShell dropper, LOLBAS abuse |
| Embedded JavaScript, URI redirect | Credential link, JS shellcode | |
| .zip / .iso / .img | Container bypass of gateway AV | LNK shortcut, embedded EXE |
| .ps1 / .vbs / .js | Direct script execution | Downloader, keylogger, RAT |
| .exe / .dll | Binary execution | Loader, stealer, ransomware dropper |
| .lnk | Shortcut pointing to remote payload | HTML smuggling, ISO delivery |
IDENTITY & IMPACT
Determine if the target user interacted with the phish. Review Okta and gateway logs for clicks, auth anomalies, MFA bypass attempts, and signs of active session compromise.
| Indicator | What it suggests | Action |
|---|---|---|
| New session from different country after auth | Session token theft (AiTM phishing proxy) | Revoke all sessions immediately, force re-auth |
| New session from VPN/proxy/Tor ASN | Attacker using anonymizing infrastructure | Block the ASN range, revoke session |
| Admin role assignment in session | Privilege escalation post-compromise | P1, loop in IR immediately |
| Mail forwarding rule created | Persistence for ongoing data exfiltration | Remove rule, document all forwarded mail |
| MFA method change | Attacker locking out the legitimate user | Disable account, escalate to IR |
CONTAINMENT
Execute blocking, revocation, and evidence preservation in order. Document all IOCs before any blocking action. All steps require analyst approval before execution.
ESCALATION
Use these criteria to determine when to escalate and at what tier. If multiple triggers fire simultaneously, default to the highest applicable tier.
| Trigger | Tier | Action required |
|---|---|---|
| Email blocked by gateway, no delivery | L1 | Document, block sender, close as P4. |
| Email delivered, no click confirmed, low-quality payload | L1 | Block IOCs, recall email, close as P3. |
| Email delivered, targeted recipient, suspicious indicators | L2 | Full analysis, identity check, notify manager if exec-targeted. |
| Credential harvesting page confirmed, delivery scope unknown | L2 | Full URL analysis, identity check across all recipients. |
| Click confirmed, no auth anomaly yet | L2 | Immediate identity check, suspend account if in doubt, escalate to L3. |
| Auth anomaly detected post-click | L3 | Suspend account, revoke sessions, loop in IR team, treat as P1. |
| MFA bypassed or downgraded | L3 | Immediate account lock, mandatory IR escalation. |
| Credential submission confirmed | L3 | Full compromise response. IR team ownership. Executive notification may apply. |
| Admin role assigned or mail forwarding rule created post-auth | L3 | P1. IR team. Possible breach notification scope assessment. |
| Malware payload executed on endpoint | L3 | Isolate endpoint, IR team, EDR containment, parallel malware analysis. |
Subject: [P1 ESCALATION] Phishing - Credential Compromise - [USER] - [TICKET ID] Team, Escalating a confirmed phishing incident for IR ownership. User: [user@company.com] Timestamp of email receipt: [YYYY-MM-DD HH:MM UTC] Confirmed action: [link clicked / credentials submitted / auth anomaly] Auth anomaly detail: [impossible travel / MFA bypass / new session] Current status: [account suspended / sessions revoked / pending] IOCs collected: [X senders, Y IPs, Z URLs, W hashes] Evidence stored: [ticket ID / SharePoint path] Requesting IR team take ownership for scope assessment and breach review. SOC Analyst: [name] Ticket: [ID]
QUERY TEMPLATES
Pre-built queries for each investigation phase. Replace bracketed values with case-specific data before running. All queries are reference templates, not production-ready rules.
EmailEvents
| where Timestamp > ago(24h)
| where SenderMailFromDomain =~ "[SENDER_DOMAIN]"
| project Timestamp, SenderFromAddress, RecipientEmailAddress,
Subject, DeliveryAction, ThreatTypes, EmailDirection
UrlClickEvents | where Timestamp > ago(48h) | where Url contains "[PHISHING_DOMAIN]" | project Timestamp, AccountUpn, Url, ActionType, IsClickedThrough, IPAddress
SigninLogs
| where TimeGenerated between (
datetime([START_TIME]) .. datetime([END_TIME]))
| where UserPrincipalName =~ "[USER@COMPANY.COM]"
| where ResultType != "0"
| project TimeGenerated, UserPrincipalName, IPAddress,
Location, AppDisplayName, ResultType, ResultDescription
| sort by TimeGenerated asc
SigninLogs
| where TimeGenerated > ago(4h)
| where UserPrincipalName =~ "[USER@COMPANY.COM]"
| extend Country = tostring(LocationDetails.countryOrRegion)
| where Country !in ("[EXPECTED_COUNTRY_1]", "[EXPECTED_COUNTRY_2]")
| project TimeGenerated, UserPrincipalName, IPAddress,
Country, AppDisplayName, UserAgent
CloudAppEvents
| where Timestamp > ago(24h)
| where ActionType in ("New-InboxRule","Set-InboxRule")
| where AccountDisplayName =~ "[USER@COMPANY.COM]"
| extend RuleDetails = todynamic(RawEventData)
| project Timestamp, AccountDisplayName, ActionType,
RuleDetails.Parameters, IPAddress
index=okta sourcetype=OktaIM2:log actor.alternateId="[USER@COMPANY.COM]"
| eval event_time=strftime(_time,"%Y-%m-%dT%H:%M:%SZ")
| table event_time actor.alternateId client.ipAddress
client.geographicalContext.country
outcome.result displayMessage authenticationContext.authenticationStep
| sort -_time
index=email sourcetype=proofpoint sender_domain="[SENDER_DOMAIN]" | table _time sender recipient subject action_dispo threatsInfoMap | sort -_time
index=proxy src_ip="[ENDPOINT_IP]" | search url="*[PHISHING_DOMAIN]*" | table _time src_ip dest_host url http_method http_status bytes_out | sort -_time
config timeframe = 4h;
dataset = xdr_data
| filter event_type = "AUTH"
and actor_effective_username = "[USER@COMPANY.COM]"
| fields _time, actor_effective_username, action_remote_ip,
action_country, outcome, event_sub_type
| sort desc _time
config timeframe = 24h;
dataset = xdr_data
| filter event_type = "NETWORK"
and (action_remote_ip = "[C2_IP]"
or dns_query_name contains "[PHISHING_DOMAIN]")
| fields _time, agent_hostname, actor_process_image_name,
action_remote_ip, dns_query_name, action_remote_port
| sort desc _time