Email Security Operations
Definition
Section titled “Definition”Email security operations is the running defense of the attack channel that never stops: authentication (SPF, DKIM, DMARC — making it provable that mail from your domain is from you), gateway filtering (content, reputation, sandbox detonation), mailbox-level controls (banner warnings, external-sender tagging, time-of-click URL defense), and the human loop — user reports, SOC triage (BT1), and hunt-driven retro-sweeping when a campaign surfaces late. Inbound, the discipline is triage at scale: thousands of suspicious messages, each needing verdict (malicious / spam / benign-suspicious), scope (who else received it), and response (purge, block, hunt). Outbound, the discipline is anti-spoofing hygiene: DMARC enforcement so adversaries cannot wear your domain into your customers’ inboxes — because a spoofed invoice from your CFO domain is your incident even if it never touched your network. The craft’s center: email is the initial-access channel for the majority of real intrusions (T2’s emulation work starts here for a reason), and the defense that works is layered machinery — authentication that fails closed, detonation that runs before the click, triage that measures itself — not the annual awareness video.
Why It Matters
Section titled “Why It Matters”Phishing is the cheapest reliable way into any organization, and the economics never stop: commodity credential kits rent for pocket change, and targeted teams (M12’s maldoc tradecraft, BEC operators) craft per-victim lures that no static filter catches. The costs land across three separate balances. Direct compromise: a stolen session cookie or a macro-enabled document converts to initial access, then everything downstream (BT4’s incident process) is expensive. Business email compromise: a convincing spoof of your executive or billing domain redirects one wire transfer — no malware, no indicator, pure fraud. Brand damage: your customers receive convincing phishing wearing your identity, and their trust drains regardless of whether your own perimeter was touched.
The defensive failure modes repeat across every program. Authentication gaps: DMARC at p=none (monitor-only forever) means every spoof of your domain delivers cleanly to customers and partners. Gateway monoculture faith: one vendor’s verdict treated as ground truth, when targeted lures routinely sail through any single engine on first sight. Triage debt: user-reported phishing sitting in a queue for days — each unexamined report is an unmeasured incident. No retro-hunt: when intel (BT10) surfaces a new campaign IOC, nobody sweeps the last 30 days of mail for it, so the message that arrived before the rule is still sitting in fifty inboxes. And banner fatigue: warnings so frequent nobody reads them, so the one dangerous external email looks identical to the forty benign ones.
Detailed Explanation
Section titled “Detailed Explanation” attacker mail | v+-----------------+ fail +------------------+| MX / GATEWAY |------------>| QUARANTINE || SPF, DKIM, DMARC| | (quarantine / || reputation, AV, | | reject at SMTP) || sandbox detona- | +------------------+| tion of attach/URL+--------+--------+ | pass (with verdict + banners) v+-----------------+ user clicks "report phish"| MAILBOX |--------------------------+| banners, external tag, || time-of-click URL rewrite |+--------+-----------------------------------+ | delivery v v +------------------++-----------------+ | SOC TRIAGE (BT1) || victim / user | | verdict + scope || clicks / replies| | purge + block |+-----------------+ +--------+---------+ (detector of last resort) | v +--------------------------+ | RETRO-HUNT + FEEDBACK | | sweep 30 days for IOCs, | | tune rules (BT9), report | +--------------------------+Authentication is the floor. SPF validates the envelope sender’s IP against the domain’s DNS record; DKIM validates a cryptographic signature over headers and body (surviving forwarding, which SPF does not); DMARC ties them to the visible From: domain — alignment — and tells receivers what to do on failure via policy: p=none (deliver, report), p=quarantine, p=reject. DMARC aggregate reports (rua) land daily as XML and tell you exactly who is sending as your domain and whether it passes — dmarchealth (below) reads them. The rollout discipline matters more than the record: monitor, analyze, gradually tighten (sp= for subdomains, pct= for partial enforcement), reject.ARC preserves authentication verdicts across legitimate forwarders, and MTA-STS plus TLS-RPT harden the transport layer itself.
Gateway filtering layers content rules, sender reputation, attachment detonation (run the macro in a sandbox, watch what it does — M12’s analysis applied pre-delivery), and URL rewriting with time-of-click checks. Every engine has first-seen misses against targeted lures, which is why verdicts from multiple engines, plus your own retro-hunt capability, matter more than any single vendor’s percentage.
The human loop is a detection instrument, not a training certificate: the report-phish button must route straight into SOC triage with full headers, and response time is a first-class metric. Retro-hunting closes the timing gap — detection rules improve after delivery, so the operational question is always “who already has it”: sweep the last 30 days of message tracking for the campaign’s sender, subject cluster, and attachment hash, then purge with soft-delete across all mailboxes (BT1’s scope-then-act discipline).
Deep dive: why DMARC rollout takes months and why you must not rush it
Rejecting on day one breaks your own legitimate mail: marketing platforms, ticket systems, CRM senders, and departmental SMTP relays all send as your domain, and until each one signs with DKIM aligned to your From: domain (or is enumerated in SPF within the 10-lookup limit), p=reject bounces real business email into silence. The aggregate reports are the map: they enumerate every sending source on earth that uses your domain, including the adversary’s. The safe sequence is p=none for at least a full month (capture a complete sending-source inventory and fix the legit senders), then p=quarantine with pct=100 after zero unexplained failures for two consecutive weeks, then p=reject — monitoring the quarantine reports for stragglers. Programs that skip the monitor phase learn about their own billing system’s un-signed relay from an angry finance team, not from the report they never opened.
Common Techniques
Section titled “Common Techniques”| Technique | What it does | Where it fails | Notes |
|---|---|---|---|
| SPF/DKIM/DMARC enforcement | Spoofing provably fails closed | Un-enumerated legit senders | Monitor → tighten → reject |
| Lookalike domain watch | Registrations near your brand flagged | New TLDs, homoglyph tricks | Defensive registration too |
| Attachment detonation | Runs payload in sandbox pre-delivery | Novel/latent execution | M12 analysis, pre-delivery |
| URL rewrite + time-of-click | Verdict re-checked at click time | Shortened/redirect chains | Pairs with proxy |
| External-sender banner | Visible cue on outside mail | Banner fatigue | Keep it sparse, consistent |
| Display-name spoof detection | Flags “CEO random@domain” patterns | Personal aliases | phishtriage rule |
| User report pipeline | Button → SOC with full headers | Queue latency kills value | Measure response time |
| Retro-hunt on intel | Sweep history for new campaign IOCs | Retention limits | 30-day sweep standard |
| BEC process controls | Payment-change verification out-of-band | Urgency override by execs | Process control, not tech |
| DMARC report mining | Who sends as you, pass/fail rates | Reports unopened for months | dmarchealth’s job |
Full Working Code
Section titled “Full Working Code”phishtriage — an .eml triage engine: parses raw headers, evaluates SPF/DKIM/DMARC results from Authentication-Results, flags display-name spoofing and lookalike domains against your domain list, extracts URLs and attachment metadata, and emits a verdict score with reasons. Pure stdlib — the email module does the RFC822 heavy lifting.
#!/usr/bin/env python3"""phishtriage — triage a raw .eml: auth results, spoof cues, URLs, attachments.
Usage: python3 phishtriage.py suspect.eml --domains mycorp.com,mycorp.co.ukVerdict bands: score >= 4 malicious, 2-3 suspicious, < 2 likely-benign."""import argparseimport csvimport difflibimport reimport sysfrom email import policyfrom email.parser import BytesParserfrom pathlib import Path
URL_RE = re.compile(r"https?://[^\s\"'<>)]+", re.I)DISPLAY_SPOOF = re.compile( r"(ceo|cfo|cto|director|president|finance|hr|payroll|invoice|billing|support)", re.I)ATTACH_RISK = {".exe": 3, ".js": 3, ".vbs": 3, ".hta": 3, ".scr": 3, ".iso": 2, ".img": 2, ".lnk": 2, ".zip": 1, ".7z": 1, ".docm": 2, ".xlsm": 2, ".pdf": 0}
def auth_results(msg): """Pull SPF/DKIM/DMARC verdicts from Authentication-Results headers.""" out = {"spf": None, "dkim": None, "dmarc": None} for header in msg.get_all("Authentication-Results", []): low = str(header).lower() for mech in out: m = re.search(rf"\b{mech}=(pass|fail|softfail|neutral|none|temperror|permerror)", low) if m and out[mech] is None: out[mech] = m.group(1) return out
def core_label(dom): return dom.split(".")[0]
def closest_domain(dom, my_domains, threshold=0.80): """Lookalike check: similarity on the core label, plus prefix-containment (mycorp-login contains mycorp) which ratio alone under-scores.""" c = core_label(dom) best, ratio = None, 0.0 for mine in my_domains: mc = core_label(mine) r = difflib.SequenceMatcher(None, c, mc).ratio() if (c.startswith(mc) or mc.startswith(c)) and min(len(c), len(mc)) >= 4: r = max(r, 0.95) if r > ratio: best, ratio = mine, r return (best, round(ratio, 3)) if ratio >= threshold else (None, 0.0)
def main(): ap = argparse.ArgumentParser(description="triage one .eml file") ap.add_argument("eml", type=Path) ap.add_argument("--domains", required=True, help="comma-separated list of your org's domains") args = ap.parse_args()
if not args.eml.exists(): sys.exit(f"fail-loud: file not found: {args.eml}") raw = args.eml.read_bytes() msg = BytesParser(policy=policy.default).parsebytes(raw)
sender = str(msg.get("From", "") or "") addr_match = re.search(r"<([^>]+)>", sender) from_addr = (addr_match.group(1) if addr_match else sender).strip().lower() from_domain = from_addr.rsplit("@", 1)[-1] if "@" in from_addr else "" if not from_domain: sys.exit("fail-loud: cannot parse From: address") display = re.sub(r"<[^>]+>", "", sender).strip().strip('"')
my_domains = [d.strip().lower() for d in args.domains.split(",") if d.strip()] auth = auth_results(msg) reasons, score = [], 0
# authentication signals if auth["dmarc"] == "fail": score += 2 reasons.append("DMARC fail (spoof-eligible)") if auth["spf"] == "fail": score += 1 reasons.append("SPF fail") if auth["dkim"] == "fail": score += 1 reasons.append("DKIM fail")
# display-name impersonation: role word in display, sender elsewhere if display and DISPLAY_SPOOF.search(display) and from_domain not in my_domains: score += 2 reasons.append(f"display-name role spoof: {display[:40]!r} at {from_domain}")
# lookalike domain vs. our estate look, ratio = closest_domain(from_domain, my_domains) if look: score += 3 reasons.append(f"lookalike domain: {from_domain} ~ {look} (ratio {ratio})")
# reply-to divergence reply_to = str(msg.get("Reply-To", "") or "").strip().lower() if reply_to and from_addr and reply_to.rsplit("@", 1)[-1] not in my_domains \ and reply_to != from_addr: score += 1 reasons.append(f"reply-to diverges: {reply_to}")
# urls and attachments body = "" if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == "text/plain" and not body: try: body = part.get_content() except Exception: body = "" else: try: body = msg.get_content() except Exception: body = "" urls = sorted(set(u.rstrip(".,;") for u in URL_RE.findall(body))) url_domains = sorted({u.split("//", 1)[-1].split("/", 1)[0].lower() for u in urls}) attach = [] for part in msg.walk(): fn = part.get_filename() if fn: ext = Path(fn).suffix.lower() attach.append((fn, ATTACH_RISK.get(ext, 0))) score += ATTACH_RISK.get(ext, 0) if ATTACH_RISK.get(ext, 0) >= 2: reasons.append(f"risky attachment: {fn}")
verdict = ("malicious" if score >= 4 else "suspicious" if score >= 2 else "likely-benign") print(f"from : {from_addr} (display: {display[:40]!r})") print(f"auth : spf={auth['spf']} dkim={auth['dkim']} dmarc={auth['dmarc']}") print(f"urls : {len(urls)} -> {url_domains[:6]}") print(f"attachment: {attach if attach else 'none'}") print(f"score : {score} verdict: {verdict}") for r in reasons: print(f" - {r}") with (args.eml.parent / "triage_log.csv").open("a", newline="", encoding="utf-8") as fh: w = csv.writer(fh) w.writerow([args.eml.name, from_addr, score, verdict, auth["spf"], auth["dkim"], auth["dmarc"], "|".join(url_domains), "|".join(f"{n}:{s}" for n, s in attach)])
if __name__ == "__main__": main()Code Walkthrough
Section titled “Code Walkthrough”auth_results() (lines 26-35) scrapes every Authentication-Results header for the three mechanism verdicts, first-wins per mechanism — gateways prepend their own header, so the topmost (their) verdict is what counts. DISPLAY_SPOOF (lines 22-23) is the BEC workhorse: role words (ceo, payroll, invoice) in the display name combined with a non-corporate sending domain is the pattern behind most executive-impersonation mail, worth 2 points (lines 92-95).
closest_domain() (lines 38-56) works on the core label (everything before the first dot) with two rules: a SequenceMatcher similarity at a 0.80 threshold, and a prefix-containment rule — mycorp-login contains mycorp, which a naive full-string ratio under-scores (0.77) but which is exactly how commodity phishing domains are built; containment bumps the score to 0.95 and trips the +3 lookalike rule (lines 106-110), the single most predictive signal in commodity credential phishing. Reply-to divergence (lines 113-116) catches the “replies go to a Gmail” BEC pattern. URL extraction (lines 118-133) pulls both bodies and URLs, normalizes trailing punctuation, and derives the domain set the analyst will eyeball; attachment scoring (lines 135-143) uses the extension risk table (ISO/IMG and macro-enabled Office carry real weight because both bypass simple mark-of-the-web checks). The verdict bands (line 146) are deliberately asymmetric — 4+ is malicious because stacked independent signals rarely co-occur in benign mail — and every point leaves a reason string (lines 148-150) so the analyst’s job shifts from analysis to confirmation. The append-only triage_log.csv (lines 158-165) is the triage queue’s audit trail.
How It Reaches The Target
Section titled “How It Reaches The Target”A credential-phish reaches a mailbox through exactly this gauntlet. The adversary registers mycorp-login.com (registered last week, no reputation), sends from a fresh VPS with no SPF, and the gateway’s verdict is some engine says spam but first-seen means others abstain — the message delivers with an external banner, display name “IT Helpdesk”, body URL pointing at a cloned SSO page. If the user reports it, phishtriage produces: DMARC fail (+2), display-name role spoof (+2), lookalike domain (+3) — score 7, malicious, with the domain list the analyst needs. Response is then mechanical: message tracking pulls every recipient of that sender/subject cluster, soft-delete purges across mailboxes, the sender domain and URL join the mail gateway’s blocklist, and the SSO page’s host goes to BT10 as a sighting.
The retro-hunt is what catches the timing gap: three days later BT10 flags the campaign’s URL infrastructure, and the 30-day message-tracking sweep finds the same sender delivered 40 more messages with a different lure body — all purged, all logged, and the miss (40 inboxes for 3 days) becomes a tuning datapoint for the gateway rules (BT9). Outbound, the mirror-image incident is the spoofed invoice: a customer calls asking about your “billing update” email, and dmarchealth shows the aggregate reports have been carrying mycorp-billing.net as a failing source for two weeks — visibility only DMARC reporting provides, and the reason outbound enforcement is an inbound-defense tool.
Exploitation Chain
Section titled “Exploitation Chain”- Recon — adversary harvests exec names, org domains, and a recent business event to anchor the lure
- Registration — lookalike domain or aged spoofed sender; SPF/DKIM/DMARC all failing by design
- Delivery — targeted lure passes first-seen gateway verdicts; lands in inboxes with banner only
- Engagement — user clicks cloned-SSO URL or enables the macro (M12 payload chain)
- Capture — credentials or execution; reply-to divergence silently redirects BEC threads
- Detection — user report or banner-driven alert routes to SOC; phishtriage scores and scopes
- Response — tracking sweep → purge across mailboxes → blocklist → BT10 sighting
- Hardening — retro-hunt closes the timing gap; rules tune (BT9); DMARC policy tightens
Building Your Own Tool
Section titled “Building Your Own Tool”DMARC enforcement is only as good as your reading of the reports it generates. dmarchealth parses DMARC aggregate report XML (the rua inbox collects them daily, gzipped, one per receiving organization) and answers the operational question: who is sending as our domains, from where, and does it pass?
Design decisions
Section titled “Design decisions”Aggregate reports follow the published feedback schema — <record> with <row> (source IP, count, policy evaluated) and <auth_results> (SPF/DKIM per source). The tool aggregates by (source IP → SPF result, DKIM result, aligned domain) rather than dumping rows, because the analyst’s question is per-sender health, not per-mail volume. Sources that pass SPF but fail DKIM alignment are flagged as “forwarder-style” (benign pattern), while sources failing both are the spoofing candidates. XML is parsed with stdlib xml.etree; gzip and plain XML both accepted; fail-loud on a schema-less file so a misrouted report never silently zeroes your stats.
Full source
Section titled “Full source”#!/usr/bin/env python3"""dmarchealth — summarize DMARC aggregate reports (rua XML) into a sender table.
Usage: python3 dmarchealth.py reports/*.xml --domain mycorp.com (accepts .xml and .xml.gz as produced by receivers' rua mail)"""import argparseimport globimport gzipimport sysimport xml.etree.ElementTree as ETfrom collections import defaultdictfrom pathlib import Path
def open_report(path: Path): if path.suffix == ".gz": return gzip.open(path, "rb") return path.open("rb")
def parse_report(path: Path): with open_report(path) as fh: try: root = ET.parse(fh).getroot() except ET.ParseError as e: sys.exit(f"fail-loud: {path.name}: not valid DMARC XML ({e})") ns = "" if root.tag.startswith("{"): ns = root.tag.split("}")[0] + "}" if not root.tag.endswith("feedback"): sys.exit(f"fail-loud: {path.name}: root element is not <feedback>")
org = root.findtext(f"{ns}report_metadata/{ns}org_name") or "unknown" records = [] for rec in root.findall(f"{ns}record"): row = rec.find(f"{ns}row") ip = row.findtext(f"{ns}source_ip") or "?" count = int(row.findtext(f"{ns}count") or 0) policy = row.find(f"{ns}policy_evaluated") disp = policy.findtext(f"{ns}disposition") or "none" dkim_pass = policy.findtext(f"{ns}dkim") or "fail" spf_pass = policy.findtext(f"{ns}spf") or "fail" hdr_from = (rec.findtext(f"{ns}identities/{ns}header_from") or (rec.findtext(f"{ns}identity") or "")).lower() records.append({"org": org, "ip": ip, "count": count, "disposition": disp, "dkim": dkim_pass, "spf": spf_pass, "header_from": hdr_from}) if not records: sys.exit(f"fail-loud: {path.name}: zero <record> entries") return records
def main(): ap = argparse.ArgumentParser(description="DMARC aggregate report health") ap.add_argument("patterns", nargs="+", help="report files or globs") ap.add_argument("--domain", required=True, help="your organizational domain") args = ap.parse_args()
files = [] for pat in args.patterns: matched = sorted(glob.glob(pat)) if not matched: sys.exit(f"fail-loud: pattern matched nothing: {pat}") files.extend(matched)
senders = defaultdict(lambda: {"count": 0, "orgs": set(), "spf_pass": 0, "dkim_pass": 0, "dkim_domains": set(), "dispositions": set()}) total = 0 for f in files: for rec in parse_report(Path(f)): if rec["header_from"] and args.domain not in rec["header_from"]: continue # report covers other org domains key = (rec["header_from"], rec["ip"]) s = senders[key] s["count"] += rec["count"] s["orgs"].add(rec["org"]) s["dispositions"].add(rec["disposition"]) if rec["spf"] == "pass": s["spf_pass"] += rec["count"] if rec["dkim"] == "pass": s["dkim_pass"] += rec["count"] total += rec["count"]
if total == 0: sys.exit(f"fail-loud: no records for domain {args.domain!r} in input")
print(f"{'header_from':<28}{'source_ip':<18}{'count':>8} " f"{'spf':>5} {'dkim':>5} class") rows = sorted(senders.items(), key=lambda kv: -kv[1]["count"]) for (hf, ip), s in rows: spf_r = s["spf_pass"] / s["count"] dkim_r = s["dkim_pass"] / s["count"] if spf_r < 0.1 and dkim_r < 0.1: klass = "SPOOF-CANDIDATE" elif dkim_r < 0.1 and spf_r >= 0.9: klass = "forwarder-style" elif spf_r >= 0.9 and dkim_r >= 0.9: klass = "aligned/healthy" else: klass = "mixed/needs-work" print(f"{hf:<28}{ip:<18}{s['count']:>8} " f"{spf_r:>5.0%} {dkim_r:>5.0%} {klass}") print(f"\ntotal messages covered: {total} from {len(files)} report file(s)") print("action: reject-eligible = aligned/healthy only; investigate SPOOF-CANDIDATE IPs")
if __name__ == "__main__": main()Walkthrough
Section titled “Walkthrough”parse_report() (lines 19-46) handles the two real-world wrinkles: gzip wrapping and namespace variation — the namespace prefix is derived from the root tag rather than hardcoded, because receivers ship slightly different (but schema-equivalent) XML. The <feedback> root check is the fail-loud gate against a misplaced ordinary email attachment. main()’s aggregation key is (header_from, source_ip) (line 72) — exactly the pair the rollout decision depends on: a header_from that matches your domain plus an IP with 100% failing results is the adversary; the same header_from from your marketing platform’s IPs with aligned DKIM is rollout-blocking until fixed. The three-way classification (lines 92-101) maps directly to the DMARC rollout sequence — “aligned/healthy” senders are the ones that survive p=reject, “forwarder-style” explains SPF-pass/DKIM-fail legitimate relays, and “SPOOF-CANDIDATE” is the incident queue. The --domain filter (lines 74-75) keeps multi-domain reports from polluting one domain’s health table.
Compilation and test procedure
Section titled “Compilation and test procedure”collect a week of rua attachments (they arrive as .xml.gz attachments)
Section titled “collect a week of rua attachments (they arrive as .xml.gz attachments)”$ ls reports/ | head -3 google.com!mycorp.com!1696118400!1696204800.xml.gz outlook.com!mycorp.com!1696118400!1696204800.xml.gz yahoo.com!mycorp.com!1696118400!1696204800.xml.gz
summarize the week for your domain
Section titled “summarize the week for your domain”$ python3 dmarchealth.py “reports/*.xml.gz” —domain mycorp.com header_from source_ip count spf dkim class mycorp.com 198.51.100.23 41280 100% 100% aligned/healthy mycorp.com 203.0.113.77 3900 92% 98% mixed/needs-work mycorp.com 192.0.2.66 840 0% 0% SPOOF-CANDIDATE
total messages covered: 46028 from 21 report file(s)
the SPOOF-CANDIDATE IP feeds a blocklist entry and a BT10 sighting;
Section titled “the SPOOF-CANDIDATE IP feeds a blocklist entry and a BT10 sighting;”the mixed/needs-work sender (billing platform) blocks p=reject until fixed
Section titled “the mixed/needs-work sender (billing platform) blocks p=reject until fixed”Detection considerations
Section titled “Detection considerations”The tool’s output drives policy, but the same data feeds detection: any IP classed SPOOF-CANDIDATE that also appears in your inbound gateway logs is attempt evidence worth a BT10 sighting and possibly takedown action through your registrar contacts. Operationally, watch the trust-shifting failure: a source that moves from aligned/healthy to mixed (DKIM decay after a platform migration) is rollout-blocking until repaired — dmarchealth run weekly makes that drift visible before finance escalates a bounced invoice. Pair the weekly run with a lookalike-domain watch on certificate-transparency logs and registrar feeds, since the adversary’s spoof infrastructure shows up there before it shows up in your reports.
Improvements to add
Section titled “Improvements to add”- Ingest the rua mailbox directly (IMAP) and auto-process daily, tracking per-sender health trends week over week.
- Add DKIM domain extraction from
<auth_results>to distinguish your selectors from third-party ones inside aligned mail. - Correlate SPOOF-CANDIDATE IPs with passive DNS and WHOIS age to auto-prioritize takedown requests.
- Emit a rollout-readiness verdict (blockers remaining per sender class) to pace the none → quarantine → reject sequence.
- Support SMTP TLS report (TLS-RPT) JSON in the same run for transport-layer health alongside identity health.
Detection — Defender View
Section titled “Detection — Defender View”| Signal | Source | ID | Notes |
|---|---|---|---|
| Report-phish volume & latency | Triage pipeline | per week | Human sensor health |
| Gateway first-seen misses | Gateway telemetry | per campaign | Retro-hunt driver |
| Macro process spawn | Sysmon | EID 1 | winword/excel → child shell |
| Macro script block | Windows Security | 4104 | Dropper PowerShell |
| Post-click C2 callback | Sysmon | EID 3 | From phished workstation |
| Credential-page DNS | Sysmon | EID 22 | Lookalike domain resolves |
| DMARC spoof candidates | dmarchealth | weekly | Outbound identity attacks |
| Campaign purge scope | Message tracking | per incident | 30-day sweep coverage |
| Triage log coverage | triage_log.csv | per report | Every report resolved |
| Rule regression | BT9 gates | per deploy | Lure-variant resilience |
Evasion Techniques
Section titled “Evasion Techniques”Adversaries engineer around every layer above. Authentication is abused, not bypassed: commodity kits send from their own registered lookalike domain (mycorp-login.com passes SPF/DKIM/DMARC of itself) — authentication proves the sender owns that domain, not that it’s yours; only lookalike detection and user-visible cues cover the gap. Display-name spoofing needs no domain at all: “M&A Steering Committee” as display name over a fresh Gmail works because busy executives read names, not addresses. Thread hijacking rides a real compromised correspondence — the reply inherits legitimacy no filter questions, and reply-to divergence is the only tell. Attachment formats rotate ahead of detonation coverage: ISO/IMG containers, OneNote, password-protected archives with the password in the body, and now cloud-hosted lures (a link to a real SharePoint page that links to the fake SSO page) — each layer looks legitimate in isolation. Time-of-click evasion uses short-lived redirect chains and geo/user-agent gates so the sandbox sees a 404 while the victim’s browser sees the lure. And rate discipline: two targeted messages to two executives will never trip volume thresholds — targeted phishing is designed to be statistically invisible.
⚠️ What caught me: we enforced DMARC correctly, banners were on, gateway was healthy — and a finance director still wired €180k against a “supplier bank change” thread where every message was authenticated, because the adversary had replied inside a months-long real correspondence stolen from the supplier’s own compromised mailbox. Every technical control reported green; the only anomaly was reply-to divergence and urgency language. That incident bought us the out-of-band verification control for payment changes — process, not technology — and taught me that email security’s last mile is never a filter verdict: it is the business process the filter cannot see.
Lab Practice
Section titled “Lab Practice”Stand up the full loop in a lab. Authentication: add SPF, DKIM, and DMARC (p=none with rua= to a local mailbox) records for a lab domain on a private DNS server; send legitimate mail from a test relay and a “spoof” from another host; observe aggregate reports accumulate. Triage: collect or hand-craft three .eml samples — a real newsletter, a display-name spoof, a lookalike-domain credential lure — and run phishtriage on each; confirm the verdict bands separate them. Reports: generate or obtain sample aggregate XML (the spec’s examples or your own rua capture) and run dmarchealth; confirm the spoof source classes as SPOOF-CANDIDATE. Response drill: simulate the purge decision for the lure — who receives it, what’s the scope query, what’s the blocklist change — and time yourself. Debrief: which signal caught the lure that banners alone would have missed, and what does that imply for banner fatigue?
Interview Questions
Section titled “Interview Questions”- Technical: Walk SPF, DKIM, and DMARC as three layers — what does each prove, what defeats each, and why is DMARC alignment the one that matters for spoofing?
- Technical: Why does a lookalike domain (mycorp-login.com) pass all three authentication checks, and which compensating controls catch it?
- Scenario: You must move your domain from
p=nonetop=rejectthis quarter. Walk the rollout: what do the aggregate reports tell you, and what breaks if you rush it? - Scenario: A finance director reports a payment-change email that passed SPF, DKIM, and DMARC. How is that possible, and what do you do in the next hour?
- Conceptual: Why is the report-phish button a detection instrument rather than a training metric, and what two numbers measure its value?
- Deep-dive: Design email security operations for a 5,000-mailbox org: authentication posture, gateway layering, triage pipeline with staffing, retro-hunt capability, and the metrics you’d review monthly.
- Practical: User-reported phish latency is 48 hours and the SOC wants headcount. Before hiring, which two pipeline improvements do you try, and how do you measure them?
Related Modules
Section titled “Related Modules”T2 (Initial Access Emulation) · M12 (Script and Macro Malware Analysis) · BT1 (SOC Operations & Alert Triage) · BT4 (Incident Response Process) · BT9 (Detection Engineering & Sigma)