Threat Overview
Island researchers disclosed an active, human-operated phishing platform disguised as AI advertising and account-management products. The operation has impersonated ChatGPT, Gemini, Claude, Perplexity, Manus, Meta’s Muse, and related advertising workflows. A “Connect” action opens a browser window drawn inside the real web page; the fake chrome displays a trusted origin such as accounts.google.com or an Okta tenant even though the real browser remains on the phishing site.
The platform is not a transparent adversary-in-the-middle proxy. It locally rebuilds Google, Meta, TikTok, and Okta authentication flows, stores up to three password attempts, fingerprints the device, and lets a human operator select the next MFA prompt in real time. Supported prompts include SMS codes, authenticator codes, Google approval/number matching, QR verification, and Okta push or authenticator challenges. Island observed hundreds of victim submissions and ongoing activity at publication. Reused Next.js, Socket.IO, Railway, and Render infrastructure links the AI-advertising lures to refund and recruitment themes.
References
- Island, “Behind the Connect Button: The Fake AI Ads Campaign,” published October 6, 2026: https://www.island.io/blog/behind-the-connect-button-the-fake-ai-ads-campaign
- The Hacker News, “Fake AI Ads Platform Uses Browser-in-the-Browser Phishing to Steal Google and Okta Credentials,” published October 6, 2026: https://thehackernews.com/2026/10/fake-ai-ads-platform-uses-browser-in.html
- BleepingComputer, “Fake AI ad platforms use browser-in-browser attacks to steal accounts,” published October 6, 2026: https://www.bleepingcomputer.com/news/security/fake-ai-ad-platforms-use-browser-in-browser-attacks-to-steal-accounts/
Impacted Systems
- Identity providers/accounts: Google accounts and Workspace identities, Okta identities, Meta accounts, and TikTok accounts entered into the phishing flows. Microsoft Entra telemetry is relevant when a captured corporate identity is also federated, reused, or followed by access to Microsoft resources; the cited campaign does not claim a native Microsoft sign-in lure.
- Business environments: advertising agencies, media buyers, Google Ads manager/MCC administrators, client advertising accounts, and organizations whose employees use work identities for recruitment or refund lures.
- Endpoints/platforms: Windows, macOS, iOS, and Android browsers; the fake browser adapts its visual chrome to the victim platform.
- Delivery/exposure: invitation emails and links to attacker-controlled domains; public pages commonly hosted on Vercel with Railway or Render backends.
- Prerequisites: user visits the lure, selects Connect, and submits credentials and/or MFA responses. Phishing-resistant, origin-bound authentication materially reduces reusable credential theft.
- Not established: the cited research does not say that every Railway, Render, Vercel, AI-advertising, refund, or recruitment site is malicious.
Why this matters
The campaign is active, targets high-value business spending accounts, and can also capture corporate Google or Okta identities through recruitment lures. Human-controlled MFA prompts defeat simple “one bad password submission” assumptions and may create convincing sequences of failures, retries, and approval prompts. Published domains, backend hosts, API paths, and control vocabulary support immediate Sentinel hunting across email, URL-click, endpoint network, proxy/DNS, and identity telemetry.
Exploitation Status
Confirmed active phishing activity. Island observed hundreds of submissions and stated that the operation remained active when the report was published on October 6, 2026. Researchers attribute operation of the live state machine to human controllers based on exposed source code and observed command behavior. The infrastructure list is a researcher-published IOC set, not a vendor blocklist; commodity hosting domains require exact-host matching and surrounding context.
What this hunt looks for
Published lure and backend domains, campaign API and Socket.IO paths, delivered and clicked URLs, endpoint connections, authentication retries followed by success, new user/IP/device combinations, post-sign-in identity changes, and mailbox persistence.
Required logs
Microsoft 365 Defender EmailEvents, EmailUrlInfo, and UrlClickEvents; Microsoft Defender for Endpoint DeviceNetworkEvents; proxy, DNS, or firewall telemetry; Microsoft Entra SigninLogs and AuditLogs; OfficeActivity; and Google or Okta identity logs where applicable.
Hunt 1 — Published campaign domains in email URL telemetry
let lookback = 30d;
let domains = dynamic([
"museads.ai","advertising-chatgpt.com","ads-team-openai.com","openai-ads.ai","chatgpt-monday-brief.com",
"advertising-gemini.com","gemini-ads.ai","gemini-google-ads.com","ads-claude.com","claude-ads.ai",
"perplexity-advertising.com","manus-meta.im","mcc-account-sync.com","sync-ads-account.com","sync-business.com",
"backend-production-6d75.up.railway.app","museadsback-production.up.railway.app","chatgptadsback-production.up.railway.app",
"geminiback-production.up.railway.app","anthropicadsback.onrender.com","syncgoogleadsback.onrender.com"
]);
EmailUrlInfo
| where Timestamp >= ago(lookback)
| extend Host=tolower(tostring(parse_url(Url).Host))
| where Host in (domains)
| join kind=leftouter (EmailEvents | where Timestamp >= ago(lookback) | project NetworkMessageId, RecipientEmailAddress, SenderFromAddress, Subject, DeliveryAction, EmailDirection) on NetworkMessageId
| project Timestamp, RecipientEmailAddress, SenderFromAddress, Subject, DeliveryAction, Url, Host, NetworkMessageId
| order by Timestamp desc
Hunt 2 — User clicks to published lure infrastructure
let lookback = 30d;
let domains = dynamic(["museads.ai","advertising-chatgpt.com","ads-team-openai.com","openai-ads.ai","chatgpt-monday-brief.com","advertising-gemini.com","gemini-ads.ai","gemini-google-ads.com","ads-claude.com","claude-ads.ai","perplexity-advertising.com","manus-meta.im","mcc-account-sync.com","sync-ads-account.com","sync-business.com"]);
UrlClickEvents
| where Timestamp >= ago(lookback)
| extend Host=tolower(tostring(parse_url(Url).Host))
| where Host in (domains)
| project Timestamp, AccountUpn, ActionType, IsClickedThrough, Url, Host, NetworkMessageId, IPAddress
| order by Timestamp desc
Hunt 3 — Endpoint connections to lure or backend infrastructure
let lookback = 30d;
let domains = dynamic(["museads.ai","advertising-chatgpt.com","ads-team-openai.com","openai-ads.ai","chatgpt-monday-brief.com","advertising-gemini.com","gemini-ads.ai","gemini-google-ads.com","ads-claude.com","claude-ads.ai","perplexity-advertising.com","manus-meta.im","backend-production-6d75.up.railway.app","museadsback-production.up.railway.app","chatgptadsback-production.up.railway.app","geminiback-production.up.railway.app","anthropicadsback.onrender.com","syncgoogleadsback.onrender.com"]);
DeviceNetworkEvents
| where Timestamp >= ago(lookback)
| where tolower(RemoteUrl) in (domains)
| project Timestamp, DeviceName, InitiatingProcessAccountUpn, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl, RemoteIP, RemotePort
| order by Timestamp desc
Hunt 4 — Campaign API and Socket.IO patterns in proxy or web-security logs
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", "")), User=coalesce(column_ifexists("SourceUserName", ""), column_ifexists("user_s", ""))
| where Raw has_any ("/api/create/user", "/api/send/ip", "operator-command", "telegram-command", "socket.io")
| where Raw has_any ("railway.app", "onrender.com", "museads.ai", "advertising-chatgpt.com", "advertising-gemini.com", "ads-claude")
| project TimeGenerated, Src, User, Evidence=substring(Raw, 0, 1800)
| order by TimeGenerated desc
Hunt 5 — Published-domain click followed by Entra sign-in failures or success
let lookback = 30d;
let domains = dynamic(["museads.ai","advertising-chatgpt.com","ads-team-openai.com","openai-ads.ai","chatgpt-monday-brief.com","advertising-gemini.com","gemini-ads.ai","gemini-google-ads.com","ads-claude.com","claude-ads.ai","perplexity-advertising.com","manus-meta.im"]);
let Clicks = UrlClickEvents
| where Timestamp >= ago(lookback)
| extend Host=tolower(tostring(parse_url(Url).Host))
| where Host in (domains) and isnotempty(AccountUpn)
| project AccountUpn=tolower(AccountUpn), ClickTime=Timestamp, Url, ClickIP=IPAddress;
SigninLogs
| where TimeGenerated >= ago(lookback)
| extend AccountUpn=tolower(UserPrincipalName)
| join kind=inner Clicks on AccountUpn
| where TimeGenerated between (ClickTime .. ClickTime + 4h)
| project TimeGenerated, UserPrincipalName, ResultType, ResultDescription, IPAddress, AppDisplayName, ClientAppUsed, DeviceDetail, LocationDetails, ConditionalAccessStatus, ClickTime, Url, ClickIP
| order by TimeGenerated desc
Hunt 6 — Rapid authentication failures followed by success from a different IP
let lookback = 14d;
let Failures = SigninLogs
| where TimeGenerated >= ago(lookback) and ResultType != 0
| summarize FailureCount=count(), FailureIPs=make_set(IPAddress, 20), FirstFailure=min(TimeGenerated), LastFailure=max(TimeGenerated) by UserPrincipalName, bin(TimeGenerated, 15m);
let Successes = SigninLogs
| where TimeGenerated >= ago(lookback) and ResultType == 0
| project SuccessTime=TimeGenerated, UserPrincipalName, SuccessIP=IPAddress, AppDisplayName, DeviceDetail, LocationDetails, AuthenticationDetails;
Failures
| join kind=inner Successes on UserPrincipalName
| where SuccessTime between (LastFailure .. LastFailure + 30m)
| where FailureCount >= 2 and set_has_element(FailureIPs, SuccessIP) == false
| project UserPrincipalName, FailureCount, FailureIPs, FirstFailure, LastFailure, SuccessTime, SuccessIP, AppDisplayName, DeviceDetail, LocationDetails, AuthenticationDetails
| order by SuccessTime desc
Hunt 7 — Successful sign-in from a new user/IP or device combination
let baseline = 30d;
let recent = 2d;
let Known = SigninLogs
| where TimeGenerated between (ago(baseline) .. ago(recent)) and ResultType == 0
| extend DeviceId=tostring(DeviceDetail.deviceId)
| summarize by UserPrincipalName, IPAddress, DeviceId;
SigninLogs
| where TimeGenerated >= ago(recent) and ResultType == 0
| extend DeviceId=tostring(DeviceDetail.deviceId), Browser=tostring(DeviceDetail.browser), OS=tostring(DeviceDetail.operatingSystem)
| join kind=leftanti Known on UserPrincipalName, IPAddress, DeviceId
| project TimeGenerated, UserPrincipalName, IPAddress, DeviceId, Browser, OS, AppDisplayName, LocationDetails, ConditionalAccessStatus, RiskLevelDuringSignIn
| order by TimeGenerated desc
Hunt 8 — Identity or privilege changes soon after a suspicious sign-in
let lookback = 14d;
let RecentSignins = SigninLogs
| where TimeGenerated >= ago(lookback) and ResultType == 0
| project UserPrincipalName=tolower(UserPrincipalName), SigninTime=TimeGenerated, SigninIP=IPAddress;
AuditLogs
| where TimeGenerated >= ago(lookback)
| where OperationName has_any ("Add member to role", "Add app role assignment", "Consent to application", "Update user", "Update authentication methods", "Add service principal", "Add owner to application")
| extend ActorUPN=tolower(tostring(InitiatedBy.user.userPrincipalName)), ActorIP=tostring(InitiatedBy.user.ipAddress)
| join kind=inner RecentSignins on $left.ActorUPN == $right.UserPrincipalName
| where TimeGenerated between (SigninTime .. SigninTime + 4h)
| project TimeGenerated, OperationName, Result, ActorUPN, ActorIP, SigninTime, SigninIP, TargetResources, AdditionalDetails
| order by TimeGenerated desc
Hunt 9 — Post-compromise mailbox forwarding or inbox-rule changes
let lookback = 14d;
OfficeActivity
| where TimeGenerated >= ago(lookback)
| where OfficeWorkload == "Exchange"
| where Operation in ("New-InboxRule", "Set-InboxRule", "Set-Mailbox", "UpdateInboxRules")
| extend Raw=tostring(pack_all())
| where Raw has_any ("ForwardTo", "ForwardingSmtpAddress", "RedirectTo", "DeleteMessage", "MarkAsRead")
| project TimeGenerated, UserId, Operation, ClientIP, ResultStatus, Parameters, RawEvent=substring(Raw, 0, 1800)
| order by TimeGenerated desc
Hunt 10 — DNS and security-device fallback for exact campaign hosts
let lookback = 30d;
let domains = dynamic(["museads.ai","advertising-chatgpt.com","ads-team-openai.com","openai-ads.ai","chatgpt-monday-brief.com","advertising-gemini.com","gemini-ads.ai","gemini-google-ads.com","ads-claude.com","claude-ads.ai","perplexity-advertising.com","manus-meta.im","backend-production-6d75.up.railway.app","museadsback-production.up.railway.app","chatgptadsback-production.up.railway.app","geminiback-production.up.railway.app","anthropicadsback.onrender.com"]);
union isfuzzy=true DnsEvents, CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Host=tolower(coalesce(column_ifexists("Name", ""), column_ifexists("DestinationHostName", ""), column_ifexists("RequestURL", "")))
| where Host in (domains) or Raw has_any (domains)
| project TimeGenerated, Host, Evidence=substring(Raw, 0, 1600)
| order by TimeGenerated desc
Detection Notes
- Hunts 1–5 are the highest-value campaign-specific detections. Exact lure-domain delivery or click plus endpoint/proxy API evidence substantially raises confidence.
- Railway, Render, Vercel, Socket.IO,
api.ipify.org, andipapi.coare legitimate services. Match exact published backend hosts and combine them with lure domains or campaign paths; do not block entire shared-hosting domains. - The operator may replay Google or Okta credentials outside Microsoft Entra. Entra-only customers can see downstream Microsoft access only if the same identity is federated, reused, or used to reach Microsoft applications.
- Push fatigue or repeated MFA events may occur entirely in Google or Okta logs. Without those connectors, Sentinel cannot see the decisive identity-provider sequence.
- A click without a sign-in does not establish credential submission. Conversely, a credential can be submitted from a personal/unmanaged device that has no endpoint or click telemetry.
- New-IP/device and mailbox-rule hunts are post-compromise behavioral detections, not unique signatures of this campaign. Confirm the published-link exposure or other identity evidence before attribution.
UrlClickEvents,EmailUrlInfo, andEmailEventsavailability depends on Defender for Office 365 licensing and connector configuration.