剖析 WordPress `wp_kses()` 函数源码:`_wp_kses_allowed_html` 钩子如何自定义白名单。

各位靓仔靓女,晚上好!我是今晚的主讲人,很高兴能和大家一起聊聊WordPress的 wp_kses() 函数,特别是它背后的白名单机制以及如何通过 _wp_kses_allowed_html 钩子来个性化定制。

咱们今天的主题是:“解剖 wp_kses():玩转 _wp_kses_allowed_html 钩子,定制你的白名单”

一、 wp_kses():WordPress 的安全卫士

在WordPress的世界里,安全至关重要。wp_kses() 函数就像一位尽职尽责的保安,专门负责过滤用户提交的HTML代码,防止XSS(跨站脚本攻击)等安全漏洞。简单来说,它会根据你预先设定的“白名单”,只允许白名单里的HTML标签和属性通过,其余的统统干掉。

举个栗子,假设你允许用户在评论区使用 <b> 标签加粗文字,那么 wp_kses() 就会放行 <b>Hello</b>,但如果用户试图插入 <script>alert('XSS')</script>,这位保安就会毫不留情地把它拦截下来。

二、 白名单的真面目:一个多维数组

wp_kses() 的核心在于它的白名单,它是一个复杂的多维数组,定义了哪些HTML标签和属性是被允许的。这个数组的结构大致如下:

$allowed_html = array(
    'tag_name' => array(
        'attribute_name' => array(), // 允许的属性,空数组表示允许任何值
        'attribute_name2' => array('value1', 'value2'), // 允许的属性,只允许特定值
    ),
    'another_tag' => array(
        // ...
    ),
);
  • tag_name: 允许的HTML标签名,比如 'p''a''img' 等。
  • attribute_name: 该标签允许的属性名,比如 'href''src''class' 等。
  • 属性值: 这是一个数组,定义了该属性允许的值。
    • 如果数组为空 array(),则表示该属性允许任何值。
    • 如果数组包含字符串,则表示该属性只允许这些特定的值。

三、 默认白名单:WordPress 的默认设置

WordPress本身已经提供了一个默认的白名单,用于处理文章内容、评论等常见场景。这个默认白名单定义在 wp-includes/kses.php 文件中(不同的WordPress版本可能会略有差异)。

我们可以用 wp_kses_allowed_html( 'post' ) 来获取用于文章内容的默认白名单:

$allowed_html = wp_kses_allowed_html( 'post' );

echo '<pre>';
print_r($allowed_html);
echo '</pre>';

运行这段代码,你会看到一个庞大的数组,里面包含了WordPress允许在文章内容中使用的各种HTML标签和属性。

四、 _wp_kses_allowed_html 钩子:定制白名单的利器

但是,WordPress的默认白名单可能无法满足所有需求。比如,你可能想允许用户在文章中使用自定义的HTML标签,或者允许某些标签使用额外的属性。这时候,_wp_kses_allowed_html 钩子就派上用场了。

_wp_kses_allowed_html 是一个过滤器钩子(filter hook),允许你修改 wp_kses_allowed_html() 函数返回的白名单数组。这意味着你可以添加、删除或修改白名单中的任何元素,从而完全掌控 wp_kses() 的行为。

五、 实战演练:用钩子定制白名单

下面,我们通过几个具体的例子来演示如何使用 _wp_kses_allowed_html 钩子定制白名单。

场景 1:允许 <iframe> 标签嵌入视频

默认情况下,WordPress不允许使用 <iframe> 标签,因为它可能被用于嵌入恶意代码。但是,如果你想允许用户嵌入YouTube或Vimeo视频,就可以使用下面的代码:

add_filter( 'wp_kses_allowed_html', 'allow_iframe_in_content', 10, 2 );

function allow_iframe_in_content( $allowed_tags, $context ) {
    if ( 'post' === $context ) { // 只针对文章内容
        $allowed_tags['iframe'] = array(
            'src'             => true,
            'height'          => true,
            'width'           => true,
            'frameborder'     => true,
            'allowfullscreen' => true,
        );
    }
    return $allowed_tags;
}

这段代码做了什么?

  1. add_filter( 'wp_kses_allowed_html', 'allow_iframe_in_content', 10, 2 );: 这行代码注册了一个过滤器,将 allow_iframe_in_content 函数绑定到 _wp_kses_allowed_html 钩子上。10 是优先级,2 是传递给回调函数的参数数量。
  2. allow_iframe_in_content( $allowed_tags, $context ): 这是我们的回调函数,它接收两个参数:
    • $allowed_tags: 当前的白名单数组。
    • $context: wp_kses_allowed_html() 函数被调用的上下文,比如 'post'(文章内容)、'comment'(评论内容)等。
  3. if ( 'post' === $context ) { ... }: 我们只希望在文章内容中允许 <iframe> 标签,所以在代码中进行了判断。
  4. $allowed_tags['iframe'] = array( ... );: 这行代码向白名单数组中添加了 <iframe> 标签,并指定了允许的属性,包括 srcheightwidthframeborderallowfullscreentrue 表示这些属性允许任何值。
  5. return $allowed_tags;: 最后,我们必须返回修改后的白名单数组。

现在,你就可以在文章中使用 <iframe> 标签嵌入视频了。

场景 2:允许 <p> 标签使用 class 属性

默认情况下,wp_kses() 可能会移除 <p> 标签上的 class 属性。如果你想允许用户在段落中使用自定义的CSS类,可以使用下面的代码:

add_filter( 'wp_kses_allowed_html', 'allow_p_class_attribute', 10, 2 );

function allow_p_class_attribute( $allowed_tags, $context ) {
    if ( isset( $allowed_tags['p'] ) ) {
        $allowed_tags['p']['class'] = true;
    }
    return $allowed_tags;
}

这段代码非常简单:

  1. if ( isset( $allowed_tags['p'] ) ) { ... }: 首先,我们检查白名单中是否已经存在 <p> 标签。
  2. $allowed_tags['p']['class'] = true;: 如果 <p> 标签存在,我们就允许它使用 class 属性,true 表示允许任何值。
  3. return $allowed_tags;: 返回修改后的白名单数组。

现在,你就可以在文章中使用 <p class="my-custom-class"> 这样的代码了。

场景 3:限制 <a> 标签的 rel 属性的值

有时候,你可能想允许某个属性,但只允许特定的值。比如,你可能想允许 <a> 标签使用 rel 属性,但只允许 noopenernofollow 这两个值。可以使用下面的代码:

add_filter( 'wp_kses_allowed_html', 'limit_a_rel_attribute', 10, 2 );

function limit_a_rel_attribute( $allowed_tags, $context ) {
    if ( isset( $allowed_tags['a'] ) ) {
        $allowed_tags['a']['rel'] = array( 'noopener', 'nofollow' );
    }
    return $allowed_tags;
}

这段代码的关键在于:

  1. $allowed_tags['a']['rel'] = array( 'noopener', 'nofollow' );: 我们明确指定了 rel 属性只允许 noopenernofollow 这两个值。

现在,wp_kses() 只会允许 rel="noopener"rel="nofollow",其他的 rel 属性值都会被移除。

场景 4:移除某个标签

如果你想完全禁止某个HTML标签的使用,可以使用下面的代码:

add_filter( 'wp_kses_allowed_html', 'remove_script_tag', 10, 2 );

function remove_script_tag( $allowed_tags, $context ) {
    unset( $allowed_tags['script'] );
    return $allowed_tags;
}

这段代码非常简单粗暴:

  1. unset( $allowed_tags['script'] );: 直接从白名单数组中移除 'script' 标签。

现在,任何 <script> 标签都会被 wp_kses() 移除。

六、 注意事项:安全第一!

在使用 _wp_kses_allowed_html 钩子定制白名单时,务必注意以下几点:

  • 只允许必要的标签和属性: 尽量保持白名单的精简,只允许确实需要的标签和属性。不要为了方便而随意放宽限制。
  • 仔细验证属性值: 如果允许某个属性,尽量限制它的取值范围,只允许特定的值。
  • 了解安全风险: 在允许新的标签或属性之前,务必了解它们可能带来的安全风险。比如,<iframe> 标签可能被用于嵌入恶意网站,onclick 属性可能被用于执行恶意JavaScript代码。
  • 测试!测试!再测试!: 修改白名单后,一定要进行充分的测试,确保没有引入新的安全漏洞。

七、 总结:掌握白名单,掌控安全

wp_kses() 函数是WordPress安全体系的重要组成部分,而 _wp_kses_allowed_html 钩子则是定制白名单的利器。通过掌握白名单的结构和使用方法,你可以根据自己的需求,灵活地调整 wp_kses() 的行为,既保证了网站的安全,又满足了用户的个性化需求。

希望今天的讲座对大家有所帮助。记住,安全无小事,时刻保持警惕,才能构建一个安全可靠的WordPress网站!

最后,祝大家编程愉快,bug永不相见! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注