WordPress开发:实现过滤包含恶意代码的危险评论的插件

操作环境

操作系统信息:

master@ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:    bionic

WordPress 信息:

主程序版本:WordPress 5.3.2
主题及版本:Twenty Sixteen(版本:2.0, 由WordPress团队开发)

需求分析

运行在公网中的 WordPress 站点极易遭受 SQL 注入攻击以及 XSS 跨站脚本攻击等。即便是一个不开放用户注册功能的个人博客,也存在攻击切入点,例如评论框。目前版本的 WordPress (WordPress 5.3.2) 本身并不会把可能包含恶意代码的评论删除,在 WordPress 没有被暴露出漏洞的时候,这么做可能并没有什么问题,但是一旦被发现存在漏洞,那么允许包含恶意代码的数据存入数据库就会变得很危险。因此,我的需求就是,一旦评论中被检测到存在可能的恶意代码,就要把该评论删除,同时,用一条表达警示信息的预置标准评论代替该评论,从而使得任何可能包含恶意代码的评论都不被写入数据库,最大程度地防范 SQL 注入攻击和 XSS 跨站脚本攻击等。

格式说明

  • 在本文中所出现的代码中,凡是我自己定义的函数和变量等,一般都会以 zkf_ 作为其前缀;
  • 没有使用 zkf_ 作为前缀的函数或者变量等,一般情况下都是 WordPress 平台或者 PHP 提供的。
  • 在没有特殊说明的情况下,下文中出现的 “WordPress” 和 “WP” 所代表的是相同的含义。
  • 本文代码块中出现的 ... ... 表示这些位置还存在其他代码,但与当前要说明的问题关系不大,故省略之。

实现过程

前面的需求可以转换成如下步骤,如图 1:

图 1.

要获取评论内容,我们就需要使用 WordPress 平台提供的如下函数:

wp_insert_comment( array $commentdata )

关于该函数的更多详细信息参见 References [1].

该函数的作用是将一个评论插入数据库中。该函数提供了很多参数,我们只需要使用其中一个参数,如下:

comment_content

comment_content 参数可以返回评论的内容,类型为 string.

wp-includes/comment.php 文件中,我们可以找到如下钩子激活点:

function wp_insert_comment( $commentdata ) {

... ...

/**
         * Fires immediately after a comment is inserted into the database.
         *
         * @since 2.8.0
         *
         * @param int        $id      The comment ID.
         * @param WP_Comment $comment Comment object.
         */
        do_action( 'wp_insert_comment', $id, $comment );

... ...

}

去掉注释更清楚:

function wp_insert_comment( $commentdata ) {

... ...

    do_action( 'wp_insert_comment', $id, $comment );

... ...

}

关于该钩子激活点的更多详细信息参见 References [2].

由上面提到的 do_action( 'wp_insert_comment', $id, $comment ); 钩子激活点的格式我们知道,wp_insert_comment 这个钩子函数可以提供两个参数,分别是评论 ID $id 和评论内容 $comment. 因此,在定义动作函数 zkf_comment_check() 时,我们同样需要为其设置两个参数,这样才能完成挂载。

接下来,使用 add_actionzkf_comment_check() 函数挂载到 wp_insert_comment, 我们之后的实现代码将写在 zkf_comment_check() 动作函数中:

add_action('wp_insert_comment', 'zkf_comment_check', 10, 2);

根据上面的代码,我们知道,该挂载点的优先级为 10, 需要为该挂载点 (即 zkf_comment_check() 函数) 传入参数的个数为 2.

接下来,开始实现 zkf_comment_check() 函数。

首先定义 zkf_comment_check() 函数并为其传入两个参数:

function zkf_comment_check($zkf_comment_id, $zkf_comment_object){

}

之后,开始在 zkf_comment_check() 函数中写入具体的实现代码。

首先创建一个数组,命名为 $zkf_commentarr

$zkf_commentarr = array();

接着使用从钩子函数那里接收到的评论 ID 为数组赋值:

$zkf_commentarr['comment_ID'] = $zkf_comment_id;

在上面的代码中,我们用到了 WP 平台提供的 comment_ID 函数,该函数的作用是返回当前评论的 ID. 详情可参见 References [3].

之后,通过 comment_content 参数获取当前评论的内容:

$zkf_new_comment = $zkf_comment_object->comment_content;

之后,将评论中的字母全部转换成小写,作为防止大小写绕过的第一道关卡:

$zkf_new_comment_check = strtolower($zkf_new_comment);

通过分析常见的 SQL 注入语句和 XSS 跨站脚本攻击代码,我总结出了如下危险关键字,一旦评论中出现了这些关键字,则评论者极有可能是在尝试进行攻击:

  • +
  • #
  • %
  • ||
  • @
  • @@
  • insert
  • update
  • delete
  • and
  • or
  • union
  • select
  • from
  • where
  • limit
  • order by
  • guoup by

我们要对上面这些危险关键字进行过滤,实现代码如下:

$zkf_str = "+\\#\\--\\%\\||\\@\\@@\\insert\\update\\delete\\and\\or\\union\\select\\from\\where\\limit\\order by\\guoup by\\<script>\\</script>";

使用 \\ 做分割标记,将字符打散为数组 $zkf_arr:

$zkf_arr=explode("\\",$zkf_str);

接下来,使用 PHP 提供的 foreach() 函数,对数组 $zkf_arr 中的每一个键值进行遍历,之后,在 foreach() 函数中使用 stripos 函数对评论内容和取出的键值进行忽略大小写的比较(这里是防止大小写绕过的第二道关卡)并将比对的结果赋值给 $zkf_flag. 若 $zkf_flag = flase, 则代表评论中不包含预置的危险代码,若 $zkf_flag = true, 则代表评论中包含预置的危险代码,此时,评论内容会被替换成“《该评论中发现危险代码,已被箭幕防御系统危险评论拦截模块删除。》”,原来含有危险代码的评论会被丢弃,不会写入数据库。代码如下:

foreach($zkf_arr as $zkf_key => $zkf_val){

//使用 PHP strpos() 函数对内容进行过滤
//stripos() 函数不区分大小写
//strpos() 函数区分大小写
/*
前面已经将所有字符都转换成小写了
这里使用 stripos() 函数再次忽略大小写
可以起到双保险的作用
*/
    $zkf_flag=stripos($zkf_new_comment_check,$zkf_val);
    if ($zkf_flag){
        $zkf_commentarr['comment_content'] = "《该评论中发现危险代码,已被箭幕防御系统危险评论拦截模块删除。》";
    }
}

去掉注释更清楚:

foreach($zkf_arr as $zkf_key => $zkf_val){
    $zkf_flag=stripos($zkf_new_comment_check,$zkf_val);
    if ($zkf_flag){
        $zkf_commentarr['comment_content'] = "《该评论中发现危险代码,已被箭幕防御系统危险评论拦截模块删除。》";
    }
}

最后,使用 wp_update_comment 函数对数据库中已经存在的评论进行一次更新,关于该函数的更多详细信息参见 References [4].

完整的实现代码如下:

//危险评论内容过滤模块

//添加评论时触发 zkf_comment_check() 函数
add_action('wp_insert_comment', 'zkf_comment_check', 10, 2);

//定义 zkf_comment_check() 函数
//传入评论 ID 和评论内容两个参数
function zkf_comment_check($zkf_comment_id, $zkf_comment_object){

//创建数组 $zkf_commentarr
    $zkf_commentarr = array();

//PHP 会把单引号里的内容当成纯文本
//单引号里面是什么就理解成什么
//PHP 中的方括号[]和花括号{}都可以用来访问数组单元对应的值
//为数组赋值
    $zkf_commentarr['comment_ID'] = $zkf_comment_id;

//获取当前的评论内容
    $zkf_new_comment = $zkf_comment_object->comment_content;

//使用 PHP strtolower() 函数将评论内容中的
//将所有字母都转换成小写
    $zkf_new_comment_check = strtolower($zkf_new_comment);

//把数据库查询代码和JS标记代码都视作危险代码
    $zkf_str = "+\\#\\--\\%\\||\\@\\@@\\insert\\update\\delete\\and\\or\\union\\select\\from\\where\\limit\\order by\\guoup by\\<script>\\</script>";

//使用 \\ 做分割标记,将字符打散为数组
    $zkf_arr=explode("\\",$zkf_str);
    foreach($zkf_arr as $zkf_key => $zkf_val){

//使用 PHP strpos() 函数对内容进行过滤
//stripos() 函数不区分大小写
//strpos() 函数区分大小写
/*
前面已经将所有字符都转换成小写了
这里使用 stripos() 函数再次忽略大小写
可以起到双保险的作用
*/
    $zkf_flag=stripos($zkf_new_comment_check,$zkf_val);
        if ($zkf_flag){
            $zkf_commentarr['comment_content'] = "《该评论中发现危险代码,已被箭幕防御系统危险评论拦截模块删除。》";
        }
    }

//对数据库中已经存在的评论进行更新
    wp_update_comment($zkf_commentarr);
}

去掉注释更清楚:

add_action('wp_insert_comment', 'zkf_comment_check', 10, 2);

function zkf_comment_check($zkf_comment_id, $zkf_comment_object){

    $zkf_commentarr = array();

    $zkf_commentarr['comment_ID'] = $zkf_comment_id;

    $zkf_new_comment = $zkf_comment_object->comment_content;

    $zkf_new_comment_check = strtolower($zkf_new_comment);

    $zkf_str = "+\\#\\--\\%\\||\\@\\@@\\insert\\update\\delete\\and\\or\\union\\select\\from\\where\\limit\\order by\\guoup by\\<script>\\</script>";

    $zkf_arr=explode("\\",$zkf_str);
    foreach($zkf_arr as $zkf_key => $zkf_val){

    $zkf_flag=stripos($zkf_new_comment_check,$zkf_val);
        if ($zkf_flag){
            $zkf_commentarr['comment_content'] = "《该评论中发现危险代码,已被箭幕防御系统危险评论拦截模块删除。》";
        }
    }

    wp_update_comment($zkf_commentarr);
}

运行效果如图 2 所示:

图 2.

References:
[1]. wp_insert_comment() | Function | WordPress Developer Resources
https://developer.wordpress.org/reference/functions/wp_insert_comment/
[2]. wp_insert_comment | Hook | WordPress Developer Resources
https://developer.wordpress.org/reference/hooks/wp_insert_comment/
[3]. comment_ID() | Function | WordPress Developer Resources
https://developer.wordpress.org/reference/functions/comment_id/
[4]. wp_update_comment() | Function | WordPress Developer Resources
https://developer.wordpress.org/reference/functions/wp_update_comment/

EOF