缘由
好久没上我的测试站点,发现多了不少评论(明明我从来没公开过链接,却还是被扫到了 ),其中一条引起我的注意:
***<script>alart(*****);</script>;*****<script>window.location.href='http://*****';</script>******
,打开评论所在页面一看,果然是 xss攻击 。测试站点没有装Wordfence,所以才让攻击者有可承之机,但我平时不会开放测试站的访客访问,多装一个Wordfence很浪费资源,于是我决定修改下评论功能。小站评论框上方一直有这样一句话 Markdown Supported while </> Forbidden
,但实际上评论html也是可以解析的,我觉得从这入手比较好,正巧Sakura主题评论插入图片的方式使用的是安全的BBCode,如果再加入仅允许Markdown评论的功能,那绝大多数XSS就直接被干掉了 。话不多说,下面记录下我的修改过程。
评论使用Ajax
此条为必须,采用Ajax提交评论可以在评论内容写入数据库之前再对评论进行一次检查。Sakura已自带Ajax评论,所以此步省略,Ajax评论引入也很简单,如需了解更多,参考这位大佬写的 WordPress Ajax 提交评论的实现,简单易懂。
过滤无效邮箱
WordPress自带了一个检查评论者邮箱是否为正常邮箱的功能,所以我不再重复添加了。这里主要是验证邮箱是否有效,用到的是 checkdnsrr()
函数,查询评论者邮箱的所属域名有没有MX记录,使用方法如下:
以Sakura主题为例,检查Ajax传入的评论参数 $incoming_comment
,得到评论邮箱 $incoming_comment['comment_author_email']
,使用 explode()
和 array_pop()
函数得到邮箱域名,再用 checkdnsrr()
函数函数检查域名DNS解析中有无 MX
记录。代码如下:
function spirit_comment_check($incoming_comment) {
if(checkdnsrr(array_pop(explode("@",$incoming_comment['comment_author_email'])),"MX") === false)
siren_ajax_comment_err('邮箱写错啦(→_→)<br>Oops,Invalid email!');
return( $incoming_comment );
}
if(!is_user_logged_in())
add_filter( 'preprocess_comment', 'spirit_comment_check' );
禁止html代码
我使用的WP-Editor.md插件,支持评论Markdown,所以无需再引入其他文件来解析评论中的Markdown,那么重点就在如何禁止评论使用HTML标签。PHP自带了一个 strip_tags()
函数,可以把字符串中的HTML标签全部过滤掉,于是就有了下面的代码。
function spirit_comment_check($incoming_comment) {
if(checkdnsrr(array_pop(explode("@",$incoming_comment['comment_author_email'])),"MX") === false) {
siren_ajax_comment_err('邮箱写错啦(→_→)<br>Oops,Invalid email!');
}else{
if($incoming_comment['comment_content'] != strip_tags($incoming_comment['comment_content'])){
siren_ajax_comment_err('评论只支持Markdown啦,见谅╮( ̄▽ ̄)╭<br>Markdown Supported while <i class="fa fa-code" aria-hidden="true"></i> Forbidden');
}
}
return( $incoming_comment );
}
if(!is_user_logged_in())
add_filter( 'preprocess_comment', 'spirit_comment_check' );
但这又有个问题,如果评论者输入的代码块中,包含了 <
的HTML标签,那么就不能提交,于是我又想到一个办法,去掉评论内容中的代码块之后再检查有无HTML标签,下面是修改版:
function spirit_comment_check($incoming_comment) {
$re = '/```([\s\S]*?)```[\s]*|`{1,2}[^`](.*?)`{1,2}|\[.*?\]\([\s\S]*?\)/m';
if(checkdnsrr(array_pop(explode("@",$incoming_comment['comment_author_email'])),"MX") === false) {
siren_ajax_comment_err('邮箱写错啦(→_→)<br>Oops,Invalid email!');
}else{
if(preg_replace($re,'temp',$incoming_comment['comment_content']) != strip_tags(preg_replace($re,'temp',$incoming_comment['comment_content']))){
siren_ajax_comment_err('评论只支持Markdown啦,见谅╮( ̄▽ ̄)╭<br>Markdown Supported while <i class="fa fa-code" aria-hidden="true"></i> Forbidden');
}
}
return( $incoming_comment );
}
if(!is_user_logged_in())
add_filter( 'preprocess_comment', 'spirit_comment_check' );
正则解释如下:
```([\s\S]*?)```[\s]*
过滤掉代码片段,`{1,2}[^`](.*?)`{1,2}
过滤掉行内代码,\[.*?\]\([\s\S]*?\)
过滤掉链接,因为有时候链接标题也会带 <
字符。开始我也担心过滤掉链接会增加被XSS攻击的风险(类似于 [Click Me](javascript:alert(***))
这样的语句),但发现WordPress的kses会自动转义这样的语句,所以就放心使用啦~
打开评论HTML标签限制
完成之后我使用访客模式打开了小站,测试评论功能时发现一个问题,评论部分Markdown格式不能转换,比如:
/**
* nth element in the fibonacci series.
* @param n >= 0
* @return the nth element, >= 0.
*/
function fib(n) {
var a = 1, b = 1;
var tmp;
while (--n >= 0) {
tmp = a;
a += b;
b = tmp;
}
return a;
}
document.write(fib(10));
正常情况下会解析为
<pre><code class="language-javascript ">
/**
* nth element in the fibonacci series.
* @param n >= 0
* @return the nth element, >= 0.
*/
function fib(n) {
var a = 1, b = 1;
var tmp;
while (--n >= 0) {
tmp = a;
a += b;
b = tmp;
}
return a;
}
document.write(fib(10));
</code></pre>
但WP-Editor.md将其解析为了
<code>
/**
* nth element in the fibonacci series.
* @param n >= 0
* @return the nth element, >= 0.
*/
function fib(n) {
var a = 1, b = 1;
var tmp;
while (--n >= 0) {
tmp = a;
a += b;
b = tmp;
}
return a;
}
document.write(fib(10));
</code>
这样的话代码高亮就失效了,体验很是不好。此外还有标题、表格、列表等也不会解析...我反复检查了插件,又翻了不少WordPress的Hook,把插件改了又改,始终没有修复这个Bug(主要还是我太菜了 ,最终只能从修改WordPress限制入手了,将下面代码加到functions.php
//打开评论HTML标签限制
function allow_more_tag_in_comment() {
global $allowedtags;
$allowedtags['pre'] = array('class'=>array());
$allowedtags['code'] = array('class'=>array());
$allowedtags['h1'] = array('class'=>array());
$allowedtags['h2'] = array('class'=>array());
$allowedtags['h3'] = array('class'=>array());
$allowedtags['h4'] = array('class'=>array());
$allowedtags['h5'] = array('class'=>array());
$allowedtags['ul'] = array('class'=>array());
$allowedtags['ol'] = array('class'=>array());
$allowedtags['li'] = array('class'=>array());
$allowedtags['td'] = array('class'=>array());
$allowedtags['th'] = array('class'=>array());
$allowedtags['tr'] = array('class'=>array());
$allowedtags['table'] = array('class'=>array());
$allowedtags['thead'] = array('class'=>array());
$allowedtags['tbody'] = array('class'=>array());
$allowedtags['span'] = array('class'=>array());
}
add_action('pre_comment_on_post', 'allow_more_tag_in_comment');
我添加了部分常用的标签,如果后续遇到不能解析的可尝试在里面继续加入更多标签。最后关闭Wordfence的xss防护,防止不能提交带有 <script
的代码块。
以上就是本次的修改历程,你有什么看法或者是对文中功能的优化吗?欢迎在评论区与我探讨。
Comments | 8 条评论
愿你忘掉旧的不安,拥有新的快乐
大佬大佬~,我问一下下,就是这个东东怎么改呀~~(●’◡’●)

还有这个头头,在我的站是图片加载失败哒~
@?梨花镇的阿肾 https://pbas.club/
@?梨花镇的阿肾 老版本主题是这样的哦,具体改
comments.php
,详情github历史commit@Spirit 嗯嗯,蟹蟹大佬,可是我在comments里面没有找到,而且在我的小破站评论的时候,点击一下输入邮箱,头像连接就会挂

是需要在lib里面改一下地址吗~
@梨花镇的阿肾 原头像cdn弃用,dev 分支已修复,可以 clone 使用了。如果不急的话可以再等一周发布 release ,我在测试一个功能
贵站打开有点慢啊
@云金券 上了cf的cdn,可能不同地区的网络状况不同吧,我这里速度还可以~