使用WordPress计划任务自动处理恶意垃圾注册攻击
作为一个使用著名开源博客程序WordPress建站的站长,都会遇到恶意注册的问题。一夜之间被人刷了无数的垃圾账号,还得麻烦管理去后台挨个删
。经过分析,发现恶意注册和正常的普通注册的区别在于使用的邮箱是否有效以及是否会使用邮箱中的激活链接将账号密码重置。而对于恶意垃圾注册,电子邮箱的使用上一般也采用不存在的邮箱地址,当然也有诸如validator.pizza之类的邮件地址检验接口,但是因为这个validator.pizza是个境外的站点,受到GFW以及链路的影响,导致它的响应延迟相当感人,于是通过电子邮箱辨别恶意垃圾注册变得相当困难。
然而,我们仍然可以从激活这个出发点来考虑,恶意注册不会激活,他的目的只是注册若干个账号而非登录使用与站长等用户互动,所以,我们可以先接受他的注册,如果一定时间内仍然没有激活之前注册的账号,则认定为垃圾注册,直接删掉这些用户即可。
首先,需要在注册用户的时候执行标记未激活和添加计划任务的操作:
define("AUTO_REMOVE_DELAY",43200); function jz_add_delete_schedule($user_id){ global $wpdb; $wpdb->update($wpdb->users,['user_status'=>1],['ID'=>$user_id]); //因为单站点WordPress没有用到user_status,也就没有引入操作这一项的代码,故直接操作数据库 wp_schedule_single_event( time()+AUTO_REMOVE_DELAY,'jz_check_user_actived',[$user_id]); //(单次任务执行时间,触发的action,附带的参数列表) } add_action('register_new_user','jz_add_delete_schedule');
然后在用户激活的时候,将事先添加的计划任务删除,同时将用户标记为已激活:
function jz_cancel_schedule($user){ if($user->user_status!=1) return; //非新用户重置密码 //更新用户状态 global $wpdb; $wpdb->update($wpdb->users,['user_status'=>0],['ID'=>$user->ID]); //取消删号计划任务 wp_clear_scheduled_hook( 'jz_check_user_actived',[$user->ID]); } add_action('after_password_reset','jz_cancel_schedule');
以上是正常用户使用的流程,而对于垃圾注册,则会在超时后触发计划任务:
function jz_remove_spam_user($user_id){ $user = get_user_by('id',$user_id); if($user->data->user_status==0) return; //逾期已激活,不做任何处理 //逾期未激活,删号处理,wp_delete_user属于admin函数,在计划任务执行时不会引入,故仍然需要自己手动改数据库 global $wpdb; $wpdb->delete( $wpdb->users,['ID' => $user_id]); $wpdb->delete( $wpdb->usermeta,['user_id'=>$user_id]); } add_action('jz_check_user_actived','jz_remove_spam_user');
至此,一个具有自动删除垃圾注册账号的功能完成了。需要注意的是,这里使用的user表中user_status字段对于多站点的WordPress是有其他用途的,而对于单站点WordPress是没有用到的,故直接用它当做是否激活的标记。
默认情况下,WordPress的计划任务是由访客触发的,由于访客访问的频率不定,导致WordPress的计划任务有相当大的几率会向后延迟,而不能准时完成既定任务。好在WordPress有提供一个额外的接口,在Linux上可以使用crontab为WordPress提供更加准确的计划任务计时基准。启用这种使用额外的计时接口,首先要先把访客触发计划任务的功能禁用,否则会产生额外的负载。在wp-config.php中添加:
define('DISABLE_WP_CRON', true);
对于这个额外的接口,即调用wp-cron.php即可.考虑到这个接口是仅限服务器自己调用,则也不考虑使用HTTPS这些会额外增加负载的操作,而使用http则可能会从外部带来安全风险。考虑到这种禁止外部使用,又不需要加密的通信方式,于是我祭出了Unix Domain Socket。
在Nginx和PHP之间的通讯即使用的Unix Socket,如果能够把curl至nginx的通讯也从普通的TCP转为UnixSocket,即可实现一个仅限本地进程间通信的通信通路。首先创建出Nginx的基于UnixSocket的虚拟主机:
# # Cron Server configuration # server { listen unix:/tmp/WP-Cron-Server; root /path/to/siteroot/; index wp-cron.php; server_name wp-cron-server; location ~ \.php{ include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.0-fpm.sock; } access_log /var/log/nginx/WP-Cron.access.log; error_log /var/log/nginx/WP-Cron.error.log; }
然后使用curl模拟请求wp-cron.php:
curl -H "User-Agent:WP-Cron Excuter" --unix-socket /tmp/WP-Cron-Server http://wp-cron-server/
在curl参数URL位置写的http://wp-cron-server/因为给定的–unix-socket参数,curl将不会再通过DNS获取wp-cron-server的IP地址,相当于手动给curl提供了通讯目的地址,同时wp-cron-server也会被放在HTTP的Host头中,提供给Nginx以匹配到特定的虚拟主机。然后就是设置crontab。考虑这个任务应该是由Nginx(网站)运行使用的用户(即www-data)完成,所以计划任务也加入到www-data的crontab中。
*/10 * * * * curl -H "User-Agent:WP-Cron Excuter" --unix-socket /tmp/WP-Cron-Server http://wp-cron-server/
对于这个wp-cron到底多久调用一次WordPress官方并没有明确说明。个人认为使用各个计划任务间隔的最大公约数即可。当然也可以简单粗暴的直接一分钟,十分钟一次,这个执行间隔也决定了最大的误差时间。
至此,我们可以上对应的计划任务日志中查看执行情况:
可以看到,每隔1分钟,都会被调用一次。
评论
您需要 先登录 才可以回复.