返回所有文章
发布于 · 作者 Renaud Deraison

Bromure 如何在钓鱼攻击找上你父母之前将其拦下

一步步拆解 Bromure 的反钓鱼机制——本地扫描、模型、判定,以及我们为什么恰恰是为你的父母、你的祖父母和对门邻居打造了它。

你的父母并不需要又一次关于「小心点击链接」的说教。他们需要的是一款能看见他们即将踏入的陷阱、并且能及时用他们听得懂的话说出来的浏览器。

你们当中有些人接到过那通电话。电话那头,母亲的声音已经低了下去,变得很小,她说:我想我在电脑上做错事了。另一些人,则是在等着那通电话。这篇文章的目的,是详细讲清楚我们打造的这套机制——让那通电话尽可能少一些;而当它真的响起时,损失也只止步于一则警告,而不是一个被清空的账户。

这个攻击产业,从来就在盯着老年人。

钓鱼攻击从来就不是一场比聪明的游戏,而是一场概率游戏:一条工业化的流水线,成千上万地批量生成假冒登录页面,冒充人们熟悉的品牌,写出带有紧迫感的消息,然后通过电子邮件、短信、社交网络和电话一股脑推送出去。攻击者所需要的,只是某一个人,在某一天,某个分心的瞬间,把密码输到了错误的框里。祖父母、父母、丧偶的邻居,以及所有从小信任银行寄来的信件长大的人——他们不是反应慢,他们是信任他人。攻击者知道这一点,并且有意识地针对这一点。

"快递送达""税务退税""微软客服""银行账户被锁""奶奶,是我啊""验证您的账户"你的父母只差一个密码的距离再多的"学会识别拼写错误"培训,也解决不了这个问题。
一位独自在家的年长用户,正承受着整个全球攻击产业的全部火力——短信、邮件、电话、社交消息、伪造的物流通知、退款骗局。反钓鱼建议假设他们能够逐一辨别处理,可他们做不到。

推送给祖父母辈的那些安全建议,本质上都是小心一点的各种变体。检查网址。悬停看链接。不要相信发件人。问题不在于祖父母们粗心大意,而在于那个攻击产业雇佣的,是一批专职以击败「小心一点」为生的专业人士。防御一方与进攻一方之间的差距,到了今天这个地步,已经有些难堪了。

我们究竟做了什么,简短版。

当一个页面在 Bromure 里加载时,一个小小的检测器会在密封的浏览器虚拟机内部运行起来。它像一个多疑的门口引座员那样盯着页面——看网址,看表单,看可见的文字,看链接,看任何二维码,甚至看这个页面悄悄往剪贴板里写了什么。一旦有任何不对劲的地方,检测器的发现就会通过一条隔离通道送到一个模型那里。模型读取这些信号,做出判定,写下一句话的说明,再把结果传回来。Bromure 会把这个判定以横幅的形式渲染在页面上。对于彻头彻尾的钓鱼页面,则会直接跳转到「已拦截站点」的警告页,根本不给它骗人的机会。

沙盒内部(虚拟机)页面加载内容脚本注入完成本地扫描表单 · 网址 · 品牌二维码 · 剪贴板预过滤跳过热门、可信、SSO渲染判定横幅安全 · 可疑 · 钓鱼vsock · 端口 5950在 BROMURE API 上缓存{domain, path}1 小时 TTLClaude Haiku结构化JSON 判定判定 JSON判定 · 置信度 · 理由
完整的流水线。蓝色区域中的步骤运行在沙盒化的客户虚拟机内部。橙色区域中的步骤运行在 Bromure 服务器上。黄色那条竖条是一条 vsock 通道——不是网络——负责在两者之间传递检测到的信号。

这篇文章接下来的部分,讲的就是这个如何实现。它会刻意写得细致一些。如果你读这篇文章是因为想到了家里的父母,尽可以直接跳到 你的数据究竟去了哪里 这一节——简短的回答是:这个功能默认是关闭的,开启时会征求同意,而它做的一切,下文都写得清清楚楚。

第一步——本地扫描。

在任何东西离开你的电脑之前,一个内容脚本会先在客户虚拟机内部运行起来。它会对页面进行几道相互独立的扫描,每一道都不耗费多少资源。所有扫描都在浏览器里完成,就在你自己的电脑里,就在与页面本身一样的那个密封沙盒中。

密码字段出现的那一刻

当一个 <input type="password"> 刚被挂到 DOM 上的瞬间——在你还没把焦点移过去之前——检测器就已经记下了域名,并标记出你之前是否在这里输入过密码。如果是一个全新域名上的第一次?光这一点就是一个信号。

表单结构

只有登录表单、没有其它导航的页面。表单将数据 POST 到与地址栏上完全不同的域名。表单冒充某个品牌,但其 logo 却是从另一个主机外链过来的。任何一条单独拎出来都很弱,但叠在一起时,胜负的天平就变了。

品牌不匹配

如果页面上写着「Apple ID」,域名却不是 Apple 的;或者 logo 是从一个可疑的主机上加载来的——这种不匹配就会被记录下来。不需要任何网络请求,只需要对页面上已有的内容做模式匹配就够了。

同形字域名

诸如 аррӏе.com 这样的域名——看上去跟「apple.com」一模一样,实际上却是由西里尔字母的相似字符拼成的——几乎可以肯定就是钓鱼。检测器通过把渲染出来的形态与一份已知的易混淆字符集做对照来识别它们。

诈骗话术

「请确认您的密码。」「请验证您的账户。」「您已被选中。」「点此领取您的奖品。」——每一句都是一个较弱的信号。但如果一个页面在一个登录表单里凑齐了其中三句,而域名又是谁都没听说过的,那就完全是另一回事了。

二维码与剪贴板投放

一个扫出来指向一个不相干钱包的加密货币支付 URI 的二维码。一个一边让你「按 Win+R 并粘贴」、一边悄悄往你剪贴板里写入 shell 命令的页面。这些都是能绕过所有传统过滤器的新型攻击手法。

还有一个本地预过滤环节,可以避免检测器就那些根本不用担心的网站去烦服务器。Tranco 榜单上前十万名的域名,再加上一份包含 30 多个 SSO 提供方的精选清单(Google、Microsoft、Okta、Apple 等等),只要没有其他可疑之处,就会被默默忽略。你母亲真正用的银行、她真正去的药房、她真正使用的网页邮箱,永远都不会被「上报」。

如果本地扫描过后没有任何值得关注的内容,故事就到此为止。没有任何请求会离开你的电脑。

第二步——第二个判断。

如果这份档案确实值得关注——或者刚刚有一个密码字段出现在了一个陌生的域名上——那么就会有一份结构化的报告被送去评估。不是截图,不是整个 DOM,而是一份经过边界裁剪、清理过的信号摘要。

用户看到的paypai-secure.xyz/loginPayPal登录您的账户请立即验证您的账户。电子邮箱密码登录模型看到的{"domain": "paypai-secure.xyz","urlSuspicion": ["brand-in-subdomain"],"brandSignals": {"title": "登录您的账户","logoDomain": "i.imgur.com"},"domainMismatch": {"claimed": "paypal","actual": "paypai-secure.xyz"},"sensitiveFields": ["password"],"pageStructure": ["only-login-forms","minimal-navigation", "data-uri-favicon"],"contentIndicators": ["urgency-language"]}
模型实际看到的东西。左边那张页面在人眼里可能做得天衣无缝;而右边的模型拿到的,是一份小而结构化的信号档案——而不是渲染出来的像素。

这份打包数据包括:网址,域名,可见文字(上限约 800 个字符、去除了控制字符),每个表单的摘要(字段类型、按钮文案和 action 指向的 URL),检测到了哪些敏感字段,页面的标题和主要标题,logo 图片是从哪里加载的,域名不匹配和同形字标记,网址层面的红旗(子域名中夹带品牌、punycode、过多连字符),结构性标记(只有登录表单、密码字段禁用了自动填充、data-URI 的 favicon、隐藏的 iframe),如果二维码被解码则附上其内容,如果剪贴板被悄悄写入了内容则附上其内容。所有字符串都有很短的长度上限,所有数组都有很小的数量上限,控制字符不会保留下来。

在服务器端,这份档案会交给 Claude Haiku 4.5——一个小而快的模型。系统提示教会模型把档案里的一切都当作不可信的取证证据来对待:要假设这个页面是由一个知道这套提示词存在、并专门设计出来迷惑它的人构造的。提示词会带着模型按顺序走完九项检查(域名分析、品牌冒充、页面结构、凭据窃取、可疑链接、诈骗内容、二维码、剪贴板投放、最终综合判断),并要求它输出一个 JSON 对象:

{
  "verdict": "phishing" | "suspicious" | "safe",
  "confidence": 0.0,
  "reason": "一句简短、通俗易懂的说明"
}

阈值是经过刻意校准的。高于 0.85,判定为 phishing,网站被拦截。在 0.4 到 0.84 之间,判定为 suspicious,显示一条警告横幅。低于 0.4,则为 safe,什么也不显示。单独一个较弱的信号——一个诈骗词、一个不匹配的 favicon——不足以单枪匹马地越过这条线。系统明确告诉模型:在你奶奶的药房页面上误报,比让一个平庸的骗局蒙混过关更糟。

有两条快速通道,会在答案已经显而易见时完全跳过模型:

  • 品牌不匹配捷径——如果页面声称自己是 PayPal,而域名却是 paypai-secure.xyz,服务器立即返回 suspicious,不调用 LLM,也不产生任何成本。
  • 缓存命中——判定以 {domain, normalized path} 为键,存入 memcache 一小时。第二次访问同一个页面时几乎零成本,毫秒级就能返回答案。

第三步——页面上的判定。

判定返回后,Bromure 会把它就地渲染在页面上——在页面中,在内容之上。没有弹窗,没有需要你父母先关掉才能看到想看内容的模态框。

安全 · 不显示任何内容(没有横幅——页面正常加载)可疑 · 琥珀色横幅,可关闭!可疑页面这个页面正在向您索要密码,但这是您第一次访问这个域名。我认识这个网站×钓鱼 · 红色全屏拦截页,默认返回检测到钓鱼攻击此站点正在冒充 PayPal。请勿输入您的凭据。paypai-secure.xyz返回到安全页面仍然继续访问
三种可能出现的横幅。安全:保持静默。可疑:一条琥珀色的横幅,可手动关闭,并提供一个「我认识这个网站」的后路。钓鱼:红色的全屏拦截页,默认动作是返回。

理由 那一行永远都是一句话,是写给正在读它的人看的,不是写给调试程序的人看的。横幅上显示的是 「此站点正在冒充 PayPal。请勿输入您的凭据。」 ——不是什么置信度分数,不是信号踪迹,也不是一串正则匹配项。我们的目标是:你的母亲读一遍横幅,就知道自己该怎么做。

对可疑横幅的每一次关闭,都只在当前会话内有效;或者,如果她点击了我认识这个网站,则会被提升到该配置文件的可信名单里。信任的决定权在她手里,而不是模型手里。

跨域拦截——一张不需要判定结果就能兜底的安全网。

甚至在任何判定返回之前,Bromure 就会强制执行一条硬性规则:密码永远不得在未经询问的情况下被发送到地址栏之外的任何域名。如果 login.bank.com 上的一个表单正要把数据 POST 到 credentials.attacker.example,这次提交就会被截下来,并弹出一个模态框。

页面login.bank.compassword: ••••••••拦截!密码正被发往别处这个页面即将把您的密码发送到另一个域名。取消继续默认动作是取消表单目标creds.attacker.example不同的域名不在 SSO 允许名单内在确认之前已被阻止
跨域密码拦截。在任何服务器判定到来之前,只要一次表单提交的密码目标与页面所在的域名不一致,就会被拦下,要求用户明确确认——并在说明中用通俗的语言点出这两个域名。

一份简短而精心策划的已知身份提供商白名单——Google、Microsoft、Okta、Apple,以及另外几十家——让合法的联合登录免于被这条规则拦下。其他一切都会每次都被弹出模态框。仅凭这一条规则本身,就能在模型尚未返回判定之前,拦下相当大比例的凭据钓鱼页面。

你的数据究竟去了哪里。

有两条准则决定着数据的走向。第一条是同意,第二条是隔离。

同意。 在每一个新建的配置文件上,这个功能默认都是关闭的。要启用它,用户需要打开 隐私与安全,会看到一个专门的模态框——在任何开关被拨动之前,它就会清清楚楚地告诉你:发送的到底是什么(网址、页面文字、表单结构、警告信号)、会送到哪里(一台由 Bromure 在 bromure.io 运营的服务器)、保留多久(短期日志,用于防滥用和模型改进),以及怎样关掉它。这个功能只在持久化配置文件上才可用——不会在临时的、用完即弃的会话上启用——这样一来,一个匿名研究会话就不会意外地把数据泄露到服务器。如果你给奶奶的 Mac 装上 Bromure 并替她开启这个功能,她看到的横幅本身,就是一个证据:这个选择是某位她信任的人,代表她刻意做出的。

隔离。 浏览器并不直接连接互联网,检测器也一样。所有的检测结果,都通过一条 vsock 通道从客户虚拟机送到宿主机,使用的是一个内部端口——不是网页能看到的那个网络接口,也不是任何页面能够影响到的代理。宿主机把请求转发给 API,收到判定,再把它送回给检测器。世界上没有任何一个网页能够触及这座 vsock 桥;没有任何一个网页能够直接触及宿主机;没有任何一个网页甚至能知道这座桥的存在。

客户虚拟机(浏览器)网页phishing-guard内容脚本后台工作进程vsock :5950macOS 宿主机PhishingAnalysisBridge转发客户端检测结果通往 API 的唯一路径HTTPSBROMURE APIbromure.io/v1/analyzememcache1 小时 TTLHaiku 4.5判定 JSON网页接触不到其中任何一跳。
数据的路径。检测结果从客户端的扩展出发,经 vsock 到达宿主机上的桥接组件,再通过 HTTPS 发往 API。网页本身接触不到其中任何一跳。

被检测页面的网址确实会在服务器上短暂地留存日志,用于防滥用。可见文字会被发送给模型进行分析,响应返回后不会保留。你在表单里输入的任何内容,永远都不会出现在这份打包数据里。如果你关闭了这个功能,就不会有任何数据被发出去。

在你已经熟悉的页面上也要快。

在每一次页面加载时都去调用一次语言模型,会让浏览器变得沉重。Bromure 会激进地缓存判定结果,这样一来,互联网上那些常用页面在第一次访问之后,评估成本基本可以忽略不计——而且,即使服务器偶尔不可达,本地扫描也会继续尽职尽责。

先查缓存,最后才找模型

判定以 {domain, normalized path} 为键缓存一小时。第二次访问同一个页面,毫秒级就能给出答案,完全不碰模型。品牌不匹配的捷径则完全跳过模型。

本地扫描就是那条底线

如果服务器不可达,或者响应缓慢,本地扫描会继续运行。针对那些最明显的情况——跨域密码、同形字域名、剪贴板投放——横幅完全是在你自己的电脑里生成的,不需要任何网络。

我们兑现承诺。

我们在第一篇文章里说过,真正的防御,应当是一双永不眨动的「第二只眼睛」。在 Bromure 里,这双眼睛现在做的,就是上面这些事。

你的母亲不该为了读个新闻,先通过一场安全考试。你的父亲不该去了解什么叫同形字。你的祖父不该因为一个礼貌的电话让他装个东西,而事后觉得自己很蠢。浏览器应该在密码离开键盘之前,及时看见那个陷阱,并说出来——用一句话说清楚,而不是用一堆术语。

这就是 Bromure 反钓鱼功能存在的意义。这也是为什么它默认关闭、启用前先讲清楚、与页面完全隔离、且在大规模使用下依然便宜。把它装到你父母的 Mac 上,带他们过一遍「隐私与安全」面板,把这个功能打开。然后,就去吃晚饭吧。