详解 WordPress `wp_nonce_field()` 函数的源码:如何生成表单中的 `Nonce` 字段,并解释其在后台管理中的作用。

各位同学,今天咱们来聊聊 WordPress 后台安全中的一个重要角色:wp_nonce_field() 函数。说白了,它就是个小卫士,专门负责在表单里埋雷,防止坏人搞事情。 别看它名字长,其实原理很简单,咱们一点一点来扒开它的源码,看看这小卫士是怎么工作的。

第一部分:Nonce 是个啥?

在深入代码之前,先搞清楚 Nonce 是个什么东西。 Nonce,全称 "Number used once",就是“一次性使用的数字”。 听起来高大上,其实就是一串随机生成的字符串,每次使用都会变,用完就作废。

把它想象成你银行卡上的动态密码,每次登录或者转账都会生成一个新的,用完旧的就失效了。 这样,即使坏人截获了你某一次的密码,也没法下次再用,安全性就大大提高了。

在 WordPress 里,Nonce 主要用来防止 CSRF (Cross-Site Request Forgery) 攻击,也就是“跨站请求伪造”。 简单来说,就是坏人诱骗你点击一个链接,然后偷偷以你的名义发送一个请求到你的 WordPress 网站,比如修改文章、删除用户等等。 如果你的网站没有 Nonce 保护,那坏人就可以为所欲为了。

第二部分:wp_nonce_field() 函数的真面目

wp_nonce_field() 函数的作用就是在 HTML 表单里插入一个隐藏的字段,这个字段的值就是 Nonce。 当表单提交的时候,WordPress 会验证这个 Nonce 是否有效,如果无效,就拒绝执行操作,从而防止 CSRF 攻击。

咱们先来看看 wp_nonce_field() 函数的定义:

function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) {
    $nonce_field = wp_nonce_form_field( $action, $name, $referer, $echo );
    if ( $echo ) {
        return;
    }

    return $nonce_field;
}

看起来很简单,实际上它只是调用了 wp_nonce_form_field() 函数,然后根据 $echo 参数决定是否直接输出 HTML 代码。 咱们重点看看 wp_nonce_form_field() 函数。

function wp_nonce_form_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) {
    $name = esc_attr( $name );
    $nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . wp_create_nonce( $action ) . '" />';

    if ( $referer ) {
        $nonce_field .= wp_referer_field( false );
    }

    if ( $echo ) {
        echo $nonce_field;
    } else {
        return $nonce_field;
    }
}

代码看起来也不复杂,咱们一步一步分析:

  1. $action 参数: 这个参数是 Nonce 的关键,它是一个字符串,用来标识这个 Nonce 的用途。 比如,如果是修改文章的表单,$action 可以是 'edit_post';如果是删除用户的表单,$action 可以是 'delete_user'记住,不同的操作一定要使用不同的 $action,否则 Nonce 就失去了意义。 如果 $action 传入的是 -1,WordPress 内部会使用 wp_magic_number() 函数生成一个随机的字符串作为 $action
  2. $name 参数: 这个参数是 HTML 表单中隐藏字段的 name 属性,默认值是 '_wpnonce'。 你可以修改它,但一般情况下保持默认值就可以了。
  3. $referer 参数: 这个参数决定是否在表单中包含一个 Referer 字段,用来验证请求的来源。 默认值是 true,表示包含 Referer 字段。 Referer 字段实际上就是 $_SERVER['HTTP_REFERER'] 的值,也就是请求页面的 URL。 注意:Referer 字段并不是完全可靠的,因为有些浏览器或者防火墙可能会篡改或者屏蔽它。
  4. $echo 参数: 这个参数决定是否直接输出 HTML 代码。 默认值是 true,表示直接输出。 如果你想把 HTML 代码作为字符串返回,可以把 $echo 设置为 false

接下来,我们看看 wp_nonce_form_field() 函数的核心代码:

$nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . wp_create_nonce( $action ) . '" />';

这行代码生成了一个隐藏的 HTML input 字段,nameid 属性都设置为 $name 参数的值,value 属性设置为 wp_create_nonce( $action ) 函数的返回值。

wp_create_nonce( $action ) 函数才是生成 Nonce 的关键。 咱们接下来重点分析它。

第三部分:wp_create_nonce() 函数的秘密

wp_create_nonce() 函数负责生成 Nonce 字符串。 咱们来看看它的源码:

function wp_create_nonce( $action = -1 ) {
    $i = wp_nonce_tick();

    if ( is_numeric( $action ) ) {
        $action = (string) $action;
    }

    $nonce = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );

    return $nonce;
}

代码看起来还是不复杂,咱们一步一步分析:

  1. $action 参数:wp_nonce_form_field() 函数一样,这个参数是 Nonce 的关键,用来标识这个 Nonce 的用途。
  2. $i = wp_nonce_tick(); 这行代码调用了 wp_nonce_tick() 函数,获取一个时间戳。 这个时间戳会定期更新,用来保证 Nonce 的时效性。 咱们稍后会详细分析 wp_nonce_tick() 函数。
  3. $nonce = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 ); 这行代码是生成 Nonce 的核心。 它做了以下几件事:
    • 把时间戳 $i$action 和当前用户的 ID 拼接成一个字符串。
    • 使用 wp_hash() 函数对这个字符串进行哈希,生成一个更长的字符串。
    • 截取哈希后的字符串的最后 12 位,再取前10位作为 Nonce。

咱们再来看看 wp_hash() 函数和 wp_nonce_tick() 函数。

wp_hash() 函数:

wp_hash() 函数用来生成哈希值。 它的源码如下:

function wp_hash( $data, $scheme = 'auth' ) {
    static $wp_hasher;

    if ( empty( $wp_hasher ) ) {
        require_once ABSPATH . 'wp-includes/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true );
    }

    return $wp_hasher->HashPassword( $data );
}

它实际上使用了 PasswordHash 类来生成哈希值。 PasswordHash 类是一个专门用来处理密码哈希的类,它使用了一种叫做 "Portable PHP password hashing framework" 的算法,安全性比较高。

wp_nonce_tick() 函数:

wp_nonce_tick() 函数用来获取一个时间戳,这个时间戳会定期更新,用来保证 Nonce 的时效性。 源码如下:

function wp_nonce_tick() {
    /**
     * Filters the lifespan of nonces.
     *
     * @since 2.5.0
     *
     * @param int $lifespan Nonce lifespan in seconds. Default is DAY_IN_SECONDS.
     */
    $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );

    return ceil( time() / ( $nonce_life / 2 ) );
}

它做了以下几件事:

  1. 通过 apply_filters( 'nonce_life', DAY_IN_SECONDS ) 获取 Nonce 的有效期,默认值是 DAY_IN_SECONDS,也就是一天。 你可以通过 nonce_life 过滤器来修改 Nonce 的有效期。
  2. 把当前时间戳除以 Nonce 有效期的一半,然后向上取整。 这样,每隔 Nonce 有效期的一半,wp_nonce_tick() 函数的返回值就会改变一次,从而保证 Nonce 的时效性。

总结一下 wp_create_nonce() 函数的流程:

  1. 获取一个时间戳,这个时间戳会定期更新。
  2. 把时间戳、$action 和当前用户的 ID 拼接成一个字符串。
  3. 使用 wp_hash() 函数对这个字符串进行哈希。
  4. 截取哈希后的字符串的一部分作为 Nonce。

第四部分:Nonce 的验证:wp_verify_nonce() 函数

生成 Nonce 的目的是为了验证它。 WordPress 使用 wp_verify_nonce() 函数来验证 Nonce 是否有效。 咱们来看看它的源码:

function wp_verify_nonce( $nonce, $action = -1 ) {
    $nonce = (string) $nonce;

    $i = wp_nonce_tick();

    // Nonce generated 0-12 hours ago
    $expected = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
        return 1;
    }

    // Nonce generated 12-24 hours ago
    $expected = substr( wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
        return 2;
    }

    /**
     * Fires when a nonce verification fails.
     *
     * @since 4.4.0
     *
     * @param string|int $nonce  The nonce that was attempted to be verified.
     * @param string|int $action The context of the nonce check.
     */
    do_action( 'wp_nonce_failed', $nonce, $action );

    return false;
}

代码逻辑如下:

  1. 获取当前的时间戳。
  2. 根据当前的时间戳、$action 和当前用户的 ID,生成一个预期的 Nonce。
  3. 比较预期的 Nonce 和传入的 Nonce 是否相等。 如果相等,说明 Nonce 有效,返回 1
  4. 如果预期的 Nonce 和传入的 Nonce 不相等,说明 Nonce 可能已经过期。 WordPress 会尝试用上一个时间戳来生成预期的 Nonce,然后再次比较。 如果相等,说明 Nonce 是在 12-24 小时之前生成的,仍然有效,返回 2
  5. 如果两次比较都不相等,说明 Nonce 无效,触发 wp_nonce_failed action,然后返回 false

注意:hash_equals() 函数用来比较两个字符串是否相等,它可以防止时序攻击。 时序攻击是一种利用比较字符串的时间来推断字符串内容的攻击方式。

第五部分:如何在后台管理中使用 Nonce

在 WordPress 后台管理中,Nonce 被广泛使用,几乎所有的表单都会包含 Nonce 字段。

1. 在表单中添加 Nonce 字段:

使用 wp_nonce_field() 函数可以很方便地在表单中添加 Nonce 字段。 例如,在编辑文章的表单中,可以这样添加 Nonce 字段:

<form action="options.php" method="post">
    <?php
    settings_fields( 'my_plugin_options' );
    do_settings_sections( 'my_plugin' );
    wp_nonce_field( 'my_plugin_options_nonce' ); // 添加 Nonce 字段
    submit_button();
    ?>
</form>

2. 验证 Nonce:

在处理表单提交的时候,需要验证 Nonce 是否有效。 例如,在处理编辑文章的表单提交时,可以这样验证 Nonce:

if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'my_plugin_options_nonce' ) ) {
    wp_die( 'Invalid nonce!' );
}

这段代码首先检查 $_POST['_wpnonce'] 是否存在,然后使用 wp_verify_nonce() 函数验证 Nonce 是否有效。 如果 Nonce 无效,就调用 wp_die() 函数显示错误信息,并停止执行。

3. 最佳实践:

  • 为不同的操作使用不同的 $action 比如,修改文章的表单使用 'edit_post',删除用户的表单使用 'delete_user'
  • 在验证 Nonce 之前,先检查 $_POST 或者 $_GET 中是否存在 Nonce 字段。
  • 如果 Nonce 无效,不要执行任何操作。
  • 考虑 Nonce 的有效期。 默认情况下,Nonce 的有效期是 24 小时。 如果你的操作需要更长的有效期,可以通过 nonce_life 过滤器来修改。

第六部分:总结

wp_nonce_field() 函数是 WordPress 后台安全的重要组成部分。 它可以有效地防止 CSRF 攻击,保护你的网站免受恶意攻击。

咱们今天详细分析了 wp_nonce_field() 函数的源码,包括 wp_nonce_form_field()wp_create_nonce()wp_hash()wp_nonce_tick()wp_verify_nonce() 函数。

希望通过今天的讲解,大家能够对 WordPress 的 Nonce 机制有更深入的了解,并在实际开发中正确使用 Nonce,提高网站的安全性。

为了方便大家理解,我把今天讲的内容整理成一个表格:

函数 作用 参数 返回值
wp_nonce_field() 在 HTML 表单中插入一个隐藏的 Nonce 字段。 $action (string, optional): Nonce 的用途标识,默认为 -1。
$name (string, optional): HTML 字段的 name 属性,默认为 ‘_wpnonce’。
$referer (bool, optional): 是否包含 Referer 字段,默认为 true。
$echo (bool, optional): 是否直接输出 HTML 代码,默认为 true。
如果 $echo 为 true,则无返回值。否则,返回包含 Nonce 字段的 HTML 代码。
wp_nonce_form_field() 生成包含 Nonce 字段的 HTML 代码。 $action (string, optional): Nonce 的用途标识,默认为 -1。
$name (string, optional): HTML 字段的 name 属性,默认为 ‘_wpnonce’。
$referer (bool, optional): 是否包含 Referer 字段,默认为 true。
$echo (bool, optional): 是否直接输出 HTML 代码,默认为 true。
如果 $echo 为 true,则直接输出包含 Nonce 字段的 HTML 代码。否则,返回包含 Nonce 字段的 HTML 代码。
wp_create_nonce() 生成 Nonce 字符串。 $action (string, optional): Nonce 的用途标识,默认为 -1。 Nonce 字符串。
wp_verify_nonce() 验证 Nonce 是否有效。 $nonce (string): 要验证的 Nonce 字符串。
$action (string, optional): Nonce 的用途标识,默认为 -1。
如果 Nonce 有效,返回 1 或 2 (表示 Nonce 是在 12-24 小时之前生成的)。如果 Nonce 无效,返回 false。
wp_hash() 生成哈希值。 $data (string): 要哈希的数据。
$scheme (string, optional): 哈希方案,默认为 ‘auth’。
哈希值字符串。
wp_nonce_tick() 获取一个时间戳,这个时间戳会定期更新。 一个时间戳,这个时间戳会定期更新。

希望这个表格能帮助大家更好地理解 Nonce 的相关函数。

好了,今天的讲座就到这里。 感谢大家的参与! 记住,安全无小事,保护好你的 WordPress 网站!

发表回复

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