Someone in a server I was in posted a message with a Discord invite link.
On joining, you are greeted with an embed asking you to verify to gain access to the rest of the Discord server channels.
This is one of the most common scams out there on Discord, aka verification scam.
The invite redirected to a URL shortener, which redirected to this:
The
data parameter is base64. Decode it and you get garbled bytes at the start, then valid JSON:json
The
clientId field resolves to MEE6's real Discord app ID. The domain field is the phishing subdomain. The expires field is a Unix timestamp in milliseconds. This link expired roughly 48 hours after it was generated.When a link expires, the server decodes the
data parameter, checks the timestamp, and issues a 302 to captcha.bot, a redirect to the legitimate verification service. A dead link looks like a session timeout.The page at
/verify is a pixel-perfect clone of captcha.bot, the real Discord verification service run by Privy.gg.It pulls the server name, icon, and member count from the decoded JSON and renders them into the template. Every link in the footer points to the real captcha.bot. The "Get help" and "Terms" buttons in the card go to real captcha.bot pages. There is a real ad unit. There is a real Cloudflare Insights beacon. The Cloudflare analytics token is
785407d363a0439084cd43bb6aa1c02f, which ties every domain in this operation to the same Cloudflare account.Before the user does anything, the page loads
collect.js.collect.js is a 5k lines of obfuscated script that runs silently on page load. It collects a full browser fingerprint before the user clicks a single thing.It gathers twelve categories of data:
| Category | What gets sent |
|---|---|
| screen | Resolution, pixel ratio, orientation, color depth |
| navigator | User agent, platform, CPU cores, device memory, plugins |
| audio | AudioContext fingerprint (a float unique to your hardware) |
| canvas | Rendered pixel data from a hidden canvas element |
| webRTC | Full SDP offer including codec capabilities |
| gpu | GPU model, feature flags, WebGPU limits |
| webgl | Renderer string, extension list, precision values |
| performance | JS heap size, stack depth, loop timing |
| storage | localStorage, sessionStorage, indexedDB availability |
| intl | Timezone, locale, epoch offset |
| window | Chrome APIs present, computed styles, media query results |
| events | Two fingerprint hashes derived from canvas and unknown sources |
After collecting all of this, it runs a SHA-256 proof-of-work challenge with a difficulty of 1000 iterations. This exists to verify the client is a real browser capable of running WASM-level crypto. A headless bot or simple HTTP request fails here.
The fingerprint is then zlib-deflated, base64-encoded, and AES-GCM encrypted with a hardcoded 32-byte key before being POSTed to
/collect:js
The server uses this to decide: real human with a genuine browser, or a researcher. If you pass, you get a session cookie and the page proceeds. If you don't, or if your session cookie is missing when you hit the login domain, you get redirected to the real captcha.bot.
I decrypted a captured payload. The fingerprint included
timezone: Asia/Tokyo from a Windows machine behind a US residential proxy.The script also has an anti-debugging layer. Right-click is disabled. F12, Ctrl+Shift+I, Ctrl+Shift+J, and Ctrl+U are all intercepted and swallowed. If the debugger is detected, the script triggers an IP blacklist and redirects you to the real captcha.bot. Analyzed from a flagged IP, the site looks completely legitimate.
js
Anti anti-debugger does bypass this.
When you click "Login to verify", a new window opens. The address bar shows
about:blank.The window title is set immediately via JavaScript:
js
In the taskbar and in the tab strip, the window looks like a legitimate Discord authorization prompt. The address bar reads
about:blank, which looks like a normal OAuth popup.Inside the window is a single fullscreen iframe:
The
client_id is 512333785338216465, which is the real captcha.bot's Discord application. The redirect_uri points to the real captcha.bot callback. Everything looks correct except the domain serving it, which is login.process-age.top instead of discord.com.The
state parameter is the guild JSON, base64-encoded twice, then reversed as a string. Cosmetic obfuscation.login.process-age.top is not a static phishing page but a full adversary-in-the-middle proxy of Discord's entire web interface (discord.com/login).Every request the browser makes to what it believes is Discord is intercepted, forwarded to the real Discord servers, and returned through the proxy. The page looks and works exactly like Discord's OAuth flow because it is Discord's OAuth flow, with one extra hop in the middle.
Returns a real Discord API response:
json
Real Discord JavaScript bundles are served through the proxy. A real WASM module is loaded for Discord's cryptographic operations. Nothing in the page's behavior is faked because nothing needs to be.
The proxy reads the responses before forwarding them. When Discord's API returns
{"token": "..."} after a successful login, the proxy logs it and passes it to the victim. The victim sees a successful authorization. The attacker has the token.The proxy also establishes a WebSocket connection to Discord's Remote Auth gateway, which is the same protocol used for QR code logins on mobile.
This is a proxied connection to
wss://remote-auth-gateway.discord.gg/?v=2. The handshake:json
The proxy generates an RSA key pair, sends the public key to Discord, decrypts the nonce challenge, and gets a fingerprint back. That fingerprint represents a pending Remote Auth session at
discord.com/ra/<fingerprint>.If a logged-in Discord mobile user scans a QR code containing that URL, Discord sends the victim's token directly to the proxy's waiting WebSocket connection. No credentials entered, no OAuth flow, no warning. Token delivered by Discord itself.
This runs in parallel with the standard email and password login flow. Two token extraction paths active simultaneously.
While I was analyzing the original domain, it expired. The link died and started bouncing to captcha.bot. Within hours, the same campaign was running on a new domain.
Both domains got the same two certificates on the day of registration: one from Let's Encrypt E7 and one from Sectigo DV E36. Same tooling, same pipeline. The
data parameter in the new URL had "domain": "login.verification-process.icu" and a fresh expiry timestamp. Everything else was identical.The member count in the new URL was 16,717. The old one was 13,956. In roughly eleven hours, the server had gained 2,761 members.
The victim successfully verified. The bot gave them access to the server (or so they think). Nothing went wrong from their perspective.
Every visible element is borrowed from a real service.
The verification page is a real service's interface. The OAuth prompt is Discord's real interface, served through a proxy. The window title is a real URL. The redirect lands on the real captcha.bot. The whole chain reads as legitimate because most of it is.
The only custom components are the verify page wrapper and the proxy layer. Everything else is authentic material used to create a plausible container for the attack.
The time-limited links mean old URLs die cleanly with a legitimate-looking redirect rather than a broken page. The fingerprinting gate keeps automated scanners out. The anti-debugger redirects researchers to the real service. By the time a security researcher has a working link and a non-flagged IP, the link has often already expired.
If you run into this campaign:
- Guild ID and OAuth app can both be reported to Discord Trust and Safety at dis.gd/report
- Domains can be reported to Cloudflare abuse, since both are behind Cloudflare (analytics token
785407d363a0439084cd43bb6aa1c02f) - Short links through is.gd have an abuse form
- The domains themselves can be reported to their registrars via ICANN's UDRP process
The infrastructure rotates fast. By the time a domain is reported and actioned, they're already on the next one. The useful targets are the guild ID and the OAuth application, which are static across the entire campaign.