与WordPress安全相关的一些建议

WordPress算是最易安装和使用的PHP程序之一,就在不久前,它在全网的使用率达到25% ,并且这个百分比会进一步变大(数据源:看这里)。

这意味着 WordPress 至少对开发者或使用者是友好的,不幸的是,这同样意味着 WordPress 是众多心怀不轨的黑客心目中的一个大蛋糕,可是,开发者或使用者可以通过通过听取以下几个建议让自己的 WordPress 实例更安全。

安全第一

像许多程序一样,WordPress 最大的安全威胁并非其自身代码或者服务器,而是与之交互的用户。

1.密码要够强壮

这里说的密码至少涵盖:服务器的SSH密码(如果你清楚的知道自己并没有SSH密码的话,可以忽略)、 FTP密码、MySQL密码、WordPress 管理员密码,它们要做够强壮。

WordPress 自 v3.7 版本起,已采用了一个基于Dropbox 的 zxcvbn 库的  密码强度指示器 ,这个工具能让你显而易见的知道自己的 WordPress 管理员密码的强壮度,但这是不够的,你还要同样重视上面提到的其它几个密码的强壮度。

2.使用密码管理工具

一个安全的密码应该是唯一的,也就是说,你最好不要在多个应用/网站上使用它,它应该仅仅用在一个地方: WordPress 管理员密码就是 WordPress 管理员密码,而不应该再被用在它处了,那么问题来了:密码要很强壮,那么它就很复杂并且很长,密码要是唯一的,那么,就会有许多密码,怎么办?答案是:你应该使用一个密码管理工具。

如果你经常使用 Windows 平台,你可以使用 KeePass ,这是一个开源软件,并且有简体中文包;如果你使用 Mac/Mac Book ,那么,你可以选择  1Password 或者 Lastpass 。

3.使用HTTPS

如果您的网站还未采用SSL,那么,它应该采用SSL。

你对网站的每一次访问,都会发送一个含有cookie的请求到网站服务器,如果某人要拦截这个请求,他们可以使用相同的 cookie 来冒充是你来做一些见不得光的不法勾当。

在服务器上安装SSL证书的代价并不像国内那些卖SSL证书的网站的标价那样吓人,许多主机商会以很优惠的价格出售SSL证书,安装此证书并正确设置了您的服务器环境( Nginx/Apache )之后,浏览器与您的服务器之间的通信/交互时加密的,也就是说,对这些通信/交互的拦截行为在大多数情况下都是无意义的了。

4.不要信任任何人

在保障了浏览器/客户端与您的网站服务器之间的通信时安全的前提下,该考虑下是谁在使用包括浏览器在内的客户端与您的网站通信了。

江湖险恶,你懂的,虽然我们应该相信大多数访客都是善良的,但是不能排除就有那么一小撮居心不良的人尝试以各种方法搞定您的网站,如果您对自己的网站持呵呵态度的话,看到这儿,您就可以关闭这个页面,该干啥干啥去了。

用户管理

上述建议可以说是泛泛而论,几乎适用于任何在线服务,以下是针对 WordPress 的具体安全建议。

5.一种用户,一个角色

一个常见的 WordPress 管理错误是在一个 WordPress 网站上干不同事儿的人,在使用一个用户账户 — 这个账户可能是在 WordPress 被安装时创建的,并且用户并还很可能是 admin (这得有多糟糕,建议你还是将其改为尽量臭长无意义一些更好),这样真的不好。

WordPress 内置了复杂精巧的角色和权限管理系统 ,您完全可以为不同的干不同事情的同事们每人分配一个用户账户,并且制定特定的角色。

长话短说,除非您需要修改管理员设置,否则不要使用管理员账户。

内置的“投稿者”这个角色用于创建内容(其创建的内容并不会即时发布);

内置的“作者”这个角色用于创建和发布内容;

内置的“编辑”这个角色用于创建、发布和修改其它用户发布的内容;

内置的“管理员”这个角色用于管理整个网站的一切

6.自定义用户角色和能力

您应该理解并能正确使用 WordPress 内置的角色与能力系统,这个系统内置的一些角色已经在上面列举了,每个角色都用不同的“能力”或者说权力,并且,您还可以自己创建角色以及与之相对应的“能力”。

举例来说:您在网站后台有个广告管理页面,并且您想仅有“广告管理员”身份的用户可以管理这个页面,可以这么搞:

//adding a custom "Ad Manager" role
function add_role_ad_manager() {
    add_role( 
        'ad-manager', 
        __( 'Ad Manager', 'cwp' ), 
        array( 
            'read'       => true, 
            'manage-ads' => true,
        )
    );
}
add_action( 'init', 'add_role_ad_manager' );

在创建了“广告管理员”这个角色后,就可以在这个广告管理页面的头部添加个判断了,示例如下:

if ( current_user_can( 'manage-ads' ) ) {
    // ... playground for ad manager...
} else {
    wp_die( __( 'WTF!You are not allowed to do this!.', 'cwp' ) );
}

代码安全

WordPress 开发者应遵循一些约定俗称的编码中的最佳实践,主要的原因是:涉及到安全。

7.过滤所有输入

这个是老生常谈了,大家都懂:不要相信任何的用户输入内容,因为用户输入的内容大多数情况下是要出现在查询或者插入之类的SQL语句的。

在 WordPress 的世界里,这意味着任何的 $_GET、$_POST、$_REQUREST 数据/变量都应该被过滤:

// basic filter example.
$integer = absint( $_GET['number'] ); // an integer > 0
$boolean = (bool) $_POST['selected']; // boolean
$title = sanitize_title( $_POST['title'] );      // sanitize a title input
$input = sanitize_textarea( $_POST['content'] ); // sanitize a textarea input

//if the content may contain HTML, pass it through "kses" to "strip evil scripts"
$content = wp_kses_post( $_POST['content'] );

//if you need to whitelist a specific array of tags, instead use wp_kses

$content = wp_kses( $_POST['input'], array(
    'a'      => array(
        'href'  => array(),
        'title' => array(),
    ),
    'br'     => array(),
    'em'     => array(),
    'strong' => array(),
) );

8.验证所有需要输出的数据

同样的,当您需要打印输出到浏览器时,您应该把数据库当做用户,不要相信它给你的东西,将它给你的数据也过滤一遍。

大多数主题作者在主题中输出一篇文章时,可能是这样的:

<article data-id="<?php echo $post->id; ?>">
    <title><?php echo $post->title; ?></title>
    <section>
        <?php echo $post->content; ?>
        <a href="<?php echo $post->permalink; ?>">Read more...</a>
    </section>
</article>

上面的示例看起来没什么不妥,其实不然,下面的这样才是安全的:

<article data-id="<?php echo esc_attr( $post->id ); ?>">
    <title><?php echo esc_html( $post->title ); ?></title>
    <section>
        <?php echo esc_html( $post->content ); ?>
        <a href="<?php echo esc_url( $post->permalink ); ?>">Read more...</a>
    </section>
</article>

善用 WordPress 内置的如下过滤函数,才会更安全:

  • esc_attr() 过滤 HTML attributes
  • esc_html() 过滤 HTML 内容
  • esc_url() 过滤 URL
  • esc_js() 过滤传递给 JavaScript 的值
  • esc_textarea() 过滤传递给 textarea 的值

更多数据验证函数的列表: WordPress Codex.

9.及时更新

当有重要的安全更新时, WordPress 可以自动更新 (自v3.7起),以保护您的网站,但并非全部需要更新的东西:WordPress 核心、主题、插件都是可以自动更新的,通常是会在网站后台给你一个更新提示,当你看到那个或者那些提示时,就应该在备份网站数据库后进行更新了。

当然了,及时更新并非仅包括 WordPress或者其主题、插件,还包括其运行环境:PHP版本是否过时?OpenSSL是否有新的补丁了?您使用的Nginx是否是最新的稳定版本?您使用的MySQL是否有安全威胁?

10.及时备份

这个算是后补的招数吧:当前面所有的建议你都做了,或者有哪些建议您没有做,导致网站被干掉,您可以先通过备份及时恢复在线业务,再通过分析服务器的日志来查找原因,以查漏补缺。

结论

以上所有所说的与 WordPress 安全相关的内容 对那些自以为“我只差一个程序员”的网站经营者或管理者是无效的,因为他们不知道什么是安全,更不了解 WordPress,总以为 WordPress 是免费的,它就得什么都给我做好,这种想法是错误的。本文首发索凌网络,转载请保留原始链接。

Meteor

Meteor 是一个用来创建实时交互、超高性能、体验超好的下一代APP的工具,或者说它开创了一种新的模式,2012年4月就发布了,开始时不怎么支持Windows,现在,对Windows以及Windows用户似乎还是鄙视有加,不过这遮不住它的光芒,因为我私下也觉得作为幕后“专利流氓”的 Windows 他爹微软将会是人类历史上的尔曹之辈(温习:尔曹身与名俱裂,不废江河万古流)。

Meteor 的特性

全栈 JavaScript ;

实时刷新;

使用 MongoDB 存储数据,并延伸到客户端;

开发快速 (你至少应熟知 JavaScript 和 MogoDB);

采用MIT许可证(之前采用GPL许可证,在大家的抱怨声中,改为MIT许可证了);

 

体验Meteor

1. 安装Meteor

Linux/OS X 系统上的安装:

curl https://install.meteor.com/ | sh

Windows 系统上的安装:

点击这里下载Meteor installer,下载后安装。

2. 导入Meteor 官方的一个TODO演示

meteor create --example todos

然后:

cd todos
meteor

Meteor

打开上述链接:

http://127.0.0.1:3000

Meteor todos example

 

3. 生成 IOS/Android 平台的APP

由于 Meteor 目前还不支持在 Windows 上制作 IOS/Android平台上的APP:

Meteor does not support generating app on Windows systerm

而我使用的是 Windows 操作系统,所以,我还不能使用 Meteor 来制作 APP。

 

Get time : 获取某一特定时刻的时间戳

本文记录如何准确获取某个时刻的时间戳。

可能需要设置时区,也可能不需要

当需要返回诸如 Y-m-d 之类的时间时,需要先设置时区,设置时区,可以使用PHP提供的date_default_timezone_set( $timezone_name );,有时候,给定的参数并不一定是 timezone_name 时,就需要先使用一个方法确定下时区的设定:

function set_current_timezone($timezone){


    if( false===stripos( $timezone,':' ) ){

        $timezone_name = $timezone;

    }else{

        $timezone = strtolower($timezone);

        $timezone = str_replace('utc', '', $timezone) ;     

        $timezone = preg_replace('/[^0-9]/', '', $timezone) * 36;

        $timezone_name = timezone_name_from_abbr(null, $timezone, true);

        if( false===$timezone_name ){

             $timezone_name = timezone_name_from_abbr(null, -10* 3600, false);
        }

    }

    date_default_timezone_set( $timezone_name );  
    /*
    May be you need it.
     */
    return $timezone_name;

}

不需要返回时,注释掉上面最后一句即可。

可接受的参数示例:

/*Timezone Name*/
$timezone = 'Asia/ShangHai';
/*Timezone offset*/
$timezone = 'UTC+10:00';

因为 UTC+10:00 这个时区(Pacific/Honolulu)比较特殊(暂无法解决夏令时的问题/ Honolulu does not observe Daylight Saving Time  year and year ), 所以,需要使用上面的方法特别处理下。

获取准确的时间戳

比如:你想获取上周开始的时间戳、上周结束的时间戳、去年开始的时间戳、昨天开始的时间戳等等,可以用下面的方法:

/**
 * Get time.
 * @param  string  $symbol      return time for what;
 * @param  string  $time_zone   timezone name or timezone offset,such as 'Asia/ShangHai' , 'UTC+10' ;
 * @param  boolean $return_date return time in 'Y-m-d H:i:s' format or not;
 * @return string               timestamp or time in 'Y-m-d H:i:s' format;
 * @author suifengtec
 * @link   http://www.suoling.net/get-time/
 */
function get_time( $symbol='N', $time_zone='Asia/ShangHai', $return_date = false ){

    $symbol = strtolower(trim($symbol));
    date_default_timezone_set($time_zone);

    switch($symbol){

        case 'n':
        case 'now':
            $time = time();     
        break;    
        case 'last_year_start':
        case 'lys':
            $time = mktime(0, 0, 0, 1, 1, date('Y')-1);     
        break;
        case 'last_year_end':
        case 'lye':
             $time = mktime(0, 0, -1, 1, 1, date('Y'));    
        break;
        case 'this_year_start':
        case 'tys':
            $time = mktime(0, 0, 0, 1, 1, date('Y'));     
        break;
        case 'last_month_start':
        case 'lms':
            $time = mktime(0, 0, 0, date('m')-1, 1, date('Y'));     
        break;
        case 'last_month_end':
        case 'lme':
            $time = mktime(0, 0, -1, date('m'), 1, date('Y'));    
        break;
        case 'this_month_start':
        case 'tms':
             $time =  mktime(0, 0, 0, date('m'), 1, date('Y'));     
        break;
        case 'this_month_end':
        case 'tme':
                $time =  mktime(0, 0, -1, date('m')+1, 1, date('Y'));   
        break;
        case 'last_week_start':
        case 'lws':
            $time = mktime(0, 0, 0, date('n'), date('j')-6, date('Y')) - ((date('N'))*3600*24);  
        break;
        case 'last_week_end':
        case 'lwe':
            $time = mktime(23, 59, 59, date('n'), date('j'), date('Y')) - ((date('N'))*3600*24);     
        break;
        case 'this_week_start':
        case 'tws':
            $time = mktime(0, 0, 0, date('n'), date('j'), date('Y')) - ((date('N')-1)*3600*24); 
        break;
        case 'yestoday_start':
        case 'ys':
             $time = mktime(0, 0, 0, date('m'), date('d')-1, date('Y'));   
        break;
        case 'yestoday_end':
        case 'ye':
             $time = mktime(23, 59, 59, date('m'), date('d')-1, date('Y'));   
        break;
        case 'today_start':
        case 'ts':
             $time = mktime(0, 0, 0, date('m'), date('d'), date('Y'));   
        break;

    }

    if(!$return_date){

        return $time;
    }


    return date('Y-m-d H:i:s',$time);


}

参数使用请参见上面的函数。

获取某个周期开始和结束的时间戳

function get_duration($symbol='T'){


            $symbol = strtolower($symbol);

            switch($symbol){

                case 't':
                case 'today':

                    $time['from'] = mktime(0, 0, 0, date('m'), date('d'), date('Y'));  
                    $time['to'] = time();  

                break;
                case 'yestoday':

                    $time['from'] = mktime(0, 0, 0, date('m')-1, date('d'), date('Y'));  
                    $time['to'] = mktime(0, 0, -1, date('m'), date('d'), date('Y'));   

                break;
                case 'this_week':

                    $time['from'] = mktime(0, 0, 0, date('n'), date('j'), date('Y')) - ((date('N')-1)*3600*24); 
                    $time['to'] = time();  

                break; 


                case 'last_week':

                    $time['from'] = mktime(0, 0, 0, date('n'), date('j')-6, date('Y')) - ((date('N'))*3600*24);  
                    $time['to'] = mktime(23, 59, 59, date('n'), date('j'), date('Y')) - ((date('N'))*3600*24);  

                break;

                case 'this_month':

                    $time['from'] = mktime(0, 0, 0, date('m'), 1, date('Y'));      
                    $time['to'] = time();    

                break;


                case 'last_month':

                    $time['from'] = mktime(0, 0, 0, date('m')-1, 1, date('Y'));     
                    $time['to'] = mktime(0, 0, -1, date('m'), 1, date('Y'));    

                break;

                case 'this_year':

                    $time['from'] = mktime(0, 0, 0, 1, 1, date('Y'));     
                    $time['to'] = time();    

                break;
                case 'last_year':

                    $time['from'] = mktime(0, 0, 0, 1, 1, date('Y')-1);       
                    $time['to'] = mktime(0, 0, -1, 1, 1, date('Y'));   

                break;


            }

            return $time;


    }

 

迁移WordPress到SSL

记录如何将WordPress从一个不支持SSL的服务器迁移到SSL。

什么是SSL

维基百科是这么说的:

传输层安全协议英语:Transport Layer Security,缩写为 TLS),及其前身安全套接层Secure Sockets LayerSSL)是一种安全协议,目的是为互联网通信,提供安全及数据完整性保障。在网景公司(Netscape)推出首版Web浏览器的同时提出SSL,IETF将SSL进行标准化,1999年公布了 TLS标准文件。

SSL包含记录层(Record Layer)和传输层,记录层协议确定了传输层数据的封装格式。传输层安全协议使用X.509认证,之后利用非对称加密演算来对通信方做身份认证,之后交换对称密钥作为会谈密钥(Session key)。这个会谈密钥是用来将通信两方交换的数据做加密,保证两个应用间通信的保密性和可靠性,使客户与服务器应用之间的通信不被攻击者窃听。

SSL在服务器和客户机两端可同时被支持,目前已成为互联网上保密通讯的工业标准。现行的Web浏览器亦普遍将HTTP和SSL相结合,从而实现安全通信。

总之吧,就是为了安全,为了让网站获取更好的排名(貌似针对百度效果不明显,不过Google很在乎:http://googlewebmastercentral.blogspot.com/2014/08/https-as-ranking-signal.html),或者是被迫的(某些第三方服务商强制使用SSL,比如LinkedIn),或者让你的网站看起来更有逼格

 

购买SSL服务

某些域名服务商为自己出售的域名提供廉价或限期免费的SSL,这是最可取的,因为如果你专门购买SSL服务的话,会很贵。

确认服务器支持SSL

查看下你的服务器是否支持SSL,如果不支持,就需要处理了,甚至重新编译PHP。

使用私钥换取证书

在服务器的终端( XShell 之类的) 生成私钥(key后缀)以及请求证书所需要的文件(csr后缀):

openssl req -new -newkey rsa:2048 -nodes -keyout coolwp_com.key -out coolwp_com.csr

生成后,要记住这两个文件的位置,再次说明文件用途:

coolwp_com.key -- 私钥,在稍后的Nginx配置中,需要这个文件;
coolwp_com.csr -- CSR文件;

然后,将csr文件提交给SSL服务商。

SSL服务商会据你提交的csr文件,给你几个crt文件。

证书的使用

将几个crt文件合并为一个,放在服务器上某个 Apache/Nginx可以访问的位置,下面的例子以Nginx和COMODO颁发的证书为例:

证书用途说明:

AddTrustExternalCARoot.crt -- 根CA证书;
COMODORSAAddTrustCA.crt -- 中间CA证书;
COMODORSADomainValidationSecureServerCA.crt -- 中间CA证书;
coolwp_com.crt (或者是根域名或者是某一个你指定的子域名);

合并证书:

cat coolwp_com.crt COMODORSADomainValidationSecureServerCA.crt  COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt > ssl.crt

在Nginx的某个配置文件( 比如vhost目录下新建一个ssl.conf )中,添加:

server {
        listen       443;
        server_name coolwp.com www.coolwp.com;
        root /www/web/actuaryapp_com/public_html;
	ssl on;
	ssl_certificate /证书路径/ssl.crt;
	ssl_certificate_key 私钥路径/server.key;
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        index  index.html index.php index.htm;
        location ~ \.php$ {
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.php;
		fastcgi_param  HTTPS on;
                include fcgi.conf;
        }
 
}

重载nginx服务,看下配置文件是否有误,无误的话,重启nginx:

WordPress SSL

不建议强制SSL,不过如果想,可以这么做:

server {
	listen 80;
	server_name coolwp.com www.coolwp.com;
	rewrite ^(.*) https://$server_name$1 permanent;
}