daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

phishing-site-link-identification.md (22783B)


      1 ---
      2 title: "Phishing Site & Link Identification"
      3 description: "1. Golden Rules 2. URL Anatomy — Where to Actually Look 3. Domain Red Flags 4. Homoglyph & Punycode Detection 5. Unwrapping Redirects & Shorteners 6…"
      4 category: web
      5 tags: ["web", "adcs"]
      6 tools: ["OpenSSL"]
      7 difficulty: intermediate
      8 updated: "2026-08-10"
      9 source: "vault:Web/Phishing Site & Link Identification - Cheat Sheet.md"
     10 ---
     11 # Phishing Site & Link Identification — Cheat Sheet
     12 
     13 ### Defensive triage for suspicious URLs, domains and landing pages
     14 
     15 > Covers: URL anatomy · lookalike domains · punycode/IDN · redirect chains · header and SPF/DKIM/DMARC checks · WHOIS and DNS · certificate transparency · safe fetching and detonation
     16 
     17 ---
     18 
     19 ## Table of Contents
     20 
     21 1. [Golden Rules](#golden-rules)
     22 2. [URL Anatomy — Where to Actually Look](#1-url-anatomy--where-to-actually-look)
     23 3. [Domain Red Flags](#2-domain-red-flags)
     24 4. [Homoglyph & Punycode Detection](#3-homoglyph--punycode-detection)
     25 5. [Unwrapping Redirects & Shorteners](#4-unwrapping-redirects--shorteners)
     26 6. [Email Header & Auth Triage](#5-email-header--auth-triage)
     27 7. [WHOIS & DNS Checks](#6-whois--dns-checks)
     28 8. [TLS Certificate & CT Logs](#7-tls-certificate--ct-logs)
     29 9. [Safe Fetching of Page Content](#8-safe-fetching-of-page-content)
     30 10. [Landing Page Tells](#9-landing-page-tells)
     31 11. [Attachment Triage](#10-attachment-triage)
     32 12. [Reputation & Sandbox Services](#11-reputation--sandbox-services)
     33 13. [Triage Workflow](#12-triage-workflow)
     34 14. [Quick Reference Table](#13-quick-reference-table)
     35 15. [Reporting & Takedown](#14-reporting--takedown)
     36 
     37 ---
     38 
     39 ## Golden Rules
     40 
     41 > [!warning] Handle every unverified URL as live malware.
     42 > - Never open a suspicious link in your daily-driver browser or on a host with credentials on it. Use a disposable VM, and route through a network you do not mind burning.
     43 > - Fetching a URL leaks your IP and often a unique token embedded in the link, which confirms to the operator that the target is live. Prefer passive lookups first.
     44 > - Judge the **registrable domain**, never the display text, the path, the favicon or the branding.
     45 > - HTTPS and a padlock prove nothing. Free DV certificates mean the overwhelming majority of phishing sites are served over TLS.
     46 > - If a page asks for credentials, MFA codes or a card number, navigate to the service yourself from a known-good bookmark instead.
     47 
     48 ---
     49 
     50 ## 1. URL Anatomy — Where to Actually Look
     51 
     52 ```
     53 https://accounts.google.com.verify-login.ru:8443/signin?token=abc#/
     54 └─┬─┘   └──────────────┬──────────────────────┘└─┬┘└──┬─┘└───┬───┘
     55 scheme          host (read RIGHT to LEFT)      port path   query
     56 ```
     57 
     58 The only part that matters for identity is the **registrable domain**, the last two labels before the public suffix. Read the host from right to left, stopping at the first `/`.
     59 
     60 | URL | Registrable domain | Verdict |
     61 |---|---|---|
     62 | `https://accounts.google.com/signin` | `google.com` | Legitimate |
     63 | `https://accounts.google.com.verify-login.ru/` | `verify-login.ru` | Phish — brand is a subdomain |
     64 | `https://google.com.evil.co/` | `evil.co` | Phish |
     65 | `https://secure-google.com/` | `secure-google.com` | Phish — hyphenated lookalike |
     66 | `https://google.com@evil.co/` | `evil.co` | Phish — everything before `@` is userinfo |
     67 | `https://sites.google.com/view/login-x` | `google.com` | Legitimate host, abused hosting |
     68 
     69 Extract the host programmatically rather than trusting your eyes:
     70 
     71 ```bash
     72 # Pull scheme, host, path out of a URL without fetching it
     73 print -r 'https://accounts.google.com.verify-login.ru/signin' | \
     74   python3 -c 'import sys,urllib.parse as u; p=u.urlparse(sys.stdin.read().strip()); print("host:",p.hostname,"\nport:",p.port,"\npath:",p.path,"\nuser:",p.username)'
     75 ```
     76 
     77 ```bash
     78 # Registrable domain (eTLD+1) using the public suffix list
     79 uv venv .venv && source .venv/bin/activate
     80 uv pip install tldextract
     81 python3 -c 'import tldextract,sys; e=tldextract.extract(sys.argv[1]); print(e.registered_domain)' \
     82   'https://accounts.google.com.verify-login.ru/signin'
     83 ```
     84 
     85 > [!tip] Common obfuscations
     86 > - `@` userinfo trick: browser goes to whatever follows the `@`.
     87 > - Decimal, octal or hex IPs: `http://2130706433/` is `127.0.0.1`.
     88 > - Percent-encoding of the host or path to hide keywords.
     89 > - Very long paths padding the real domain off the end of a mobile URL bar.
     90 > - Data URIs and `blob:` URLs rendering a login form with no remote host at all.
     91 
     92 ---
     93 
     94 ## 2. Domain Red Flags
     95 
     96 | Signal | Why it matters | How to check |
     97 |---|---|---|
     98 | Registered in the last 30 days | Phishing infra is disposable and short-lived | `whois` creation date |
     99 | Brand name as a subdomain or in the path | Legitimate brands own their apex | Read host right to left |
    100 | Hyphenated brand combos (`paypal-secure-login`) | Cheap way to look plausible | Visual |
    101 | Unusual TLD for the brand (`.zip`, `.mov`, `.top`, `.cf`, `.xyz`) | Cheap or free registration | Visual |
    102 | Free hosting or dev platform subdomains | Abused for zero-cost hosting with valid TLS | Check apex against known SaaS |
    103 | Privacy-shielded WHOIS on a "corporate" login page | Real brands do not hide registrant data | `whois` |
    104 | Wildcard DNS answering every subdomain | Per-victim subdomains | `dig random.$domain` |
    105 | Hosting ASN mismatched with the brand | Bulletproof or cheap VPS ranges | `whois <ip>` |
    106 | Open directory listing or `/.git` exposed | Sloppy kit deployment | Manual, in a VM |
    107 
    108 Legitimate-but-abused hosting worth recognising: `*.web.app`, `*.firebaseapp.com`, `*.pages.dev`, `*.workers.dev`, `*.r2.dev`, `*.blob.core.windows.net`, `*.s3.amazonaws.com`, `*.weeblysite.com`, `*.glitch.me`, `sites.google.com/view/...`, `*.notion.site`, IPFS gateways. The apex is genuine, so reputation feeds often miss them.
    109 
    110 ---
    111 
    112 ## 3. Homoglyph & Punycode Detection
    113 
    114 Internationalised domains let attackers register visually identical names. Browsers show punycode as `xn--` only in some cases, so decode explicitly.
    115 
    116 ```bash
    117 # Decode punycode to the real Unicode label
    118 python3 -c 'print("xn--80ak6aa92e".encode().decode("idna"))'      # -> аррӏе (Cyrillic)
    119 
    120 # Encode a suspect Unicode host to see its punycode form
    121 python3 -c 'print("аррӏе.com".encode("idna").decode())'
    122 ```
    123 
    124 ```bash
    125 # Flag any non-ASCII characters in a host, and name the script of each
    126 python3 - <<'PY'
    127 import unicodedata
    128 host = "аррӏе.com"
    129 for ch in host:
    130     if ord(ch) > 127:
    131         print(f"{ch!r} U+{ord(ch):04X} {unicodedata.name(ch)}")
    132 PY
    133 ```
    134 
    135 Mixed-script hosts (Latin plus Cyrillic or Greek in one label) are almost always hostile. Classic swaps to watch for:
    136 
    137 | Looks like | Actually | Codepoint |
    138 |---|---|---|
    139 | `a` | Cyrillic а | U+0430 |
    140 | `e` | Cyrillic е | U+0435 |
    141 | `o` | Cyrillic о | U+043E |
    142 | `p` | Cyrillic р | U+0440 |
    143 | `i` / `l` | Cyrillic ӏ, Turkish ı | U+04CF, U+0131 |
    144 | `rn` | reads as `m` at small sizes | ASCII only |
    145 | `vv` | reads as `w` | ASCII only |
    146 | `1` / `l` / `I` | font-dependent confusion | ASCII only |
    147 
    148 Generate and check typosquats around a brand you protect:
    149 
    150 ```bash
    151 # dnstwist enumerates permutations and resolves the live ones
    152 uv pip install dnstwist
    153 dnstwist --registered --mx --format cli example.com
    154 ```
    155 
    156 ---
    157 
    158 ## 4. Unwrapping Redirects & Shorteners
    159 
    160 Resolve the chain without executing anything. Prefer `HEAD` and never follow blindly into a download.
    161 
    162 ```bash
    163 # Show every hop, headers only, no body, no auto-follow of unsafe schemes
    164 curl -sIL --max-redirs 10 --max-time 15 -A 'Mozilla/5.0' 'https://short.link/abc' \
    165   | grep -Ei '^(HTTP/|location:)'
    166 ```
    167 
    168 ```bash
    169 # One hop at a time, so you can bail out
    170 curl -sI 'https://short.link/abc' | grep -i '^location:'
    171 ```
    172 
    173 Many shorteners expose a preview or API that avoids touching attacker infra at all:
    174 
    175 | Service | Preview method |
    176 |---|---|
    177 | bit.ly | append `+` to the URL |
    178 | tinyurl.com | `https://preview.tinyurl.com/<code>` |
    179 | ow.ly, buff.ly | Bitly-family, `+` often works |
    180 | t.co | `curl -sI` returns `location` without rendering |
    181 
    182 Unwrap corporate link-rewriting so you see the real destination:
    183 
    184 ```bash
    185 # Proofpoint URLDefense v3, Microsoft Safe Links, Barracuda etc. all URL-encode the original
    186 python3 -c 'import sys,urllib.parse as u; q=u.parse_qs(u.urlparse(sys.argv[1]).query); print(q.get("url",[""])[0])' \
    187   'https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fevil.co%2Flogin&data=...'
    188 ```
    189 
    190 > [!warning] Every link in a phish is usually unique per recipient. Fetching it tells the operator your address is live and may burn the sample before analysis.
    191 
    192 ---
    193 
    194 ## 5. Email Header & Auth Triage
    195 
    196 Get the **original** headers, not a forward. In Gmail use "Show original", in Outlook "View source", and save the `.eml` intact.
    197 
    198 What to read, in order:
    199 
    200 1. `From:` display name versus the actual address in angle brackets.
    201 2. `Return-Path:` / envelope sender. A mismatch with `From:` is normal for mailing lists but suspicious for a bank.
    202 3. `Reply-To:` pointing somewhere unrelated is a strong lure signal.
    203 4. `Authentication-Results:` for SPF, DKIM and DMARC verdicts.
    204 5. Earliest `Received:` hop, which shows the true origin before the receiving infra.
    205 6. `Message-ID` domain matching the sending domain.
    206 
    207 ```bash
    208 # Pull the auth verdicts and the sender fields out of a saved .eml
    209 grep -Ei '^(authentication-results|received-spf|dkim-signature|from|reply-to|return-path|message-id):' sample.eml
    210 ```
    211 
    212 ```bash
    213 # Parse an .eml properly, including nested parts and URLs in the body
    214 python3 - <<'PY'
    215 import email, re
    216 from email import policy
    217 m = email.message_from_file(open("sample.eml"), policy=policy.default)
    218 for h in ("From","Reply-To","Return-Path","Subject","Date","Authentication-Results","Message-ID"):
    219     print(f"{h}: {m.get(h)}")
    220 body = "".join(p.get_content() for p in m.walk() if p.get_content_type() in ("text/plain","text/html"))
    221 for url in sorted(set(re.findall(r'https?://[^\s"\'<>)]+', body))):
    222     print("URL:", url)
    223 PY
    224 ```
    225 
    226 Interpreting the verdicts:
    227 
    228 | Result | Meaning | Weight |
    229 |---|---|---|
    230 | `spf=fail` + `dkim=fail` + `dmarc=fail` | Spoofed sending domain | Strong |
    231 | `spf=pass` on an attacker-owned lookalike domain | Auth passes for *their* domain, proves nothing about the brand | Neutral, common |
    232 | `dkim=pass` with `d=` not matching the `From:` domain | Unaligned DKIM, DMARC will not pass on it | Suspicious |
    233 | `dmarc=pass` | Aligned and authenticated for the `From:` domain | Reassuring, not conclusive if the account is compromised |
    234 
    235 ```bash
    236 # Check what the claimed domain publishes
    237 dig +short TXT example.com | grep -i spf
    238 dig +short TXT _dmarc.example.com
    239 dig +short TXT selector1._domainkey.example.com
    240 ```
    241 
    242 > [!note] Business email compromise sends from a genuinely owned, fully authenticated mailbox. Auth passing is not innocence. Weight the request itself: payment redirection, urgency, secrecy, out-of-band contact.
    243 
    244 ---
    245 
    246 ## 6. WHOIS & DNS Checks
    247 
    248 ```bash
    249 # Registration age is the single highest-signal indicator
    250 whois evil-login.co | grep -Ei 'creation|created|registered|registrar|registrant|name server'
    251 ```
    252 
    253 ```bash
    254 # Resolution and infrastructure
    255 dig +short A evil-login.co
    256 dig +short NS evil-login.co
    257 dig +short MX evil-login.co          # MX present = capable of receiving replies
    258 dig +short TXT evil-login.co
    259 
    260 # Wildcard test: does a random subdomain resolve? Per-victim subdomains are a kit tell
    261 dig +short "$(openssl rand -hex 6).evil-login.co"
    262 
    263 # Who owns the hosting
    264 whois "$(dig +short A evil-login.co | head -1)" | grep -Ei 'orgname|netname|country|origin'
    265 ```
    266 
    267 ```bash
    268 # Passive DNS style pivot: what else is on that IP (use a service, do not scan)
    269 # See section 11 for tooling. Shared cheap hosting will show hundreds of unrelated domains.
    270 ```
    271 
    272 Age heuristic worth internalising: a "Microsoft account security" page on a domain created 4 days ago with a privacy-shielded registrant and a Let's Encrypt certificate issued the same day is phishing until proven otherwise.
    273 
    274 ---
    275 
    276 ## 7. TLS Certificate & CT Logs
    277 
    278 ```bash
    279 # Inspect the presented certificate without loading the page
    280 echo | openssl s_client -connect evil-login.co:443 -servername evil-login.co 2>/dev/null \
    281   | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
    282 ```
    283 
    284 What to read:
    285 
    286 | Field | Phishing tell |
    287 |---|---|
    288 | `notBefore` | Issued hours or days ago |
    289 | Issuer | Free DV CA on a page impersonating a bank |
    290 | Subject | `CN` is the lookalike domain, no organisation details |
    291 | SAN list | Dozens of unrelated brand-ish hostnames on one cert |
    292 
    293 Certificate Transparency is a free, passive early-warning source for lookalikes of a domain you own:
    294 
    295 ```bash
    296 # All certs ever issued for a domain and its subdomains, from CT logs
    297 curl -s 'https://crt.sh/?q=%25.example.com&output=json' \
    298   | python3 -c 'import sys,json; [print(r["name_value"].replace("\n",","), r["not_before"]) for r in json.load(sys.stdin)]' \
    299   | sort -u | head -50
    300 ```
    301 
    302 Search CT for brand permutations (`example-secure`, `examp1e`, `example-login`) to catch infrastructure before the campaign launches.
    303 
    304 ---
    305 
    306 ## 8. Safe Fetching of Page Content
    307 
    308 Passive first. If you must fetch, do it from an isolated VM or a cloud sandbox, never your host.
    309 
    310 ```bash
    311 # Headers only, no body executed, short timeout, no cookies stored
    312 curl -sI --max-time 10 -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' 'https://evil-login.co/'
    313 ```
    314 
    315 ```bash
    316 # Fetch the raw HTML to a file for offline inspection, do not open it in a browser
    317 curl -s --max-time 15 -A 'Mozilla/5.0' 'https://evil-login.co/' -o page.html
    318 file page.html && wc -c page.html
    319 ```
    320 
    321 ```bash
    322 # Extract form targets, external scripts and iframes from the saved HTML
    323 python3 - <<'PY'
    324 import re
    325 h = open("page.html", encoding="utf-8", errors="replace").read()
    326 for label, pat in [("FORM ACTION", r'<form[^>]*action=["\']([^"\']+)'),
    327                    ("SCRIPT SRC",  r'<script[^>]*src=["\']([^"\']+)'),
    328                    ("IFRAME SRC",  r'<iframe[^>]*src=["\']([^"\']+)'),
    329                    ("INPUT NAME",  r'<input[^>]*name=["\']([^"\']+)')]:
    330     for m in sorted(set(re.findall(pat, h, re.I))):
    331         print(f"{label}: {m}")
    332 PY
    333 ```
    334 
    335 > [!tip] The form `action` is the payoff. A login page whose form posts to an unrelated domain, a raw IP, a `.php` on cheap hosting, or a Telegram bot API endpoint is conclusive.
    336 
    337 Server-side cloaking is standard, so a plain `curl` often returns a benign decoy. Attacker kits filter on User-Agent, `Referer`, geolocation, ASN (blocking known security vendors) and sometimes require the unique token from the original link. Getting a harmless page back does not clear the URL.
    338 
    339 ---
    340 
    341 ## 9. Landing Page Tells
    342 
    343 Observed in an isolated VM, or from saved HTML.
    344 
    345 - Form posts to a different domain than the one in the address bar.
    346 - Credentials submitted, then a redirect to the real site's genuine login page, so the victim assumes a mistyped password.
    347 - Password field with autocomplete disabled and no "forgot password" or account-creation flow that actually works.
    348 - Requests for data the real service would never ask for together: password plus MFA code plus card number plus mother's maiden name.
    349 - MFA relay kits (Evilginx, EvilProxy, Tycoon) proxy the real site live, so the page is pixel-perfect and the TLS is valid. The **domain** is your only reliable tell.
    350 - Right-click, view-source or devtools disabled via JavaScript.
    351 - Blocked or broken links for everything except the login form.
    352 - Base64 or heavily obfuscated inline JavaScript that assembles the form at runtime.
    353 - The brand logo hotlinked from the genuine CDN while everything else is local.
    354 - Fake browser chrome drawn in HTML, a "browser in the browser" popup simulating an OAuth window. Try to drag it outside the page, a real window can leave, a fake one cannot.
    355 - QR codes in the email body ("quishing") to move the click onto an unmanaged mobile device. Decode offline before scanning:
    356 
    357 ```bash
    358 uv pip install "qreader" opencv-python-headless
    359 python3 -c 'import cv2; d=cv2.QRCodeDetector(); print(d.detectAndDecode(cv2.imread("qr.png"))[0])'
    360 # or
    361 zbarimg --quiet --raw qr.png
    362 ```
    363 
    364 ---
    365 
    366 ## 10. Attachment Triage
    367 
    368 Static inspection only, in a VM, never double-click.
    369 
    370 ```bash
    371 file suspicious.*
    372 sha256sum suspicious.*                 # hash first, then look it up rather than uploading
    373 ```
    374 
    375 ```bash
    376 # Office documents: check for macros and embedded objects
    377 uv pip install oletools
    378 olevba -a suspicious.docm
    379 oleid suspicious.doc
    380 ```
    381 
    382 ```bash
    383 # PDFs: look for JavaScript, auto-actions and embedded launches
    384 uv pip install pdfid pdf-parser
    385 pdfid.py suspicious.pdf                # /JS /JavaScript /OpenAction /Launch /EmbeddedFile counts
    386 ```
    387 
    388 ```bash
    389 # Archives: list contents without extracting, watch for double extensions and LNK/ISO/IMG
    390 unzip -l suspicious.zip
    391 7z l suspicious.iso
    392 ```
    393 
    394 High-risk containers used to defeat mark-of-the-web: `.iso`, `.img`, `.vhd`, `.7z`, password-protected `.zip` with the password in the email body, `.lnk`, `.chm`, `.one`, `.svg` with embedded script, `.html` smuggling attachments that rebuild a payload client-side.
    395 
    396 > [!warning] Hash first and search the hash. Uploading a targeted sample to a public multi-scanner makes it public and tips off the operator.
    397 
    398 ---
    399 
    400 ## 11. Reputation & Sandbox Services
    401 
    402 | Service | Use | Notes |
    403 |---|---|---|
    404 | urlscan.io | Renders a URL, screenshots, DOM, request chain | **Set scan to private** for targeted phish. Public scans are searchable by anyone, including the attacker |
    405 | VirusTotal | URL, domain, IP and file reputation | Search by hash before uploading. Uploads are shared with vendors |
    406 | Hybrid Analysis / Joe Sandbox / ANY.RUN | Full detonation | Free tiers make results public |
    407 | crt.sh | Certificate transparency search | Passive, free, no attacker contact |
    408 | Shodan / Censys | Host and cert fingerprinting, pivot on kit artefacts | Passive |
    409 | PhishTank / OpenPhish | Community phish feeds | Good for known campaigns, weak on fresh ones |
    410 | Google Safe Browsing / Microsoft Defender SmartScreen | Browser-level blocklists | Lag of hours to days on new infra |
    411 | Have I Been Pwned | Assess exposure after a credential submission | Post-incident |
    412 
    413 Absence of detections means nothing on a domain registered this morning. Reputation feeds are lagging indicators. Registration age plus form target plus domain reading beat any single verdict.
    414 
    415 ---
    416 
    417 ## 12. Triage Workflow
    418 
    419 ```
    420 1. PRESERVE      Save the original .eml and the raw URL. Do not click anything.
    421 2. PARSE         Extract host, registrable domain, and every URL in the body.
    422 3. READ DOMAIN   Right to left. Decode punycode. Check for mixed scripts.
    423 4. AGE IT        whois creation date. Under ~30 days is a strong signal on its own.
    424 5. AUTH          SPF / DKIM / DMARC alignment against the claimed From: domain.
    425 6. INFRA         dig A/NS/MX, ASN owner, wildcard test, cert notBefore and issuer.
    426 7. REPUTATION    Hash and domain lookups. Passive sources first.
    427 8. UNWRAP        Resolve redirect chain with curl -sIL from an isolated host.
    428 9. DETONATE      Only if needed, in a VM or private urlscan. Note cloaking.
    429 10. VERDICT      Weight registration age + form target + domain reading above all else.
    430 11. RESPOND      Report, block, hunt for other recipients, rotate any exposed credentials.
    431 ```
    432 
    433 If a credential was submitted, treat it as compromised immediately: change the password from a different device, revoke active sessions and refresh tokens (MFA relay kits steal the session cookie, so a password change alone is insufficient), re-enrol MFA, and check mailbox rules and OAuth app grants for attacker persistence.
    434 
    435 ---
    436 
    437 ## 13. Quick Reference Table
    438 
    439 | Check | Command |
    440 |---|---|
    441 | Extract host from URL | `python3 -c 'import sys,urllib.parse as u;print(u.urlparse(sys.argv).hostname)' "$URL"` |
    442 | Registrable domain | `python3 -c 'import tldextract,sys;print(tldextract.extract(sys.argv).registered_domain)' "$URL"` |
    443 | Decode punycode | `python3 -c 'print("xn--...".encode().decode("idna"))'` |
    444 | Redirect chain | `curl -sIL --max-redirs 10 "$URL" \| grep -Ei '^(HTTP/\|location:)'` |
    445 | Domain age | `whois "$DOM" \| grep -Ei 'creation\|created'` |
    446 | DNS records | `dig +short A "$DOM"; dig +short NS "$DOM"; dig +short MX "$DOM"` |
    447 | Wildcard DNS test | `dig +short "$(openssl rand -hex 6).$DOM"` |
    448 | Hosting owner | `whois "$(dig +short A "$DOM" \| head -1)" \| grep -Ei 'orgname\|netname'` |
    449 | Cert details | `echo \| openssl s_client -connect "$DOM":443 -servername "$DOM" 2>/dev/null \| openssl x509 -noout -subject -issuer -dates` |
    450 | CT log history | `curl -s "https://crt.sh/?q=%25.$DOM&output=json" \| jq -r '.[].name_value' \| sort -u` |
    451 | SPF / DMARC published | `dig +short TXT "$DOM" \| grep -i spf; dig +short TXT "_dmarc.$DOM"` |
    452 | Email auth verdicts | `grep -Ei '^(authentication-results\|received-spf\|from\|reply-to\|return-path):' sample.eml` |
    453 | Save page HTML | `curl -s --max-time 15 -A 'Mozilla/5.0' "$URL" -o page.html` |
    454 | Form targets | `grep -oEi '<form[^>]*action="[^"]+"' page.html` |
    455 | Typosquat sweep | `dnstwist --registered --mx example.com` |
    456 | File type + hash | `file f; sha256sum f` |
    457 | Macro check | `olevba -a f.docm` |
    458 | PDF actions | `pdfid.py f.pdf` |
    459 | Decode QR | `zbarimg --quiet --raw qr.png` |
    460 
    461 ---
    462 
    463 ## 14. Reporting & Takedown
    464 
    465 | Where | How |
    466 |---|---|
    467 | UK, general public | Forward the email to `report@phishing.gov.uk` (NCSC SERS). Suspicious texts to `7726` |
    468 | UK, financial loss | Action Fraud, `actionfraud.police.uk` or 0300 123 2040. In Scotland, report to Police Scotland on 101 |
    469 | Google Safe Browsing | `safebrowsing.google.com/safebrowsing/report_phish/` |
    470 | Microsoft | `microsoft.com/wdsi/support/report-unsafe-site`, or the Report Phishing add-in |
    471 | APWG | `reportphishing@apwg.org` |
    472 | Hosting provider | `abuse@` for the ASN owner found via `whois <ip>` |
    473 | Registrar | Abuse contact from `whois <domain>` |
    474 | CDN in front of the site | Cloudflare and similar have their own abuse forms, they will pass to origin |
    475 | Impersonated brand | Most banks and large SaaS publish a phishing reporting address |
    476 
    477 Include the full URL, the original headers, timestamps with timezone, and the file hashes. Do not include live credentials.
    478 
    479 ---
    480 
    481 ## Related Notes
    482 
    483 - Hashing cheat sheet 
    484 - pcap-credential-extraction-cheatsheet
    485 - Forensics Cheatsheet
    486 - GitHubDeviceCodePhishing
    487 
    488 ## External References
    489 
    490 - [NCSC — Phishing attacks: defending your organisation](https://www.ncsc.gov.uk/guidance/phishing)
    491 - [RFC 7489 — DMARC](https://datatracker.ietf.org/doc/html/rfc7489)
    492 - [Public Suffix List](https://publicsuffix.org/)
    493 - [crt.sh — Certificate Transparency search](https://crt.sh/)
    494 - [urlscan.io](https://urlscan.io/)
    495 - [dnstwist](https://github.com/elceef/dnstwist)
    496 - [oletools](https://github.com/decalage2/oletools)