各位观众老爷,早上好、中午好、晚上好! 欢迎来到“老码农的碎碎念”系列讲座。 今天咱们要聊的是WordPress里一个非常重要的函数:wp_kses()
。 别看这名字怪里怪气的,它可是WordPress安全的一道重要防线,专门用来过滤HTML的。 你可能会问,为什么要过滤HTML? 难道我们辛辛苦苦写的代码,还不配让它显示出来吗? 哎,还真不配!
HTML安全:一场永无止境的猫鼠游戏
想象一下,如果WordPress不对HTML进行任何过滤,那将会发生什么? 坏人们就可以在评论里、文章里甚至用户名里,偷偷塞一些恶意代码,比如 JavaScript。 这些 JavaScript 就像小偷一样,可能会偷走用户的Cookie、重定向用户到钓鱼网站,甚至直接控制你的网站! 这可不是闹着玩的,轻则损失用户数据,重则网站被黑,名誉扫地。
所以,为了保护网站的安全,WordPress必须对HTML进行严格的过滤。 wp_kses()
就是承担这项重任的“安全卫士”。 它的核心思想很简单:白名单机制。
什么是白名单机制?
简单来说,白名单机制就是“允许什么,禁止什么”。 wp_kses()
维护着一份允许使用的 HTML 标签、属性和协议的清单。 只有出现在这份清单上的内容,才能被保留;其他的一律被干掉! 这就像一个严格的门卫,只允许持有特定通行证的人进入,其他人统统挡在门外。
wp_kses()
的基本用法
wp_kses()
函数的基本用法非常简单:
$safe_html = wp_kses( $string, $allowed_html, $allowed_protocols );
$string
: 你要过滤的HTML字符串。$allowed_html
: 一个数组,定义了允许使用的HTML标签和属性。$allowed_protocols
: 一个数组,定义了允许使用的URL协议(比如http
,https
,mailto
)。
返回值 $safe_html
就是经过 wp_kses()
过滤后的安全HTML字符串。
$allowed_html
:白名单的核心
$allowed_html
是 wp_kses()
的灵魂所在。 它是一个多维数组,每一项代表一个允许使用的HTML标签,以及该标签允许使用的属性。
举个例子,如果我们只想允许使用 <a>
标签,并且只允许使用 href
, title
两个属性,那么 $allowed_html
应该这样写:
$allowed_html = array(
'a' => array(
'href' => true,
'title' => true,
),
);
这里:
'a'
是标签名。'href' => true
表示允许使用href
属性,true
表示不限制href
属性的值。'title' => true
表示允许使用title
属性,true
表示不限制title
属性的值。
如果我们要允许使用 <img>
标签,并且允许使用 src
, alt
, width
, height
属性,并且对 src
属性的值进行更严格的限制(比如,只允许 http
和 https
协议),那么可以这样写:
$allowed_html = array(
'img' => array(
'src' => true,
'alt' => true,
'width' => true,
'height' => true,
),
);
$allowed_protocols
:URL协议的卫士
$allowed_protocols
数组用于指定允许使用的URL协议。 默认情况下,WordPress允许的协议包括 http
, https
, ftp
, mailto
, news
, irc
, gopher
, nntp
, telnet
, mms
, rtsp
, svn
, tel
, fax
, xmpp
等。
如果你想自定义允许的协议,可以这样写:
$allowed_protocols = array( 'http', 'https', 'mailto' );
这样,wp_kses()
就只会允许 http
, https
, mailto
这三种协议的URL。
深入 wp_kses()
源码
光说不练假把式。 咱们现在就来扒一扒 wp_kses()
的源码,看看它到底是怎么工作的。 友情提示:源码略长,请准备好你的咖啡和小零食。
wp_kses()
函数位于 wp-includes/kses.php
文件中。 它的主要流程如下:
- 准备工作: 初始化一些变量,例如
$allowed_html
和$allowed_protocols
。 - 预处理: 对输入的HTML字符串进行一些预处理,例如将所有标签转换为小写。
- 循环处理: 遍历HTML字符串,查找标签和属性。
- 标签过滤: 如果找到了一个标签,就检查它是否在
$allowed_html
中。 如果不在,就直接删除该标签。 - 属性过滤: 如果标签在
$allowed_html
中,就检查它的每个属性是否在$allowed_html
中。 如果不在,就删除该属性。 还会对属性的值进行进一步的检查,例如检查URL协议是否在$allowed_protocols
中。 - 递归处理: 如果标签包含子标签,就递归调用
wp_kses()
函数来处理子标签。 - 后处理: 对处理后的HTML字符串进行一些后处理,例如将一些特殊字符转换为HTML实体。
下面是一些关键代码片段的解读:
1. 预处理:将标签转换为小写
$string = preg_replace_callback( '%<(/?)([^> ]*)( |/|>)%', 'kses_strtolower', $string );
这段代码使用正则表达式,将HTML标签转换为小写。 这样做是为了避免大小写不一致导致的安全问题。 例如,<A>
和 <a>
应该被视为同一个标签。
2. 标签过滤:判断标签是否在白名单中
if ( ! isset( $allowed_html[ $tagname ] ) ) {
$string = str_replace( '<' . $origtag, '<' . $origtag, $string );
$string = str_replace( '</' . $origtag, '</' . $origtag, $string );
continue;
}
这段代码判断标签 $tagname
是否在 $allowed_html
数组中。 如果不在,就将该标签转换为HTML实体,从而使其失效。 例如,<script>
标签会被转换为 <script>
。
3. 属性过滤:判断属性是否在白名单中
if ( ! isset( $allowed_html[ $tagname ][ $attrname ] ) ) {
$string = str_replace( $whole_attribute, '', $string );
continue;
}
这段代码判断属性 $attrname
是否在 $allowed_html
数组中。 如果不在,就直接删除该属性。
4. 属性值过滤:检查URL协议
if ( 'href' === $attrname || 'src' === $attrname || 'cite' === $attrname ) {
$string = kses_bad_protocol( $string, $allowed_protocols );
}
这段代码针对 href
, src
, cite
等属性,调用 kses_bad_protocol()
函数来检查URL协议。 kses_bad_protocol()
函数会遍历 $allowed_protocols
数组,判断URL协议是否在允许的列表中。 如果不在,就将该URL替换为空字符串。
一些需要注意的地方
wp_kses()
并不是万能的。 它只能过滤已知的恶意代码。 如果有新的攻击方式出现,wp_kses()
可能无法有效地防御。wp_kses()
的性能并不是很高。 因为它需要遍历整个HTML字符串,并进行大量的字符串操作。 因此,在处理大量HTML数据时,应该尽量避免频繁调用wp_kses()
函数。wp_kses()
的白名单配置需要根据实际情况进行调整。 如果你的网站需要使用一些特殊的HTML标签或属性,你需要将它们添加到$allowed_html
数组中。
代码示例:自定义白名单
假设我们想创建一个允许用户输入简单的加粗和斜体文本的表单。我们可以这样自定义白名单:
function my_kses_allowed_html( $allowed_html, $context ) {
if ( 'my_custom_form' === $context ) {
$allowed_html = array(
'strong' => array(),
'em' => array(),
'br' => array() // 允许换行
);
}
return $allowed_html;
}
add_filter( 'wp_kses_allowed_html', 'my_kses_allowed_html', 10, 2 );
// 使用方法
$user_input = $_POST['my_custom_field']; // 假设这是用户输入
$safe_html = wp_kses( $user_input, wp_kses_allowed_html( array(), 'my_custom_form' ) );
echo $safe_html;
在这个例子中:
- 我们定义了一个名为
my_kses_allowed_html
的函数,该函数接收$allowed_html
和$context
两个参数。 $context
参数允许我们根据不同的场景使用不同的白名单。 这里我们定义了一个名为'my_custom_form'
的上下文。- 如果
$context
为'my_custom_form'
,我们就将$allowed_html
数组设置为只允许使用<strong>
,<em>
和<br>
标签。 - 我们使用
add_filter()
函数将my_kses_allowed_html
函数添加到wp_kses_allowed_html
过滤器中。 - 在使用
wp_kses()
函数时,我们将$context
参数设置为'my_custom_form'
,从而使用我们自定义的白名单。
更复杂的示例:允许带样式的段落
如果我们想允许用户使用 <p>
标签,并且允许用户使用 style
属性来设置段落的样式,但是只允许设置 color
和 text-align
两个样式属性,该怎么做呢?
这有点复杂,因为 wp_kses()
默认情况下不会对 style
属性的值进行过滤。 为了实现这个目标,我们需要编写一个自定义的过滤函数,并将其添加到 wp_kses_attr
过滤器中。
function my_kses_style_attributes( $out, $tagname, $stylename, $stylevalue, $okprotocols ) {
$allowed_styles = array(
'color',
'text-align',
);
if ( 'style' === $stylename && in_array( $tagname, array( 'p' ), true ) ) {
if ( in_array( strtolower( trim( $stylevalue ) ), $allowed_styles, true ) ) {
return $out; // 允许该样式
} else {
return ''; // 拒绝该样式
}
}
return $out; // 其他情况保持不变
}
add_filter( 'wp_kses_attr', 'my_kses_style_attributes', 10, 5 );
function my_kses_allowed_html_with_style( $allowed_html ) {
$allowed_html['p'] = array(
'style' => true,
);
return $allowed_html;
}
add_filter( 'wp_kses_allowed_html', 'my_kses_allowed_html_with_style' );
// 使用方法
$user_input = '<p style="color: red; text-align: center; font-size: 20px;">这是一个带样式的段落。</p>';
$safe_html = wp_kses( $user_input, wp_kses_allowed_html() );
echo $safe_html; // 输出: <p style="color: red; text-align: center;">这是一个带样式的段落。</p>
在这个例子中:
my_kses_style_attributes
函数用于过滤style
属性的值。 它首先检查标签名是否为'p'
,然后检查style
属性的值是否为color
或text-align
。 如果是,就允许该样式;否则,就拒绝该样式。my_kses_allowed_html_with_style
函数用于允许<p>
标签的style
属性。- 我们分别使用
add_filter()
函数将my_kses_style_attributes
和my_kses_allowed_html_with_style
函数添加到wp_kses_attr
和wp_kses_allowed_html
过滤器中。
总结
wp_kses()
是WordPress安全的重要组成部分。 理解它的工作原理,并学会自定义白名单,可以帮助你更好地保护你的网站免受恶意代码的攻击。
当然,wp_kses()
只是安全的第一步。 还有很多其他的安全措施需要采取,例如使用强密码、及时更新WordPress版本、安装安全插件等等。
希望今天的讲座对你有所帮助! 如果你有什么问题,欢迎在评论区留言。 咱们下次再见!