没推广,后台却多了 300 多个用户:一次机器人注册排查与人机验证实战
摘要:一个刚上线、几乎没推广的 Blog,后台却突然出现了数百个“订阅用户”。用户名像随机敲出来的字符串,邮箱来自不同服务商,评论数全部为 0。这个问题不是增长,而是一次很典型的机器人批量注册。这篇文章记录我们如何从真实数据发现异常、定位 WordPress 的开放注册入口,并为它补上人机验证、服务端验签、蜜罐与频率限制。
上图为后台真实注册数据:用户名高度随机、注册时间连续、角色统一为 subscriber、评论数均为 0;邮箱等敏感信息已脱敏。
事情是怎么被发现的
最开始只是打开统一用户中心看了一眼。蜡笔小账本有 21 个账号,租的值有 1 个账号,这些数字都符合当前的真实使用情况;但 Horu Blog 突然出现了 378 个账号。
问题在于,这段时间并没有做大规模推广。再往下看,账号又呈现出几乎相同的特征:
- 用户名大多是 9–11 位无意义字母组合;
- 注册集中出现在深夜和凌晨,并持续间隔发生;
- 邮箱来自 Gmail、Yahoo、AOL 等不同服务商,看起来像真实地址;
- 角色全部是 subscriber,评论数全部为 0。
单独看某一个账号,很难断定真假;但把用户名、时间、角色和行为放在一起看,模式就很明显了:这不是自然增长,而是自动化程序在批量提交注册表单。
先纠正一个容易误解的概念
WordPress 后台里的 subscriber 中文通常会显示为“订阅者”,但它不等于“订阅了邮件”或“愿意持续阅读内容”。它只是 WordPress 的最低权限用户角色。
只要注册成功,账号就会进入用户表,并被赋予 subscriber 角色。所以后台数字上涨,并不能直接当成内容增长数据。对一个没有邮箱激活、没有人机验证的开放注册页来说,这个数字甚至可能主要由机器人贡献。
真正的根因:注册入口是裸露的
为了让用户登录后评论,我们开放了 WordPress 原生注册。注册页只要求填写用户名和邮箱,然后系统发送一封设置密码邮件。
这套流程对真人很简单,对机器人同样简单。公开的 WordPress 注册地址很容易被扫描器发现;只要提交的用户名和邮箱格式符合要求,账号就会先写入数据库。页面里原本已有的“防重复提交”,只能避免真人连续点击按钮,并不能识别自动程序。
这也是这次最重要的判断:前端体验优化不等于安全防护。
我们的修复方案:四层防护
第一层:Cloudflare Turnstile 人机验证
注册表单加入 Turnstile。它通常不需要用户挑选红绿灯或公交车图片,而是根据浏览器环境和行为完成风险判断,正常用户的操作成本比较低。
但只把组件放在页面上是不够的。前端返回的 token 必须提交到服务器,再由服务器请求 Cloudflare Siteverify 接口进行验证。只有服务端收到明确的成功结果,WordPress 才能继续创建账号。
第二层:服务端验签
机器人可以跳过网页,直接向注册接口发送请求。因此,是否放行绝不能只由前端决定。我们在 WordPress 的 registration_errors 阶段完成校验:验证失败就返回错误,账号不会写入用户表。
同时检查 token 对应的 action 与 hostname,避免其他页面生成的 token 被拿来复用。Turnstile 的 Secret Key 只保存在服务器配置中,不会出现在主题代码、网页源代码或公开仓库里。
第三层:蜜罐字段
表单里增加一个真人看不到、自动填表程序却可能填写的字段。这个字段只要出现内容,就直接判定为异常提交。
蜜罐不能替代人机验证,但它对低成本脚本非常有效,而且不会给真实用户增加操作。
第四层:注册频率限制
同一来源在 15 分钟内最多提交 6 次。超过限制后暂时拒绝注册。这样即使某个机器人偶尔通过验证,也无法在短时间内批量制造账号。
如果 Turnstile 的生产密钥暂时不可用,系统会自动切换到带服务器签名的算术题,保证注册入口不会重新回到完全无保护的状态。
为什么不直接关闭注册
关闭“任何人都可以注册”当然是最直接的办法。如果 Blog 完全不需要会员和登录评论,这也是最稳妥的选择。
但 Horu Blog 后续仍希望保留用户参与,因此我们选择保留入口,同时把验证补完整。这里没有唯一答案,关键是先问清楚:这个入口是否真的服务于产品目标?如果答案是否定的,就不要为了一个“以后可能会用”的功能承担持续的安全成本。
AI 搭建产品时,最容易漏掉的是入口
用 AI 做页面和功能时,我们很容易把注意力放在“能不能跑起来”:注册按钮能不能点、邮件能不能发、评论能不能提交。功能闭环跑通后,看起来项目就完成了。
但真正上线后,还需要从另一个角度重新检查:
- 这个接口会不会被绕过页面直接调用?
- 一次正常操作能不能被自动重复一万次?
- 页面上的验证,服务端有没有再次确认?
- 异常增长出现时,有没有数据能帮助判断?
- 如果第三方验证暂时不可用,系统是放行还是安全降级?
简单,不只是让正常用户少走一步,也包括让异常程序无路可走。产品真正上线以后,真实数据会不断提醒我们:那些在开发阶段没有出现的问题,才是下一轮迭代最值得解决的内容。
这次留下的检查清单
- 确认 WordPress 是否真的需要开放注册;
- 注册、登录、找回密码和评论表单都要考虑自动化攻击;
- 人机验证必须包含服务端验证,不能只放前端组件;
- 加入蜜罐与频率限制,降低单点防护失效的影响;
- 定期检查新增账号的时间、角色和行为,而不只看总数;
- 清理可疑账号前先备份数据库,并确认没有异常高权限角色。
这次没有推广却出现的 300 多个“用户”,最终变成了一次很具体的产品安全课:上线不是终点,真实世界开始访问你的产品之后,才是测试真正开始的时候。

评论
登录后即可参与讨论 💬
登录 / 注册后评论