Seventy-one Google Ads campaign IDs. Around 850 paid landing pages. Twenty-six lookalike ChatGPT destinations, inside a three-month observation window that closed in August. That is the advertising budget behind a phishing platform whose merchandise is advertising accounts, and the loop closes on itself: the accounts it takes pay for the ads that find the next person to take one from.
Oleg Zaytsev and Ofek Ronen of Island published the teardown on October 6. The pages sell campaign optimisation, spend audits and early-access "connect your business account" programs for Gemini, Claude, ChatGPT, Perplexity, Meta Muse and Manus. Click Connect and a browser window appears, drawn inside the browser you are already using. Its address bar reads accounts.google.com, or your own Okta tenant. The real browser never left the attacker's domain, and a human operator is watching the form fill in.
Who is in range, and who can close the tab
If nobody at your company signs into an advertising platform, this one is not yours. The lure is specific: agency staff, media buyers, and the person who administers a manager account. A general-purpose credential-phishing warning to all staff will not change your exposure here, because the kit is not fishing for mailbox logins.
You are in range if anyone at the company holds a Google Ads, Meta Business Manager or TikTok Ads login. Those accounts carry a saved payment method, a usable balance, and in an agency the manager-level access that reaches down into every client underneath. The loss arrives as spend. An operator with your ad account runs campaigns against your card, or resells the login: aged Google Ads accounts list at $200 to $270 on the Telegram markets Island tracked. The bookkeeper notices a card charge nobody recognises; the marketing lead opens a dashboard full of campaigns they did not create. Both of those come days after the credential was typed.
Timing is the other reason to read this today. Island saw museads.ai go up on September 16, eight days after Meta announced Muse. The operators ship a landing page for a product the week it is announced, while the real signup flow is still unfamiliar to everyone and nobody has a bookmark for it yet. Every AI vendor launch is a scheduled opportunity for this crew.
What the Connect button draws
Browser-in-the-browser is four years old. mr.d0x published the technique in March 2022: build a convincing single-sign-on popup out of HTML and CSS instead of calling window.open, and the fake window carries whatever address you want to print in it. The author named the one hard requirement at the time, and it still holds. The victim has to be standing on your page first.
Paid search solves that requirement. It also explains the production values. The kit adapts its fake chrome to Windows, macOS, iOS and Android, with separate treatments for the Safari URL pill and Chrome custom tabs, and Island found a comment in the source explaining why the frosted-glass effect mattered: "Real iOS Safari and Chrome custom tabs use frosted toolbars. Without this, the chrome looks painted-on and gives away the fake." The implementation is a backdrop-filter: saturate(180%) blur(20px) away from convincing.
What a drawn window cannot do is leave the page. It is a positioned element inside the viewport, so dragging it toward the taskbar stops dead at the edge of the content area. That makes the drag test worth teaching to the people who hold ad logins. It is faster than reading a URL and it does not ask them to spot a homoglyph.
There is a person on the other end
The part that matters for your response plan is the live Socket.IO channel the page holds open to the operator, who drives the victim through whatever the real login asks for next. Island recovered the command vocabulary from the exposed source.
/password- reject what was typed and ask again. The kit's state machine keeps three separate password fields,password_one,password_twoandpassword_three, because people try their other passwords when told the first was wrong./2fa,/authApp,/googlePrompt,/googleQrVerify,/verifyTap,/oktaApprove,/oktaAuthApp- render the exact second-step screen the real provider just presented to the operator, who is logging in as the victim in another window./wrong2fa- tell the victim the code was wrong, and collect another one./ban- blank the page for a visitor the operator does not want, which is how a researcher or a scanner gets served nothing.
Two operational consequences follow. The first is that a one-time code has no shelf life to protect: it is relayed and spent within seconds, so "we caught it the next morning" is not a containment story. The second is a help-desk signal worth writing into your intake script. A user reporting that a login kept telling them their code was wrong, and that they entered several, is describing /wrong2fa and /password. Treat that call as a confirmed credential compromise and start the reset, rather than as somebody fat-fingering a six-digit number.
Triage the reported link without opening it in a real browser
When someone sends you the URL, you need a verdict in a couple of minutes, and the kit leaves a fingerprint in the page it serves. Fetch the markup and its first-party bundles from a disposable host and count the tells.
# Triage a reported URL from a throwaway VM. Never from a workstation
# that holds a session you care about.
URL="https://suspicious-ads-portal.example/"
curl -sSL --max-time 20 -A 'Mozilla/5.0' "$URL" -o page.html
# Pull the first-party scripts the page loads.
grep -oE 'src="[^"]+\.js[^"]*"' page.html | cut -d'"' -f2 | sort -u \
| while read -r js; do
curl -sSL --max-time 20 "$(python3 - "$URL" "$js" <<'PY'
import sys, urllib.parse; print(urllib.parse.urljoin(sys.argv[1], sys.argv[2]))
PY
)"
done > bundles.js
# Count the kit's fingerprint across the page and its bundles.
grep -ohiE 'password_(one|two|three)|/api/(create/user|send/ip)|socket\.io|operator-command|telegram-command|backdrop-filter[^;"]*|[a-z0-9-]+\.(up\.railway\.app|onrender\.com)' \
page.html bundles.js | sort | uniq -c | sort -rn
How to read the output
- Three numbered password fields on a single sign-in form has no legitimate explanation. On its own it is enough to call the page malicious.
/api/create/userand/api/send/ipare the kit's own record-creation and fingerprint-submission endpoints. Island traced one backend host across 73 archived scans on 25 separate page domains between May 27 and June 20, so the paths survive the domain rotation that defeats blocklists.- A Socket.IO client plus
operator-commandortelegram-commandevent names means the page is interactive. Assume everything typed into it reached a person. - A
.up.railway.appor.onrender.comcallback is the hosting pattern Island observed. Those platforms host ordinary applications too, so treat this one as corroboration alongside any of the above rather than as a verdict. - No matches is not a clean verdict.
/banexists to serve a blank page to the wrong visitor. If the user described a login window, keep going and reset the credential anyway.
Find the rest of the clicks in your egress logs
One report means other people saw the same ad. The campaign was delivered through paid search, so it arrived in working hours, to the exact job roles you would expect, and nobody was warned. Run the retrospective across the window the ad ran.
# Zeek, default TSV logs. Sources that hit the kit's API paths or opened a
# Socket.IO channel to a Railway/Render backend.
zeek-cut -d ts id.orig_h method host uri status_code < http.log \
| awk -F'\t' '
$4 ~ /\.up\.railway\.app$|\.onrender\.com$/ ||
$5 ~ /^\/api\/(create\/user|send\/ip)|\/socket\.io\//'
# Same hunt against a forward proxy or Cloudflare Gateway HTTP export.
jq -r 'select(.RequestURI | test("/api/(create/user|send/ip)|/socket\\.io/"))
| [.Datetime, .SourceIP, .HTTPHost, .RequestURI] | @tsv' gateway_http.log
Every source that comes back needs the same treatment as the person who reported it: password reset, every session revoked, and an inventory of which advertising accounts that identity can reach. The identity provider's own sign-in log is the second half of the picture. You are looking for a successful interactive sign-in from an address that is not the user's, inside minutes of the proxy hit, because that is the operator finishing the login the victim started.
The second step decides whether any of this works
Every command in that vocabulary exists to move a secret from the victim's screen to the operator's. A time-based code, an SMS, a push approval and a QR tap are all transferable, which is why the kit has a screen for each one. An origin-bound passkey or a security key is not transferable, and the reason is in the specification rather than in anyone's product claims.
The W3C Web Authentication specification requires the relying party to verify "that the origin in the clientDataJSON matches the expected origin" before it accepts an assertion. The browser writes that origin itself, from the real top-level document. A page served from gemini-ads.ai cannot obtain an assertion that Google will accept for accounts.google.com, no matter what its painted address bar says, and the operator has nothing to relay because the authenticator never produced anything.
In Google Workspace that control is one setting. Under Security, Authentication, 2-Step Verification, the allowed methods can be restricted to security keys and passkeys rather than left open to "any" second step. Scope it to an organisational unit holding the accounts that administer advertising, and you have taken this entire campaign out of play for the identities that matter most, without a fleet-wide rollout. Check enrolment in the admin console before you enforce it, and keep a registered backup authenticator per user so enforcement day is not an outage.
Pull the user list on every ad account you own
Do the access review first, because it tells you whether somebody already succeeded. In Google Ads it lives under Admin, Access and security: read every account listed, drop the agency contractor who left in the spring, and question any Administrative or Email-only entry you cannot name. Then check the manager links, because a hijacked account is often linked upward to an attacker's manager account rather than given a new user, and that link keeps control after a password change. Do the same walk through Business settings in Meta Business Manager for people and partners. Thirty minutes across your ad platforms today, then the Workspace setting above for the handful of people who administer them this week.
If you want a second set of eyes on which identities in your tenant can still be phished with a code, that mapping is the first thing we do on an identity assessment, and the ad platforms are usually the accounts nobody included in scope.
Need help hardening your identity infrastructure?
We assess Active Directory and Entra ID environments for the misconfigurations attackers actually exploit, and we map which identities can still be phished with a code. Book a session to discuss your environment.
