续集:加强服务器安全性保护WordPress

自上次写过WordPress被黑之后,虽然安装了Wordfence,但发现依然不够,Wordfence能反复扫描出各种被黑的PHP木马。

没想到WordPress的安全性如此成问题,看来安全性还是必须从系统入手,不能光依赖WordPress本身。

一、禁用PHP的系统相关函数

首先经过检查,发现虽然篡改了PHP文件,甚至在tmp下面跑了一个进程,但似乎并没有渗透到系统级别,没有获得root权限,所以我立即在php.ini里面禁用了一些系统相关的函数,以防止被获得系统的权限


disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexi
ted,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_ws
topsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pc
ntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriorit
y,pcntl_setpriority,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,cu
rl_multi_exec,parse_ini_file,show_source

二、将WordPress目录下的文件权限改为root

为了防止PHP文件被插入恶意脚本,我将PHP文件都设置为只读,虽然这样会导致WordPress无法自动更新或者是自动安装插件,但要好过被篡改。然而uploads目录是必须要可以写入的,没有这个功能无法上传图片。


# chown -R root .
# chown -R www-data wp-content/uploads

三、禁止 uploads 目录中执行任何 PHP 脚本

在做了前两步之后,我发现依然有黑客可以上传脚本到 uploads 目录下,并通过远程请求调用执行,所以必须要禁止这个目录下的PHP脚本的运行。我在nginx中增加了一下配置


server {
listen 80;
server_name shiningray.cn *.shiningray.cn ;
root /var/www/shiningray;
index index.html index.htm index.php;
include /etc/nginx/php_params;
location ~* /(?:uploads|files)/.*.php$ {
deny all;
}

}

四、配置防火墙

当我还没配置第三步的时候,我通过 netstat 和 lsof 等指令调试系统的时候,发现PHP进程会连接到很多邮件服务器的25(smtp)端口,我想起来之前Linode客服所说的经常能发现我的服务器在投递垃圾邮件,原来是拿我当肉鸡发垃圾邮件。所以我觉得直接堵死smtp端口的连接,就可以断了他们的念想。

我使用Ubuntu的ufw来配置了端口的访问:

表示拒绝任何从本地发起到外部25端口的连接,使用 deny 替换掉 reject 也可以,但是 deny 是丢弃掉链接上发出去的包,而 reject 是直接断开链接。由于木马往往会反复不停的尝试,如果只是deny的话,可能会导致进程被堵死,所以最后选择了 reject

至此之后,再也没有感染过PHP的恶意脚本了。

WordPress标签分隔符插件

WordPress 2.3加入了标签功能,我们可以在撰写文章的时候,在文章编辑框的下方输入一些以“,”分割的标签。这是一个半角的逗号,对于国内的用户来说,经常需要输入中文的标签,这时候中文输入法打出来的是全角的逗号“,”,就必须切换到半角模式或者关闭输入法,再输入一个半角的逗号,输入完了之后还要把输入法切换回来,十分麻烦。目前没有找到任何一种插件可以解决这个问题。当我研究了WordPress的代码之后,发现光写一个插件是不能达到我要的功能的,必须对现有的代码进行一些修改。这是由于WordPress代码本身有一个小小的不完善的地方。下面我给出我的解决方案,希望对大家有用:

找到WordPress安装目录下wp-includes/post.php,打开编辑,将第432行(WordPress 2.3.1版)由原有的:

改为

大家可以看到在这个数组的最后添加了一个’tags_input’, 这样修改的原因是:包含这一行的函数
sanitize_post 要对输入的帖子的数据数组进行过滤和转义,这个数组就是告诉 sanitize_post
帖子数据中有哪些键对应的值需要过滤和转义,正好漏掉了标签对应的键名“tags_input”。另外很有意思的是,我们还能在这一行的上面看到一个 TODO 注释:

// TODO: Use array keys instead of hard coded list

也就是说,WordPress的开发团队注意到了这个问题,将来会直接遍历整个数组(或对象)来进行过滤和转义而非使用这样一个列表。

接下来,将我写的以下代码保存为一个php文件,放到wp-content/plugins目录下:

或者直接下载这个文件:multiple_tag_separator.zip
解压缩后放入/wp-content/plugins目录下。

然后进入你的WordPress的后台,进入“Plugins”管理界面,找到“Multiple Tag Separator”并启用,便可使用这个功能了。

Nginx的WordPress配置

WordPress是一个非常流行的Blog系统,它可以利用Apache的mod_rewrite来实现URL的静态化。安装好的WordPress在配置了持久链接之后,会在网站的根目录下(如果可写)生成一个.htaccess文件,这个文件可以指示Apache如何进行URL重写(如果服务器配置为允许使用htaccess的指令的话),它的内容如下:

这个文件的意思就是,如果当请求的文件不存在,那么把请求内部重定向到/index.php。WordPress会自己分析请求的URL,来判断显示哪个页面。

在上次配置了Nginx+PHP之后,由于Nginx不支持Apache的.htaccess文件,要实现持久连接静态化,我们必须手工配置Nginx的文件。首先找到Nginx的配置文件,默认编译后的配置文件在/usr/local/nginx/conf/nginx.conf;Ubuntu通过包安装的配置文件位于/etc/nginx/nginx.conf,也可以编辑vhost的配置文件,放在了/etc/nginx/sites-available下。

以下是基本的配置(Ubuntu下的范例):

还可以有很多种不同配置方式,例如不改写所有包含wp-的url等。此配置考虑了目录下的索引文件index.html和index.php。-f指令表示测试文件是否存在(不考虑文件和目录的区别),!-f则表示不存在。注意在重写url到index.html后面有个break,而重写到index.php后没有break。因为html文件不需要任何额外工作可以直接发送到客户端,所以重写规则在这里终止,下面就直接让nginx发送文件。而.php文件需要进一步发送到fastcgi进程来运行,Nginx会继续判断该文件符合第二个部分location ~ .*\.php$的规则,并进行FastCGI的转发。

大家可以将以上内容保存为wordpress.conf,然后在自己的vhost配置,即server节中应用该配置文件,例如(以下为Ubuntu进行的配置):

接下来让Nginx重新载入配置文件,便可使用WordPress的持久链接了。