恶意网址识别
那天上午,我收到一封邮件,标题是 “你们的短链接被用于钓鱼诈骗”。
如果你们不立即处理这个链接,我们将向公安机关报案。
我们的客户通过你们的 short.url/xxxxx 访问了一个仿冒银行的网站,输入了银行卡号和密码。这是严重的违法行为。
我的心一下子凉了。
我立刻打开那个短链接——果然,跳转到了一个仿冒某银行的钓鱼网站。页面做得极其逼真,如果不是域名不对,我自己都分辨不出来。
这是我第一次直面黑产。
第一步:下线
我没有犹豫,直接下线了那条链接。
落地思路
- 下线的动作是把
urls里那条记录的状态改成禁用,跳转时命中禁用状态就返回拦截页,而不是照常跳出去。- 改状态而不是删记录:删除会把投诉、举报、点击这些证据一起弄没,出事说不清。
- 同一批排查出来的可疑链接要一起处理,87 条可疑链接不能只下线被投诉的那一条。
- 兜底是状态改动要让缓存同步失效,否则跳转会先命中旧缓存,用户照样落到钓鱼页面上。
然后给投诉者回邮件道歉,承诺 24 小时内给出完整的解决方案。
第二步:排查
我查了一下数据库,发现问题的严重性超出我的想象——
过去一周的可疑链接:
- 指向 .xyz/.top/.gq 域名的短链接:87 条
- 指向 IP 地址(而非域名)的短链接:23 条
- URL 中包含 "login" "verify" "account" 的:45 条
- 被多个用户举报的:12 条
87 条可疑链接。而我之前居然一条都没发现。
“我不能靠人工一条条看。我需要一套自动检测系统。”
我花了一周时间,搭了三层防线。
第一层:黑名单检测(最快)
“最快的办法:维护一份已知恶意域名列表。”
落地思路
- 维护一份已知恶意域名和 IP 的名单,检测时先取目标 URL 的主机名做一次查找。
- 名单放在 Redis 里,应用启动或定期刷新时一次性读进内存,检测时就是一次内存查找,不占用请求时间。
- 名单要同时覆盖整段主机名和父域名:命中
evil-bank.com时,login.evil-bank.com也要算命中。- 兜底是名单拉取失败时继续用上一次成功加载的版本服务,不能因为一次拉取失败就变成「不检测」。
“黑名单检测非常快,只需一次内存查找。但缺点也明显:只能识别已知的恶意域名,新出现的钓鱼网站识别不了。”
第二层:特征检测(中等速度)
“对于新出现的钓鱼网站,我需要分析 URL 本身的特征。”
落地思路
- 看的是 URL 本身的结构特征:是不是直接指向 IP 而不是域名、路径里有没有 login/verify/account 这类关键词、有没有用
@混淆、有没有超长子域名。- 判定用一组规则组合,命中若干条就提高风险分,而不是靠单一特征下结论。
- 新注册域名、刚解析出的 IP 这类信号也要算进来,它们往往还没进任何名单。
- 兜底是规则要可解释:每条规则命中与否都要记下来,人工复核和用户申诉时才说得清为什么被拦。
第三层:Google Safe Browsing API(最准确)
“Google 维护了全球最大的恶意 URL 数据库。虽然调用有延迟,但准确率最高。”
落地思路
- 把目标 URL 提交给 Safe Browsing,拿回是否被标记为恶意,这是三层里准确率最高的一层。
- 接口有延迟,所以放在创建链路的最后:前面两层已经能拦掉的,不浪费这一次外部调用。
- 调用要设超时并做好缓存:同一个 URL 的检测结果缓存一段时间,避免反复请求同一个目标。
- 兜底是接口不可用时不能把它当成「安全」:超时按「未确认」处理,交给前面两层和人工复核兜底。
把三层检测串成一条完整流程,我看到的实际效果是这样:
URL 安全三层防线
- 1第一层:黑名单检测(最快)最快
维护一份已知恶意域名列表,一次内存查找即可命中
只能识别已知恶意域名,新钓鱼网站识别不了
- 2第二层:特征检测(中等速度)中等
分析 URL 本身特征:可疑域名后缀、IP 直连、敏感词
能覆盖部分新出现的钓鱼网站
- 3第三层:Google Safe Browsing API(最准确)最准
调用全球最大的恶意 URL 数据库
延迟最高,但准确率也最高
- 4实时拦截 + 举报 + 申诉闭环
创建时拦截、访问时阻断,用户举报兜底,误杀可申诉复核
自动检测不可能 100% 准确,需要人机闭环
关键认知: 安全检测不是某一层单独搞定,而是 快慢结合、多级兜底。 黑名单快但覆盖有限,Safe Browsing 准但慢——真实系统用它们组合, 再配合举报与申诉闭环处理漏网和误杀。
落地思路
- 顺序是黑名单 → 特征检测 → Safe Browsing,快的在前、慢的在后,大多数恶意链接在前两层就被拦下。
- 每一层只返回「恶意 / 疑似 / 正常」三种结论,任意一层判恶意就立刻拒绝创建。
- 判「疑似」的走人工复核队列而不是直接放行——这批数量很小,人工完全看得过来。
- 兜底是任一层自身不可用时降级而不是全放行:比如 Safe Browsing 超时就按「疑似」处理。
“检测系统搭好了,但必须在创建短链接时就拦截,不能事后补救。”
落地思路
- 检测放在创建接口里同步执行,用户提交长 URL 的那一刻就出结果,不等到有人点了才发现。
- 检测通过才写
urls并分配短码,被拦截的请求不落库,也不占用短码。- 检测耗时不能拖垮创建接口:给整条检测链路设一个总超时,超时就按「疑似」处理并转人工。
- 兜底是误判要能恢复:申诉通过后重新放行,并把当时的判定原因记录下来,用于调整规则。
“自动检测不可能 100% 准确。我还需要一个用户举报通道。”
落地思路
- 举报入口挂在短链页面上,用户填举报原因后落到一条举报记录里,带上报链接短码和举报人。
- 同一短码的举报要能累加:达到阈值就自动触发一次检测,不用等人工逐条翻。
- 举报数少的先排队人工处理,举报数多的直接按禁用处理,再补一次检测确认。
- 兜底是误判申诉:确认是误报就恢复链接并给举报人一个说明,举报不是一票否决。
上线一周后的数据:
| 指标 | 数值 |
|---|---|
| 创建请求总数 | 12,345 |
| 被拦截的恶意 URL | 156 条(1.26%) |
| 黑名单拦截 | 89 条 |
| 特征检测拦截 | 48 条 |
| Safe Browsing 拦截 | 19 条 |
| 用户举报 | 7 条(其中 5 条已被自动拦截) |
| 误判申诉 | 3 条(审核后 2 条恢复) |
“156 条恶意链接被拦截在创建阶段。如果这些链接流出去了,平台信誉就毁了。”
发现内容有问题?
告诉我们哪里需要改进。
