慢雾安全团队披露,近期收到一名 Fomo 用户求助。该用户在 Fomo 网页端查看 MOONLET 代币详情时,点击页面展示的项目官网入口进入 voltage.family,并按提示完成所谓“人机验证”。随后,其账户内约 4.8 万美元资产被转走。用户表示,整个过程未连接钱包、未进行链上授权,也未输入助记词或私钥;其通过 Google 登录 Fomo,且账号未开启二次验证。
慢雾团队分析相关页面及脚本后发现,所谓“人机验证”会引导用户添加并点击恶意书签。书签内嵌 JavaScript 代码,目标是在 Fomo 页面读取浏览器存储、提取登录令牌,并将数据发送至外部地址。MistEye 已第一时间通过情报推送与客户告警通道同步风险。

钓鱼站点首页伪装成交易产品页面,并设置访问入口。

一、钓鱼入口与伪造验证
根据用户补充截图,当时其正在查看 Fomo 上的 MOONLET 代币详情页。红箭头标出了代币名称旁的官网入口,右侧项目介绍区域也有“官网”按钮。结合反馈,本次访问钓鱼站点的入口来自该代币详情页展示的外部链接。

Fomo 的 MOONLET 代币详情页及项目官网入口。
用户访问的站点为 voltage.family。进入 /app 页面后,页面展示九个图标,要求用户找到“四条腿的动物”,将其拖到浏览器书签栏,再连续点击该书签三次。拖拽时,页面还会显示操作引导,让用户误以为这是正常验证步骤。

伪造的人机验证页面。

页面引导用户将图标拖入浏览器书签栏。
最终添加的书签名为 Triple click to verify。它保存的并非普通网页地址,而是一段以 javascript: 开头的代码,即 bookmarklet。用户点击这种书签时,代码会在当前页面中执行;攻击者正是借此诱导用户在已登录的 Fomo 页面运行恶意脚本。此前,慢雾安全团队也曾分析过一起 Discord Token 窃取案例,揭示浏览器恶意书签的盗取手法。
二、恶意书签投递与执行
页面上的动物图标会随机出现,但用户选哪个图标并不影响最终添加的恶意书签内容。fomo-track.js 读取页面内嵌的 BMCODE,替换访客标识后,将所有 a.mini-bookmark 元素的 href 设置为同一段书签代码。替换动作会在页面 DOM 变化、定时器触发、鼠标按下和拖拽开始时反复执行,以覆盖页面原本的书签链接。正常加载页面时,九个图标均指向这段代码。所谓“识别动物”,实际是在为恶意书签的投递提供借口。


书签实际内容为 javascript 代码。
验证后的 /app/live 页面继续模仿交易终端。界面注明市场数据为模拟数据,连接钱包按钮仅弹出提示。结合前面的引导过程,这些页面主要是为了把钓鱼站点伪装得更可信。

验证后展示的模拟交易终端。
三、本地数据采集与凭证提取
书签代码压缩后有八千多字符。最外层使用 location.hostname.includes('fomo.family') 判断当前页面主机名,符合条件才继续执行。这里用的是字符串包含判断,而不是严格域名匹配,因此凡主机名中包含 fomo.family 的页面都可能满足条件,不能理解为只会在 fomo.family 及其合法子域下执行。这也说明,单纯打开钓鱼网站并不等于已经执行后续采集逻辑。脚本需要在符合条件的页面上下文中运行,才能读取该页面可访问的存储。
采集函数 v() 首先遍历 localStorage 和 sessionStorage。它按键名匹配 privy、wallet、share、secret、seed、mnemon、turnkey、dynamic、fomo、mfa 等关键词;键名未命中时,再检查值的前 300 个字符。命中的数据最多保留 4000 个字符,存入对象 o。

随后,脚本调用 indexedDB.databases() 获取当前上下文可访问的数据库,逐一遍历 object store,通过 getAll() 读取记录,并按“库名/表名”保存。该流程设置了 4 秒超时,避免单个采集步骤长时间阻塞。采集范围同时覆盖登录凭证和钱包相关数据。在嵌入式钱包场景下,这些数据可能涉及钱包访问、恢复和密钥材料;一旦泄露,攻击者可能进一步控制钱包。
脚本优先查找 privy:token 和 privy:refresh_token,并通过 y() 处理可能包裹在 JSON 中的 token 或 accessToken 字段。如果没有找到固定键名,则逐步放宽匹配范围,查找其他 token 字段,同时排除 id_token、identity 等不属于目标令牌的字段。最后,脚本还会尝试从值中提取以 eyJ 开头的 JWT 片段。该兜底方式只能选出疑似令牌,并不保证结果有效。
取得访问令牌后,脚本携带 Bearer 令牌、Privy 应用 ID 和客户端 ID,请求 https://auth.privy.io/api/v1/users/me,并读取响应中的 mfa_methods(或 mfaMethods)字段,以判断账号已绑定的二次验证方式;该数组是否为空,直接决定后续 MFA 分支是否执行。脚本同时包含返回 401 时借助刷新令牌重试请求的处理。脚本还包含收到 401 后向 /sessions 提交刷新令牌的逻辑:以 refresh_token 为请求体,代码先对响应调用 .json() 解析,再取其中的 token 字段作为新的访问令牌,取得后重新请求 /users/me。
与 MFA 相关的代码会创建一个 4×4 像素、透明度为 0.01 的 iframe,加载 Privy 的 embedded-wallets 页面,再通过 postMessage 发送事件请求。消息按随机请求 ID 对应,默认等待 6 秒;就绪检查以 100 毫秒为间隔,最多等待 8 秒。脚本尝试以 TOTP 为方法初始化注册,并从响应中提取 secret 或 totpSecret。若取得密钥,它会在本地按 30 秒时间步长、HMAC-SHA1 和动态截断计算六位验证码,再提交注册请求。该逻辑的意图是将攻击者掌握的 TOTP 密钥用于账户验证。
需要区分“发起注册”与“注册成功”。初始化返回密钥,不代表服务端已经完成绑定;脚本将包含 already、exist、enrolled、duplicate 等字样的错误视为可继续处理,也不能证明新密钥已生效。代码中,这段 MFA 逻辑的触发条件很明确:只有当 /users/me 返回的 mfa_methods 非空,也就是账号已绑定至少一种二次验证方式时,脚本才会创建 iframe、调用 privy:mfa:init-enrollment 获取 TOTP 密钥并提交注册;如果该数组为空,整段 MFA 逻辑都不会执行。用户称其账号原先未开启二次验证,因此这部分很可能没有触发。但这不影响本地数据和凭证的采集、外传,因为 ls 与 idb 是独立通道。
四、凭证泄露如何危及钱包资产
本次攻击针对的是用户已登录的 Fomo 页面及其嵌入式钱包访问凭证。恶意书签执行后,会采集 Privy 访问令牌、刷新令牌及钱包相关本地数据,并发送至攻击者控制的地址。这些凭证涉及钱包的访问与恢复流程,泄露后可能被用于接管钱包,进一步导出私钥或发起交易。因此,即使用户没有主动连接钱包、签署链上授权或输入助记词,也可能面临资产被盗的风险。当前展示的脚本主要负责采集和外传,后续具体采用哪条路径完成资产转移,还需结合进一步调查结果确认。
五、数据外传设计
发送前,脚本把采集结果组织为 ls、idb 和 mfa 三部分,分别对应 localStorage、IndexedDB 记录以及 MFA 方法和处理结果。再进行 base64url 编码。编码后超过 11000 个字符时,脚本先删减 IndexedDB 记录,保留与 privy、share、wallet、key、secret 相关的内容;若仍超限,则将本地存储部分缩减为 privy:token、privy:refresh_token 和 mfa-session-storage 三个关键字段。流程另设 20 秒兜底发送,避免一直等待采集结束。外传目标为 noisy-heart-5856.zmfkc29.workers.dev。脚本采用两种方式发送:一是在取得 TOTP 初始化密钥后,通过 fetch 提前发送,密钥放在 initsecret 参数中;二是通过 location.href 跳转,把令牌、采集数据以及脚本保留的 TOTP 密钥放入 URL 参数。参数包括 t:访问令牌;r:刷新令牌;vid:访客标识;site:站点标识;Is:经 base64url 编码的采集数据;initsecret:初始化阶段取得的 TOTP 密钥;totp:脚本最终保留的 TOTP 密钥。
对该地址进行非破坏性测试时,根目录、未知路径以及携带凭证参数的请求都返回 302,并跳转回 voltage.family;POST /visit 返回 204。该响应符合“接收数据后把用户送回钓鱼站点”的设计,但仅凭响应本身无法确认服务端是否保存了数据。fomo-track.js 开头还保留了一段注释,描述 tracker → Cloudflare Worker → panel → Telegram 的数据链路,并称复用了旧验证页的中继。这属于样本作者留下的线索,尚未获得后台面板或 Telegram 侧证据支持。注释内容为:/* voltage tracker -> cloudflare worker -> panel -> telegram (same relay the old captcha page uses; no keys in this file) */。
六、MistTrack 资金分析
根据受害者提供的信息,共 45,106.85 USDC 被黑客分三次提现到黑客地址 D783JqupQ2FYhkxUfGEd1gaFEoAJDXRX1NEb4aVH2hZ6。慢雾旗下链上追踪与反洗钱工具 MistTrack 对该地址进行反洗钱分析。黑客地址 D783Jq…hZ6 从 9 月 17 日开始活跃。在受害者资金进入前,该地址已经存在较为频繁的 USDC ↔ SOL 资金交互,推测存在其他受害者。

链上可以观察到多笔金额相近的 USDC 兑换为 SOL,也存在反向兑换 SOL → USDC,兑换多数通过 Jupiter Aggregator 完成。兑换后的 SOL 并未长期留存在该地址,而是被进一步发送至大量不同的中间地址,例如 HGSe...Ucim、3XyKap...gXVj 等地址。同时,也可以观察到部分 USDC 被直接拆分发送至多个地址,例如向 HGSe...、2th9... 等地址连续转出多笔 USDC 和 SOL。

该地址还通过 Relay.link 将 SOL 转换为其他链上的资产,例如:0.6 SOL → 0.024 ETH,目标 Ethereum 地址 0x77bd56dfef9530ba61962a420e34a0dbd55de51c;0.59 SOL → 0.025 ETH,目标同一 Ethereum 地址;0.29 SOL → 0.012 ETH,目标同一 Ethereum 地址。

0.29 SOL → 0.00039 BTC,进入 Bitcoin 地址 bc1q6j54xfvlgqjw6mq2ja3pym3qrezp8k39aeftxh。

除向外跨链外,该地址还接收了通过 deBridge 跨链转入的资金。其中,一笔由 Robinhood 链上的 USDG 兑换为 Solana 上的 USDC,另一笔由 BNB Chain 上的 USDC 跨链转入。



该地址向 Privacy Cash Pool 共转入 1568.33 SOL,也是该地址最主要的洗钱出口。

此外,部分资金最终流向博彩平台相关地址。例如,该地址曾将部分 USDC 兑换为 SOL 后转入 Stake 平台,也有部分 SOL 直接转入 Stake 平台。


后续资金还多次进入 Winna 相关地址,包括向 Winna deposit 地址转入 11.34 SOL 和 1,500 USDC,以及从 Winna hot wallet 接收 7,199.81 USDC。


从 D783Jq…hZ6 的整体资金行为来看,该地址呈现出较为明显的多层拆分、资产兑换、跨链转移和平台充值特征。我们将持续关注相关地址的后续资金转移。
七、IOC
钓鱼页面与数据外传:hxxps://voltage[.]family/app;hxxps://voltage[.]family/app/live;noisy-heart-5856[.]zmfkc29[.]workers[.]dev。
样本 SHA256:index.html 24df225d70e399fa30a2d0761e8645e339d99a0f751af6f9e5e00ab555e2fa07;fomo-track.js 6d771096e442b36b0719a4e07b19c62a5a985005e8a07ab5d50dd6ca59c4ad06;VerifyRoute d5576bb50cb299d0c48657ed0cfd3f03b4c2313db8b37abcee57f779d70f7f18;LiveTerminal 193b305baf9548a6dded341f3dd613a811e4604c7dfa4447219664f6c43818f8。
八、总结与安全建议
本次分析发现,攻击者将恶意 JavaScript 包装成浏览器书签,并用“人机验证”引导用户在已登录的 Fomo 页面执行。其目标包括 Privy 登录凭证和钱包相关本地数据,风险涉及嵌入式钱包控制权及资产安全。用户没有主动输入私钥或签署链上授权,并不意味着钱包未受影响。从样本看,攻击者通过恶意书签采集并外传凭证,为后续接管钱包提供条件。用户反馈损失约 4.8 万美元;本次事件最终通过私钥导出还是钱包签名等方式完成转移,仍需进一步核实。
给用户的建议:正常的人机验证不需要把网页图标拖进书签栏,再到钱包或交易页面点击执行。遇到这类要求,应立即停止操作。定期检查书签栏,删除来历不明的 javascript: 书签,并为重要账号开启平台支持的多因素验证。如果已经执行过可疑书签,应从可信设备访问官方渠道,优先撤销异常会话和相关授权,检查并移除陌生的验证器,同时联系平台协助处置。通过 Google 等第三方身份登录的用户,也应检查对应身份账号的安全状态。不要只依赖修改密码,现有会话和刷新令牌是否同步失效,需要以平台的实际机制为准。如有证据表明助记词或私钥已经泄露,应使用全新生成的钱包转移剩余资产。保留可疑页面地址、书签内容、操作时间和交易记录,有助于后续调查。
给项目方的建议:对 MFA 新增、替换、恢复以及资金转移等敏感操作设置独立的验证和风控要求,避免只凭已有会话就完成安全设置变更。向用户提供会话查看与撤销入口,对异常设备、来源和 MFA 变更及时告警,并确保服务端能够按需撤销相关凭证。同时检查客户端存储的敏感数据范围,减少长期有效凭证的暴露。对外链、用户生成内容及站内引导加强审查,明确提醒用户不要在已登录页面执行陌生书签或代码。若发现类似页面,可保留线索并向慢雾安全团队反馈。
