个人网站的表单留言功能一般用于与访客交流,有多种成熟的垃圾评论管理方法可供选择,如 reCAPTCHA、Akismet 插件,甚至直接使用第三方评论交流工具。
然而,对于一些企业网站,尤其是产品推广类网站来说,表单通常用于获取客户询价,是访客转化的重要工具,因此网站管理员在处理垃圾留言时通常会非常谨慎。
不幸的是,很多提供留言功能的企业网站每天都会收到大量垃圾留言。一些企业的信息系统中,正常的信息甚至会被淹没在垃圾信息之中,大量精力被浪费在处理这些信息上。

这迫使企业不得不采取措施,例如增加验证码、使用关键词屏蔽、网址屏蔽功能、留言评级插件等。
但这里会有几个问题:
一是增加验证码会增加操作复杂度,很多企业不希望给客户制造额外障碍,以免因此劝退潜在客户;
二是网站咨询本身就比较有限,企业又担心关键词屏蔽或者评级插件误伤正常留言,从而遗漏商机。
针对这种情况,我之前的做法是限制重复提交,控制一条信息中链接的数量,同时增加灰名单功能。灰名单中的留言如果包含指定关键词,就直接进入垃圾箱;而留言中包含黑名单关键词,则直接拒绝提交。这样可以屏蔽掉一部分具有明显特征的垃圾留言。

然而,仍然有不少漏网之鱼。他们要么发送纯垃圾信息,要么发送各种推销信息。
每天分析这些留言、提取新的屏蔽关键词,本身又变成了一件非常耗费时间的事情。如何以较低的成本,更长期地解决这些问题,就成了不得不考虑的事情。
实际上,经过分析可以发现,这些垃圾留言很多都是通过程序自动提交的。一些不良爬虫在抓取网页之后,会分析网页中的表单,然后直接向表单的 action 地址发送数据。因此可以推测,大部分垃圾留言并不是通过正常浏览器行为产生的。
那么,是否可以直接通过 User-Agent 白名单进行屏蔽?
肯定不行。
攻击者如果连 User-Agent 伪装都不会,基本也很难成为真正的问题。
那么,使用浏览器和不使用浏览器,究竟有什么区别?
最主要的区别在于页面资源的处理方式。
试想一个访客来到一个包含表单的页面之后,浏览器通常会先加载页面 HTML,然后继续解析并加载页面中的其它资源,例如图片、CSS、JavaScript 等,最后将页面渲染出来。
而很多简单的爬虫并不会完整执行这些操作,它们可能只获取 HTML,然后直接分析其中的表单结构并发送请求。
基于这一点,我们就可以做一些操作。
当浏览器正常解析页面时,通过某个请求给客户端写入一个 Cookie。之后浏览器再次请求其他资源,包括提交表单时,都会自动携带这个 Cookie。
而一个只抓取 HTML、没有进一步加载页面资源的简单程序,通常不会获得这个 Cookie。
于是,我们就可以在表单处理程序中检查这个 Cookie,从而拦截一部分自动提交的垃圾留言。
具体实现方法如下。
页面中增加请求
在页面中增加一段 JavaScript,向后端服务器发送请求。
如果网站使用 CDN,则需要保证这个请求能够正常回源。如果网站本身已经存在其他每次都需要回源的请求,也可以考虑复用。
例如:
<script>
new Image().src = '//example.com/getcaptcha.php';
</script>
这里使用一个图片请求,是因为它不需要在页面中实际显示图片,同时也能够触发一次 HTTP 请求。
后端设置 Cookie
后端接受这个请求,并根据情况给客户端设置 Cookie。
如果有需要,还可以加入随机值或者签名,以便进行更复杂的校验。
示例代码:
<?php
header('Cache-Control: private, max-age=3600');
header('Content-Type: image/gif');
if (isset($_COOKIE['domai_captcha'])) {
setcookie(
'domai_captcha',
time(),
time() + 36000,
'/'
);
}
echo base64_decode(
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
);
?>
这里的示例只是一个最简单的逻辑。实际使用时,通常应该在首次请求时就设置一个随机 Cookie,并在后续提交时进行有效性验证,而不是只依赖 Cookie 是否存在。
在表单处理程序中检查
最后,在表单处理程序中进行检查:
<?php
if (!isset($_COOKIE['domai_captcha'])) {
header('HTTP/1.1 403 Forbidden');
die('禁止提交,非法访问');
}
// 下面是表单处理逻辑
?>
如果没有这个 Cookie,就认为当前请求可能不是经过正常页面访问流程产生的,从而拒绝提交。
当然,这只是一个逻辑示例,实际业务中的处理程序通常会更加复杂。
CMS中的实现
大部分网站都使用 CMS 进行管理,因此直接修改源代码并不是特别优雅。
较好的 CMS 通常会通过 Hook、Filter 或其它接口提供扩展能力。例如 Domai CMS、WordPress 都可以通过类似的方式实现。
例如在 Domai CMS 的 functions.php 中:
<?php
function _modify_feedback($request) {
// 不让没有获取 Cookie 的请求直接提交表单
if (!isset($_COOKIE['domai_captcha'])) {
return new DM_Error(
__('Message Not Sent', 'hitoy'),
__('Unauthorized Request, Please Use Your Browser to Access and Enable Javascript!', 'hitoy')
);
}
// 下面还可以对请求数据进行进一步处理,
// 例如检查前台提交了大量后端未定义的字段等
}
add_filter('pre_submit_feedback', '_modify_feedback');
?>
这样就不需要修改 CMS 本身的核心代码,而是通过接口对留言提交过程进行控制。
如果用户没有启用JavaScript怎么办?
有聪明的读者可能会想到:我一个访客都不想错过,如果访客的浏览器没有启用 JavaScript 怎么办?其实也可以使用 noscript 标签:
<noscript>
<img
width="1"
height="1"
src="//example.com/getcaptcha.php"
alt=""
>
</noscript>
这样,即使 JavaScript 没有执行,也可以通过图片请求完成 Cookie 的初始化。
最终,我们通过这种方式成功减少了大量垃圾留言。按照当时的实际测试,垃圾留言数量减少了 90% 以上。剩余的一小部分,再通过后台 IP 屏蔽、关键词过滤和人工管理等方式处理,整体工作量就会大大减少。
这种方式最大的特点是:对正常访客几乎无感,同时又可以在服务器端增加一道自动化筛选。
当然,上述代码只是为了说明基本逻辑。实际业务实现还需要根据网站环境进行更完善的设计,例如 Cookie 校验、请求频率限制、来源判断以及更严格的服务端验证等。
另外,这种方法并不是万能的。一旦攻击者了解网站的防护逻辑,就可能针对性地模拟正常浏览器行为。因此,它更适合作为多层防护中的一环,而不应该被认为是唯一的反垃圾机制。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。