操作环境
操作系统信息:
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:
要获取评论内容,我们就需要使用 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_action
将 zkf_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 所示:
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