当扫描器漏掉攻击:Cloudflare 客户端安全如何保护店铺
Cloudflare 用图神经网络加 LLM 复核,在真实流量里抓到四个恶意 JavaScript 行动、共八个载荷——其中七个在 VirusTotal 上完全不存在。文章逐个拆开它们的门控、斗篷与后门,并给出失陷指标与给防御者的四条经验。
中文
复制

一家现代店铺可能看起来完全健康,底下却有恶意 JavaScript 在干活:抽走联盟佣金、劫持搜索和点击、篡改分析数据,或者向远程服务器询问下一步该执行什么。页面能加载,商品能显示,结账能用,而浏览器可能正在悄悄做站点所有者从未授权的事。
这正是我们的客户端安全机器学习(ML)模型要暴露的盲区。本文跟随四个行动、共八个载荷,它们都是我们的 Page Shield ML 在真实环境中发现的。这些恶意载荷的检测是自动的;人工只在系统标记之后核实每一个发现。事后我们用安全扫描工具复核这些行动时,八个载荷里有七个在 VirusTotal 上完全不存在,URLScan 对其中任何一个都没有给出恶意判定。Page Shield ML 则在实时流量里抓到了全部八个。
举例来说,安全研究界多年前就记录过更宽泛的 Lnkr 家族,但其中一个具体的载荷版本被 URLScan 收录了将近两年半,一直标着「无分类」,包括 2024 年 1 月的一次直接扫描。只有这一个案例里 VirusTotal 更早就收录过该载荷:它目前把这个脚本标为恶意,但公开历史并没有透露这个判定最早是什么时候给出的。与此同时,Page Shield ML 在一家在线零售商的店铺上、在实时流量里独立地发现了那些一模一样的字节。更宽泛地说,一个哈希可能早在它背后的代码被判定为恶意之前就为人所知。如果你的防御在等那个标签,你已经晚了。你需要能拆解 JavaScript 本身、并大规模判断它的 ML。
确实,看到一个文件不等于理解它。棘手之处在于,这四个行动没有通用的特征码,也没有共同的隐藏手法。其中一个只有在设备、国家、时间、来源或浏览器状态匹配它所等待的条件时才会醒来。另一个把一次无点击的联盟请求藏在一个不可见的 iframe 里。还有的拦截点击、压制监控,或者有条件地从远程服务器加载更多代码。要抓到它们,你必须看这些部件如何协同工作:脚本什么时候醒来,它藏了什么,它拦截了什么,它接下来又去取什么。只检查页面一次是不够的;这些案例表明,这类脚本被设计成在合适的受害者出现之前一直保持安静。这就是为什么持续的浏览器可见性决定了你是抓到一次攻击,还是完全错过它。
我们如何大规模检测并标注 JavaScript
标记出本文这四个行动的同一个 GNN(图神经网络),此前已经抓到过恶意 npm 包和一个真实环境中的 Magecart 支付窃取器。这个 GNN 不把 JavaScript 当成一段扁平的文本,而是把代码当成图来推理:一棵连接代码符号的语法树,它暴露出谁调用了谁、攻击者试图埋掉什么,以及什么东西仍在向家里打电话。这种结构帮它在压缩、重命名和某些混淆之下仍能识别可疑模式,而不必依赖已知的 URL 或字节特征码。
被 GNN 标为恶意的少数脚本(不到全部分析流量的 0.3%)会交给 Workers AI 上一个轻量的大语言模型(LLM)做实时的第二意见。这进一步降低误报,同时保持高召回。当 LLM 印证了 GNN,客户就会收到告警。
为了大规模调查最复杂的脚本,我们用了一组前沿模型,我们称之为教师(一个自动评审的集成)。这一组汇集了来自大约六个不同家族的领先模型,包括跑在 Workers AI 上的开放权重模型。我们把每一个都作为一个 agent 启动,让它在自己全新的、独立的会话里分析同一个可疑脚本。在有用的时候,它们的 agent 工具权限让它们可以用一个受限的 JavaScript 求值器去拆开小片段、暴露被藏起来的行为。我们很快会用 Cloudflare Sandbox 扩展这套流程,在隔离环境里做更深入的分析。
前沿模型有时会意见不一,尤其是在最错综复杂的脚本上。我们把这种分歧当作信号,而不是噪声。每一个标注变成一票,权重是该模型在 Artificial Analysis Intelligence Index 上的得分,最终产出四个标签上的概率分布:良性、支付窃取(magecart)、其他恶意软件、以及加密货币挖矿。因此人工复核者只需要看那些被标为恶意、或者没有拿到明确三分之二多数的脚本。之后我们把这些标签分布喂回 GNN 的训练,帮它区分越来越细微的案例。这个反馈回路目前仍有部分是手动的,不过我们正在开始把它自动化。
我们抓到的四个恶意 JavaScript 行动
这四个行动做的事情差别很大,从偷佣金到窃取店铺已经花钱买来的顾客的分析数据。偷一笔佣金和刷一张信用卡不是一回事;同样,劫持搜索和偷密码也不是一回事。如果一个 ML 模型只认识其中一种把戏,它就会对其他的呼呼大睡。所以我们的 Page Shield ML 必须对每一种敌意行为都保持敏感。下面我们逐个深入看看每个行动和它是怎么运作的。
| 行动 | 对客户的影响 | 脚本做了什么 |
|---|---|---|
| 1)下班后的联盟佣金劫持者 | 劫持联盟佣金 | 移动设备与时间门控;动态页面监控;点击拦截;多日冷却 |
| 2)无点击的联盟偷窃 | 不需要用户点击就偷走联盟佣金 | 屏幕外的 iframe;自动点击隐藏链接作为兜底;多余的 IP 查询请求与时间门控;按小时轮换的联盟链接 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | 追踪用户,并打开一个后门以任意执行远程 JavaScript | 遗留的关键词静默;localStorage 退出机制;遥测;远程代码加载 |
| 4)只对投放流量生效的移动端斗篷 | 对带活动标签的移动访客蒙住店铺,试图替换广告与分析,并藏起客服 | 主机、视口与 UTM 标签门控;325 条 IP 子串名单;禁用 9 个监控/分析工具;零像素追踪信标 |
行动 1:下班后的联盟佣金劫持者
想象一个安静的周日下午:一位顾客在手机上点了一下某件商品。脚本没有照常跟随这次点击,而是在新标签页里打开一个攻击者预先选好的商品页或活动落地页,同时让原来的标签页走一遍联盟跳转。店铺看起来仍然正常。如果这位顾客完成了购买(无论当时还是之后),这次绕路就劫持了归因,把这笔销售(以及由此产生的佣金)记到一个并没有带来这次推荐关系的账号名下。
店铺损失了什么
店铺可能把一笔不该付的佣金付给了一个并没有带来这位顾客的账号。更糟的是,如果这次推荐本来来自一个合法合作伙伴,这次强制请求可能把它错误归因,把这份功劳和可能的报酬从真正做了工作的伙伴那里挪走。损害可能超出一笔佣金:不再信任归因系统的合作伙伴,可能也不再信任它背后的这家零售商。
攻击链
符合条件的移动访客 → 被拦截的商品点击 → 脚本选定的页面在新标签页打开 + 原标签页走攻击者的联盟路径

它是怎么藏住的
我们找到了五个相关的脚本构建:两个活跃,三个被抓到时处于暂停状态。每个活跃变体在动手之前都使用一组不同的门控,检查访客的设备与本地时间、这个把戏最近有没有跑过、某个商品按钮有没有出现、以及是否真的有人点击它。这团规则迷宫让恶意行为在一次短暂的自动化访问中不被看见,除非该变体的特定条件被满足。活跃脚本使用 MutationObserver(一个 JavaScript API)来监视页面首次加载之后动态出现的商品卡片和按钮。这让它们能拦截对这些迟到元素的点击,而一个只加载过一次 HTML 就停手的爬虫可能完全错过这条重定向路径。
在较晚的活跃变体里,脚本拦截一次符合条件的点击,并向 localStorage 写入一个三天冷却(在这台设备上休眠数天)。然后它执行一套双标签页手法:把一个攻击者选定的商品页弹进新标签页以留住顾客,同时原标签页悄悄跑一趟攻击者的联盟追踪链接、再回到店铺,在后台种下攻击者的归因 cookie。控制台遮蔽和自我防御的源码检查让审查更难,而冷却期和狭窄的时间表则限制了这条恶意路径在平常购物过程中出现的频率。
下面这段脱敏后的节选展示了载荷如何挂钩动态商品卡片并执行双标签页绕路。为了可读性,我们简化了标识符、重排了代码格式,并把目标 URL 做了无害化处理。
// Watch for late-rendering product elements and hook clicks
new MutationObserver((_, observer) => {
const tile = document.querySelector(TARGET_SELECTOR);
if (!tile) return;
observer.disconnect();
tile.addEventListener("click", (e) => {
// Bail out if cooldown is still active on this device
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || "null");
if (stored && stored.expires > Date.now()) return;
e.preventDefault();
e.stopPropagation();
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({ value: "tracked", expires: Date.now() + COOLDOWN_MS }),
);
// Keep shopper engaged in new tab...
window.open(target.link, "_blank");
// ... while routing original tab through the attacker's affiliate link
setTimeout(() => {
window.location.href = target.redirectUrl;
}, 200);
}); // Note: some variants added { once: true } to detach after the first tap
}).observe(document.body, { childList: true, subtree: true });
暂停状态的构建展示了这场行动如何在不动脚本的情况下转入静默。它们内嵌的配置里设了 status: "paused",所以它们在安装点击处理器之前就退出了。这些暂停脚本带着不同的按访客冷却配置(3 天、4 天和 5 天)。其中一个暂停脚本甚至留了一条版本历史注释,明确记录这场行动是在黑色星期五之后暂停的。
为了接触到访客,这个行动借用了站点的营销供应链:电商网站为了追踪广告活动和分析数据而嵌入的第三方脚本与标签管理器。一条已确认的投放路径经过了两个本来很普通的标签管理器:Google Tag Manager → 另一个标签管理器 → 恶意脚本。这就是载荷抵达浏览器的方式,并不能证明这两个标签管理器中的任何一个被攻陷了。
攻击者甚至伪装了托管脚本的域名,以通过快速的市场审查。一个投放主机就藏在明处:adtargett[.]com 与 adtarget[.]com 只差一个「t」,后者是一个 1998 年注册的广告域名。这个仿冒域名注册于 2025 年,我们查看时,它的首页自称「Adtarget.com - Performance Marketing Agency」。这就是域名抢注:通过模仿一家真实的广告代理,这个主机混进了日常的营销标签里,悄悄提供那个劫持顾客点击、并把他们经联盟付款链接重定向的恶意载荷。
行动 2:无点击的联盟偷窃
第一个骗局还需要一次点击,这一个需要的更少。顾客可以打开一个预订页,在产品选项上停留,从不碰任何广告。但在后台,脚本可能已经发出了一个联盟请求,让之后的一笔销售看起来像是别人推荐了这位顾客。确实,当脚本的条件被满足时,载荷会通过一个隐藏的 iframe 或者一个自己点击自己的链接发出那个请求。
店铺损失了什么
对那家受影响的旅游业务来说,这次攻击可能破坏获客的经济账:一笔合法的预订或购买可能被记到一个不该得的联盟账号上。代码证明了存在隐蔽的、自动化的联盟请求,但具体某次请求在实践中有没有真的形成归因、被记入账号或付出佣金,仍未观察到。
攻击链
带时间门控的浏览器 → 隐蔽的联盟请求(屏幕外 iframe)→ 一小时的节流 cookie → 被拦下时自动点击隐藏链接兜底

它是怎么藏住的
脚本用两层来隐藏这次联盟请求:选择性执行(一道预检网络门控加上按小时的排程)和隐蔽投递(一个屏幕外的 iframe)。第一层让人意外,因为它的国家标签与实际地理位置脱节:决定选择的既不是顾客的位置,也不是店铺的位置。
首先,脚本调用一个公开的基于 IP 的地理定位服务,但忽略它返回的一切,包括顾客所在的国家。我们无法确定它为什么要求一次成功的响应却忽略返回的数据;这可能是为了迷惑调查者,也可能只是早前版本的残留。有意思的是,如果地理定位请求失败,脚本会静默停止;它的 promise 链以 .catch(() => {}) 结尾。尽管意图未经证实,这种失败即关闭的行为可能帮助脚本躲过限制网络的沙箱。
接着,载荷没有使用取回的地理定位数据,而是内嵌了三个 TradeDoubler(一家联盟营销网络)的配置对象,标签是 AU、US 和 UK。这些设置块嵌在代码里,每个都包含一个联盟 URL 以及开始和结束时间。脚本用 JavaScript 计算 Asia/Kolkata 时间,检查那些配置好的时间窗口,然后套用固定的奇数/偶数小时规则来从三者中选一个,否则就跳过这次运行的联盟请求。这个选择是确定性的。
排程与浏览器状态检查合在一起,构成了带时间门控的选择性执行,也就是一种斗篷。当这些条件对不上时,联盟行为保持休眠,所以一次性检查会错过它。
一旦脚本选定了某个配置,它会写一个名为 affiliateClicked_ 的本地 cookie 作为一小时的冷却,这样它不会立刻在该区域再次触发(这是客户端节流以避免噪声,不是联盟网络的归因 cookie)。接着,它在一个来源被抑制的屏幕外 iframe 里加载那个联盟 URL。iframe 是主要的投递路径,但它带了一个激进的兜底:如果 iframe 出错,或者在一到两秒后仍未加载完成,脚本会创建一个没有 target 属性的隐藏链接并程序化地点击它,这可能让用户当前的活动标签页发生跳转。对符合条件的顾客来说,一切看起来都很正常:他们从没看到广告,从不需要点击,可以像什么都没发生一样关掉标签页。
至于脚本的混淆,简单但有效:连属性名都是一个字符一个字符拼起来的。下面这段脱敏节选展示了载荷如何创建一个不可见的屏幕外 iframe。为了可读性,我们重命名了关键标识符并重排了代码格式。目标地址已移除。
function loadAttribution(target) {
const frame = document['c'+'r'+'e'+'a'+'t'+'e'+'E'+'l'+'e'+'m'+'e'+'n'+'t'](
'i'+'f'+'r'+'a'+'m'+'e'
);
frame['s'+'r'+'c'] = target;
frame['r'+'e'+'f'+'e'+'r'+'r'+'e'+'r'+'P'+'o'+'l'+'i'+'c'+'y'] =
'n'+'o'+'-'+'r'+'e'+'f'+'e'+'r'+'r'+'e'+'r';
frame['s'+'t'+'y'+'l'+'e']['c'+'s'+'s'+'T'+'e'+'x'+'t'] =
'w'+'i'+'d'+'t'+'h'+':'+'1'+'p'+'x'+';'+'h'+'e'+'i'+'g'+'h'+'t'+':'+'1'+'p'+'x'+';'+
'p'+'o'+'s'+'i'+'t'+'i'+'o'+'n'+':'+'a'+'b'+'s'+'o'+'l'+'u'+'t'+'e'+';'+
'l'+'e'+'f'+'t'+':'+'-'+'9'+'9'+'9'+'9'+'p'+'x'+';'+
'v'+'i'+'s'+'i'+'b'+'i'+'l'+'i'+'t'+'y'+':'+'h'+'i'+'d'+'d'+'e'+'n';
document['b'+'o'+'d'+'y']['a'+'p'+'p'+'e'+'n'+'d'+'C'+'h'+'i'+'l'+'d'](frame);
}
行动 3:旧搜索引擎破坏者,如今成了店铺后门
多年前,Lnkr 恶意软件家族因为藏在可疑的浏览器扩展里上过新闻:它拦截 Google 和 Bing 搜索,重定向结果并把广告钱装进自己口袋。如今,攻击者把这套代码库改了用途,给一家在线零售商的网站种下一个后门。
因为脚本跑在一家店铺而不是搜索引擎上,它那些旧的重定向把戏保持休眠。这一次,脚本被用来把遥测数据发回给攻击者。更危险的是,它给了攻击者一个远程入口,可以随时在顾客的浏览器里任意下载并运行新的 JavaScript,而完全不用碰服务器上的任何文件。它甚至带着从扩展时代留下的一招:如果有人往 Google 里输入「virus」或「popup」这类词,它就关掉自己。从外面看,这家店铺继续在卖东西,没有任何出问题的迹象。
店铺损失了什么
店铺失去了对「在自己的顾客浏览器里跑什么代码」的控制。攻击者在秘密追踪访客会话,并且有一个直接的后门,可以随时在店铺页面上推送并运行任何他们想要的 JavaScript。
攻击链
由 HTML 引用的脚本 → 躲避分析人员的门控 → 并行的按主机门控分支(休眠的搜索模块 vs 活跃的后门)→ 任意远程 JavaScript 执行

它是怎么藏住的
与那些通过标签管理器投放的行动不同,这个脚本直接嵌在商家的 HTML 里。我们无法确定最初的入侵路径;在实践中,直接的 HTML 插入通常来自店铺管理员凭据被攻陷、一次未授权的模板改动,或者一个被感染的第三方主题或插件。
在底层,这个脚本是一个模块化的工具箱,同时带着活跃和休眠的代码。它较老的模块(透明的点击覆盖层、搜索引擎查询拦截器、扩展商店链接改写器,以及针对抢注域名的重定向,比如用 buking[.]com 冒充 booking[.]com)只在特定的目标站点上醒来,所以在这家店铺上它们保持关闭。几个内嵌域名(sugabit[.]net、votetoda[.]com、cdnpps[.]us,以及遥测端点 hanstrackr[.]com)就待在这些被禁用的模块里。
在这家店铺上,活跃的分支专注于规避、遥测和远程控制:
- 对安全研究者装死。 一个从浏览器扩展时代继承下来的规避手法:脚本监视搜索输入框和 URL 查询,寻找有辨识度的广告软件术语。搜到一个安全关键词,脚本就在这次访问中暂停。搜到两个或更多,就往
localStorage写一条持久的退出记录,在那位分析人员的机器上永久静默这个脚本,让重复测试什么也找不到。虽然它最初是为了在搜索引擎上躲开分析人员而写的,但据我们所能确定的,这个检查是硬编码到 Google 搜索 URL 上的,在这家商家的店铺页面上保持休眠。 - 动态远程代码执行。 脚本不需要改动店铺就能改变自己的行为。虽然硬编码的域名(
scrprime[.]com、youronlinesearches[.]com、jullyambery[.]net)与更早的捕获完全一致,但这些端点返回什么完全由攻击者决定。脚本可以把访客遥测发回家、向那些服务器索要新指令,并把新的 JavaScript 直接拉进顾客的浏览器。实际上,这给了攻击者一个在店铺页面上运行任意代码的活后门。我们无法确定实践中到底投放过什么样的第二阶段载荷。
总而言之,对站点做一次静态快照只能看到正常的店铺,而底下的状态检查、反分析陷阱和远程加载分支暴露了这个后门。
行动 4:只对投放流量生效的移动端斗篷
店铺已经花钱把这位访客从移动广告或营销活动里带了进来。恶意脚本放这次访问通过,然后切断商家的可见性。分析数据变黑,实时客服聊天消失,一个流氓观察者开始对店铺刚刚花钱买来的这次会话记录遥测。
在幕后,载荷会拒绝运行,除非这次访问匹配一整套精心设计的条件:确切的目标店铺、一个窄小的移动屏幕,以及访问头两页时带有某个活动标签。它在笔记本、企业网络、云服务商和 VPN 上保持休眠,所以最可能去调试页面的那些工程师永远看不到它触发。脚本还会在美国选定的城市和地区保持休眠,背后是一份手工维护的 325 条 IP 字符串的拒绝名单,用来躲开自动扫描器和安全分析人员。只有在这之后,脚本才会尝试拆掉店铺的监控、换上替代的广告与分析身份,并往家里打电话。从错误的设备或网络再看一眼,永远不会触发它。而与此同时,店铺继续在卖东西。
店铺损失了什么
对一家直接面向消费者的零售商来说,这个恶意软件专门瞄准店铺通过付费搜索和营销活动(ppc、cpc、sms、paid)花钱买来的高价值流量。这些顾客仍然可以下单。但店铺面临三个明确的威胁:广告归因被转移以及不该付的发布方报酬、九个可观测性工具上关键会话分析数据的丢失、以及帮助聊天和联系表单被压制(让顾客无法提问或报告异常)。在沙箱浏览器环境里的动态分析确认,替换用的分析脚本确实加载并触发了一个追踪信标(一个用来记录访客活动的不可见网络请求),但攻击者在实践中是否成功捕获了会话遥测或挪走了广告收入,仍未得到证实。
攻击链
带活动标签的移动端到达 → 多层斗篷与网络门控 → 监控被破坏 → 广告、分析与客服控件被改写

它是怎么藏住的
为了混进店铺的营销供应链,攻击者从 sdk-amazonaws[.]com 投递载荷,这是一个 2024 年注册的仿冒域名,与官方的 Amazon Web Services 域名(amazonaws.com,注册于 2005 年)毫无关联。为了让欺骗更进一步,攻击者还给这个域名加了一个模仿某流行电商营销平台的子域前缀。这种叠起来的、双重可信品牌的域名抢注造出了一个有说服力的伪装,专门设计成能溜过快速的标签审查。Amazon Web Services 和被冒充的营销平台都没有参与这次攻击,也没有遭到任何攻陷。
一旦在浏览器里加载,脚本在触发主载荷之前先执行了一套极其密集的斗篷门控:
- 目标主机与浏览上下文。 脚本核实
window.location.hostname与它被构建来瞄准的那个特定商家主机一致(在其他任何地方立刻退出),确保当前窗口是顶层窗口(不是被嵌入的 iframe),并检查路径里不含/challenge。它还核实浏览器里尚未存在追踪标记 cookie(_cart_dr和_logo_alt)。 - 设备与活动过滤。 访客的视口宽度必须窄于
477像素(一部手持智能手机)。此外,访客必须通过一个首次接触(访客最初的来源)活动到达,且该活动带有六个特定 UTM medium 之一(Urchin Tracking Module,用来追踪营销活动的标准 URL 标签):ppc、cpc、sms、paid、flow或campaign。而且这必须是他们会话中的第一页或第二页。有意思的是,虽然代码里有一条名义上的非 UTM 路径,但它要求会话页数同时大于 -1 且小于 -2(一个数学上不可能的条件,让那个分支完全不可达)。这可能是又一种误导手法,也可能是代码改动留下的残余。 - 那个永远通过的「随机」门控。 代码里有一个看起来像概率节流的判断(
Math.random() <= threshold),让执行显得时断时续。但当我们把去混淆后的算术解出来,这个阈值恰好化简为 1。因为 JavaScript 的Math.random()返回的值总是严格小于 1,这个门控永远为真。就像那个不可达的非活动分支一样,这是一个实际上从不做任何决定的条件。它可能是一个遗留的节流,也可能是为了迷惑任何阅读这段混淆算术的人而故意做的伪装。无论哪种情况,被捕获的载荷从未用它跳过任何符合条件的访客。 - 第三方 IP 情报。 脚本用一个内嵌的密钥查询了一个外部的第三方 IP 情报 API。它要求一个美国的移动消费级连接,同时在响应标记出企业网络、托管设施、云服务商、bogon、Tor 出口节点、VPN、代理、中继或一般性威胁指标时立刻放弃。
- 地理排除。 载荷不会对来自特定地区(
US-NY、US-CA和US-NHDD)或来自 San Francisco、Plymouth、Compton、Hopkinton 和 Lafayette 这些城市的访客激活。 - 325 条 IP 子串陷阱。 脚本把访客的 IP 与一份内嵌的、包含 325 条完整 IPv4 地址字符串的拒绝名单做了比对。去重之后,这些代表 249 个不同的三段前缀下的 313 个唯一地址。作者没有做结构化的 CIDR(无类别域间路由)子网匹配,而是直接把访客 IPv4 地址的最后一段剥掉,然后跑一次原始的子串搜索:
!denylistString.includes(visitorPrefix)。
用精简的伪代码表示,这层多级主激活漏斗是这样的:
// 1. Context, device, and campaign gates
let eligible = isTopWindow && host === EXPECTED_HOST && !path.includes("/challenge");
eligible &&= !hasCookie("_cart_dr") && !hasCookie("_logo_alt");
eligible &&= viewportWidth < 477 && [1, 2].includes(sessionPage);
eligible &&= ["ppc", "cpc", "sms", "paid", "flow", "campaign"].includes(utmMedium);
eligible &&= Math.random() <= 1; // Apparent random gate always resolves to true
// 2. IP intelligence & geographic gates (fetching external API)
eligible &&= ipInfo.country === "US" && ipInfo.isMobile && !ipInfo.isBusiness;
eligible &&= !ipInfo.isCloud && !ipInfo.isProxy && !ipInfo.isVpn && !ipInfo.isTor && !ipInfo.isThreat;
eligible &&= !["US-NY", "US-CA", "US-NHDD"].includes(ipInfo.region);
eligible &&= !EXCLUDED_CITIES.includes(ipInfo.city);
// 3. 325-entry IP prefix check (raw substring matching)
let clientPrefix = ipInfo.ip.slice(0, ipInfo.ip.lastIndexOf("."));
eligible &&= !DENYLIST_STRING.includes(clientPrefix);
if (!eligible) return; // Cloak passes only for qualifying consumer mobile sessions
破坏可观测性并劫持身份:
只有在每一道主门控都通过之后,脚本才执行它的载荷:
- 蒙住监控工具。 它搜索 DOM 并移除九个不同可观测性与分析服务的 script 标签:Lucky Orange、Segment、Optimizely、New Relic、Bugsnag、LogRocket、Hotjar、Microsoft Clarity,以及店铺的 Google Tag Manager 容器(
GTM-)。在剩下的内联脚本里,它把这些工具的引用字符串替换成未定义的假标识符(hji0),让对它们的调用静默失败,试图蒙住店铺的错误上报与监控。 - 压制客服。 它注入 CSS 并移除元素,把客服聊天和联系表单的容器藏起来,切断顾客与店铺客服之间的直接通道。
- 替换广告与分析身份。 它清掉 Google Ads 的全局变量(
google_ad_modifications、adsbygoogle),拆掉已有的广告位(ca-pub-),并以一个替换用的发布者 ID(ca-pub-)加载 Google Ads。接着它注入一个新的 Microsoft Clarity 会话回放脚本,配置了一个流氓的替换项目 ID。
更简单的独立信标与那个 600 天标记:
与精心设计的主斗篷形成鲜明对比的是,载荷里还包含次级信标分支(独立的例程,悄悄 ping 一台外部服务器以确认一次访问),它们完全绕过了视口、主机名、活动、地理和 IP 门控。如果访客已经在第二页或更后面,脚本会写一个持久的 cookie(_cart_dr=1),有效期恰好 600 天(51,840,000,000 毫秒),并向 maper[.]info 上的一个远程遥测端点发出一次不可见的零像素图片请求(一个追踪信标,用来记录浏览器走到了这一步)。
另有一个独立分支检查另一个标记(_logo_alt),触发第二个遥测 .png 信标(这个 cookie 是本脚本会去找、但自己从不写入的,很可能是由配套脚本种下的)。这给了攻击者一个简单、持久的命中计数器,用来记录整家店铺所有访客的基本流量(IP 和 User-Agent 都会记在端点上),同时把他们高风险的广告劫持例程严格藏在移动端斗篷之后(高价值的付费流量)。这也说明,为什么只分析一个可见的效应,揭示不了一个多用途载荷的全部触达范围。
失陷指标(IOC)
我们发布这些指标,是为了帮助安全团队和研究者在自己环境里检测和追查这些行动。所有指标都直接取自捕获到的载荷及其网络连接。列出的 URL 已做无害化处理。部分指标被隐去或做了泛化,因为发布它们可能无意中泄露受影响组织的身份。列出的域名反映的是在这些攻击期间观察到参与了投递、重定向或遥测链的基础设施;被列入并不意味着某个共享服务或托管提供商本身是恶意的。
| 行动 | 指标 | 类型与作用 |
|---|---|---|
| 1)下班后的联盟佣金劫持者 | adtargett[.]com | 脚本投递与联盟重定向的抢注域名 |
| 1)下班后的联盟佣金劫持者 | gdataroute[.]com | 在攻击链中观察到的联盟重定向短链服务 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | scrprime[.]com | 浏览器劫持脚本的投递域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | searchvalidation[.]com | 搜索劫持与流量重定向域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | sugabit[.]net | 强制搜索重定向域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | youronlinesearches[.]com | 有条件的远程脚本投递域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | hublosk[.]com | 远程脚本投递域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | jullyambery[.]net | 远程 JavaScript API 与指令域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | votetoda[.]com | 注入载荷投递域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | hanstrackr[.]com | 隐藏的访客遥测域名 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | adrs[.]me | 在攻击链中观察到的抢注流量重定向服务 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | youradexchange[.]com | 在攻击链中观察到的变现重定向服务 |
| 3)旧搜索引擎破坏者,如今成了店铺后门 | cdnpps[.]us | 注入广告框投递域名 |
| 4)只对投放流量生效的移动端斗篷 | sdk-amazonaws[.]com | 滥用品牌信任的仿冒脚本投递域名 |
| 4)只对投放流量生效的移动端斗篷 | maper[.]info | 有条件的访客遥测信标域名 |
给防御者的四条经验
合在一起看,这些行动讲了一个不断升级的故事:攻击者改变了目标、投递路径和伪装,但浏览器仍然必须执行他们的逻辑。有四条经验格外突出。
行为胜过特征码。 这些行动追求的是不同形式的变现和操纵,但每个载荷仍然必须在浏览器里行动:观察事件、检查状态、改动页面、安排任务、发起网络请求,或者加载下一个阶段。这正是结构化分析要找的东西:一个敌意载荷必须携带的逻辑,即使 URL、特征码和目标都在变。
选择性执行是攻击的一部分,不是脚注。 设备、时间、地理、来源、会话、网络和冷却期门控,都能击败一个只访问一次、拍下静态快照的爬虫。持续可见性之所以重要,是因为一次攻击可能只对某一个浏览器、在某一种状态、在某一时刻出现。
混淆提高了分析成本,但在这些案例里没有阻止检测。 自我防御的循环、控制台压制、调试器陷阱、轮换的字符串表以及死分支,都让分析变得复杂。Page Shield ML 仍然在那些障碍之下发现了全部四个行动。快速的内部模型大规模地把可疑代码浮上来,前沿模型则去调查最难的那些案例。它们之间的分歧会凸显出最棘手的混淆和逻辑,帮我们收窄焦点。
上下文才让画面完整。 孤立看起来普通的代码,一旦防御者把静态分析与动态上下文连起来,就会暴露它的恶意角色:它是怎么到达的、是哪种浏览器状态激活了它、它打开了哪些连接,以及它在运行时究竟做了什么。
对客户端执行的持续可见性
这四个行动依赖不同层次的误导,但它们都受同一个约束:它们的 JavaScript 必须在浏览器里执行。公开扫描器和静态爬取会漏掉有门控的行为。持续观察有助于弄清,当真实访客与页面交互时,代码究竟在做什么
Cloudflare 的客户端安全在所有套餐上都提供这种可见性。你可以在安全设置里打开持续脚本监控,追踪店铺上第一方和第三方的脚本,而自动的恶意脚本检测与告警在客户端安全高级版里提供。你可以直接在 Cloudflare 控制台里查看脚本活动、管理检测结果。