Typecho垃圾评论

Typecho垃圾评论骚扰

本博客一直以来就没有多少人评论,突然从今年三月份开始就经常在段时间内突然出现大量的垃圾评论;由于安装了 Comment2Wechat 插件,导致我经常在半夜被大量持续的评论微信通知给吵醒;

虽然这些垃圾评论都需要我审核后才会显示,但还是架不住半夜频繁得骚扰,必须彻底解决这个问题!

图片验证码插件

首先想到的就是在评论表单上添加 图形验证码,以此来过滤那些 Spam Bot 的批量提交评论行为;
由于 Typecho 有优秀的外挂插件机制,也不想对 Typecho 自身的代码作大刀阔斧的修改,所以先找看有没有现成的 Captcha 插件可用;

结果发现 Typecho 当前的生态还真有点悲观!大多数插件都是十几年前开发的,对现在主流的PHP版本也不兼容了,没几个是直接能用的;

最后找到一款基于 secureimage 开发的 Typecho Captcha 插件;不过由于其集成的secureimage组件太老旧了,不兼容 PHP8 导致验证码图片生成不出来,显示红叉!所以 fork 了一份自己完善了一下;

目前本博客的评论区图片验证码效果如下:
图形验证码效果

修改后的插件和使用方式在如下仓库

插件下载地址

插件冲突

由于 Typecho Captcha 和评论微信通知 Comment2Wechat 插件都是注册在 comment 事件上的方法,所以当有任何评论被提交时这两个插件都会同时工作;

// Typecho Captcha 的注册方式
Typecho_Plugin::factory('Widget_Feedback')->comment = array('Captcha_Plugin', 'filter');

// Comment2Wechat 的注册方式
Typecho_Plugin::factory('Widget_Feedback')->comment = array('Comment2Wechat_Plugin', 'sc_send');

所以当有 SpamBot 发了一条垃圾评论,虽然因为没有正确的验证码而被 Typecho Captcha 插件拦截了,但依然会触发 Comment2Wechat 插件向我发送微信评论通知;而这两个插件的执行顺序也没有办法能进行控制;所以我暂时只能先停用了 Comment2Wechat 微信通知插件!

等后面有空了,我计划将微信评论通知的功能集成到我改版的 Typecho Captcha 插件中,实现只在通过了验证码校验后的合法评论提交时才发送微信通知;

SpamBot的盲目尝试

加上评论验证码后,世界总算清净了,已经一个礼拜都没有垃圾评论再打扰我了;今天突然想看下在加了验证码后,是否还会有 SpamBot 在尝试提交垃圾评论;

结果在后台的 nginx 日志中,依然看到每天还是有不少发送 comment 的 POST 请求,这些评论请求虽然都没有提交成功,但服务器返回的状态码却都是 302,而不是 500 这就有点诡异!
SpamBot的垃圾评论提交日志

按照 Typecho Captcha 插件的处理方式,当验证码校验失败时是会返回 500 状态码和错误信息的;我手动模拟测试提交没有验证码和错误验证码的评论请求,都是返回的 500 状态码,评论成功的状态码是 200;什么情况下提交评论会返回 302 重定向呢?

虽然垃圾评论骚扰的问题已经解决了,但这些 SpamBot 每天还是在不停的尝试提交评论,估计这些 bot 的代码逻辑忽略了 302 的响应,或者是将 302 当成了评论成功后跳转回原博文页面的状态码,所以才会每天乐此不疲的尝试发送;

定位 302 的原因

肯定和这些 bot 提交的 POST 请求有关,所以我需要记录这些 302 的完整 POST 请求参数和内容;然后对比正常的评论,查找差异,才能定位到是什么原因或条件才导致服务器返回 302 状态码;

因此我修改 nginxaccess_log 格式,在末尾添加了 [$request_body] 变量,实现在log中记录每一个 POST 请求的完整内容;

经过半天的守株待兔(这些 bot 还真勤劳)终于成功抓到了状态码为 302 的完整 POST 请求;access_log记录如下:

93.183.92.77 - - [09/Mar/2025:19:29:56 +0800] "POST /archives/145.html/comment HTTP/1.0" 302 0 "https://www.moonfly.net/archives/145.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" "-" [receiveMail=yes&text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D1%81%D1%82%D0%B2%D1%83%D0%B9%D1%82%D0%B5%21+%0D%0A%D0%9F%D1%80%D0%B8%D0%BE%D0%B1%D1%80%D0%B5%D1%81%D1%82%D0%B8+%D0%B4%D0%B8%D0%BF%D0%BB%D0%BE%D0%BC+%D0%92%D0%A3%D0%97%D0%B0+%D0%BF%D0%BE+%D0%B2%D1%8B%D0%B3%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9+%D1%86%D0%B5%D0%BD%D0%B5+%D0%B2%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%2C+%D0%BE%D0%B1%D1%80%D0%B0%D1%89%D0%B0%D1%8F%D1%81%D1%8C+%D0%BA+%D0%BD%D0%B0%D0%B4%D0%B5%D0%B6%D0%BD%D0%BE%D0%B9+%D1%81%D0%BF%D0%B5%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B9+%D1%84%D0%B8%D1%80%D0%BC%D0%B5.+%D0%97%D0%B0%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D1%8C+%D0%B4%D0%B8%D0%BF%D0%BB%D0%BE%D0%BC+%D0%BE+%D0%B2%D1%8B%D1%81%D1%88%D0%B5%D0%BC+%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8%3A+<a+href%3Dhttp%3A%2F%2Fpolucxxxxxlom.com%2Fkupit-diplom-magistra-26%2F>polucxxxxxlom.com%2Fkupit-diplom-magistra-26%2F<%2Fa>&author=Sazrhio&mail=xbyonolqrsr%40rudiplomust.com&url=%5Burl%3Dhttp%3A%2F%2Fpolucxxxxxlom.com%2Fkupit-diplom-o-srednem-obrazovanii-31%2F%5Dpolucxxxxxlom.com%2Fkupit-diplom-magistra-26%2F%5B%2Furl%5D&captcha_code=%21UNKNOWN_TYPE%21&submit=&comment_post_ID=&comment_parent=]

日志末尾 [] 的内容就是完整 request_body 的内容,解码后发现竟然还是一条俄文的买卖莫斯科学历文凭的广告垃圾评论;其中虽然有带 captcha_code 这个属性,但显然并没能填写正确的验证码,但这也不是导致 302 的原因;

receiveMail=yes
text=Здравствуйте!+Приобрести+диплом+ВУЗа+по+выгодной+цене+возможно,+обращаясь+к+надежной+специализированной+фирме.+Заказать+диплом+о+высшем+образовании:+<a+href=http://polucxxxxxlom.com/kupit-diplom-magistra-26/>polucxxxxxlom.com/kupit-diplom-magistra-26/</a>
author=Sazrhio
mail=xbyonolqrsr@rudiplomust.com
url=[url=http://polucxxxxxlom.com/kupit-diplom-o-srednem-obrazovanii-31/]polucxxxxxlom.com/kupit-diplom-magistra-26/[/url]
captcha_code=!UNKNOWN_TYPE!
submit=
comment_post_ID=
comment_parent=

接下来我有手动从评论区提交了一个验证码错误的评论请求,并抓取浏览器 POST 的表单数据进行对比!

正常的验证码错误评论请求

发现正常的 POST 请求比 bot 提交的 302 POST 请求中多了2个字段:_checkReferer

经过手动反复测试验证,确认当 POST 请求中不含 _ 属性或 _ 属性的值不正确时,服务器就会返回 302 状态码;只有当这个属性值验证通过后才会进入到后续评论插件的处理流程;

那么应该就是 Typecho 自身就对这个缺少 _ 属性的非法 POST 请求做了过滤才返回的 302,而并非是验证码插件处理的结果!

不过这个 _ 的值看起来应该是一个校验用的 Token 并且针对同一篇文章这个 Token 还是固定的,感觉这个安全性也不太高!

关于 Typecho 自身的反垃圾评论机制

经过各种搜索,最终确定这个 _ 属性和其值,就是 Typecho自身的反垃圾评论功能所添加的 Token 校验值;

Typecho反垃圾评论保护

本博客一直都是有开启这个反垃圾评论的选项的;不过在安装图形验证码插件之前就已经出现垃圾评论的问题了,毕竟这个校验的 Token 针对每一篇文章都是固定的,只要POST请求里写死这个 Token 就可以不停地向同一篇文章无限提交垃圾评论了!

所以依靠 Typecho 自身的反垃圾评论机制仅仅只能阻挡那些低级的、通过抓取评论表单属性来进行自动化提交的简单bot;并不能阻挡有针对 Token 进行 Crack 的高级bot发送垃圾评论;要有效阻挡 bot 还是得依靠人机识别技术;

对我这个小破站来说,上个图片验证码挡一下就足够了!期待遇到更厉害的 bot 让我再次重新评估我的防御方案!

这里要感谢如下这篇文章,对 Typecho 自身的反垃圾 Token 验证原理介绍得非常详细:

改变Typecho对无效评论的处理行为 302 -> 500

如果针对这些无效的 POST 评论请求都能统一返回 500 状态码,那么它们应该就会减少或放弃在我的网站上不停白费功夫的尝试了!

编辑 Typecho 目录下 var/Widget/security.php 中 63 行的 protect() 方法,将原本的 goBack() 方法替换为返回 500 状态码+异常信息;

 60     /**
 61      * 保护提交数据
 62      */
 63     public function protect()
 64     {
 65         if ($this->enabled && $this->request->get('_') != $this->getToken($this->request->getReferer())) {
 66             //$this->response->goBack();    //默认 302 返回前一页面
 67             $this->response->setStatus(500); //改为直接return 500 拒绝
 68             $this->response->throwContent("Illegal request!非法请求!");
 69         }
 70     }

所以我们不要给那些弱智的 Bot 以假象(302),让它似乎以为能看到希望;而要直接明确打破它的幻想(500)让其知难而退!

Last modification:March 11, 2025
如果觉得我的文章对你有用,请随意赞赏