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-identification.md (21705B)


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