解释 `wp_verify_nonce()` 函数的源码,它如何通过重新生成 `Nonce` 并进行对比来验证其有效性。

Nonce 的秘密:wp_verify_nonce() 函数深度解析

大家好!我是今天的主讲人,很高兴能和大家一起探索 WordPress 世界里一个既神秘又重要的概念:Nonce。

你可能听过 Nonce,也可能只是在调试 WordPress 时偶然瞥见过它。但你真的理解它背后的原理吗?今天,我们就来扒一扒 wp_verify_nonce() 函数的源码,看看它是如何像一位严谨的保安,通过重新生成 Nonce 并进行对比,来验证其有效性的。

Nonce 是什么?为什么要用它?

首先,我们来聊聊什么是 Nonce。Nonce,全称 "Number used once",顾名思义,就是一个只能使用一次的随机数。在 WordPress 中,它被用来防止 CSRF (Cross-Site Request Forgery,跨站请求伪造) 攻击。

想象一下,如果没有 Nonce,黑客可以伪造一个请求,比如让你不小心点击一个链接,然后你的博客就自动删除了一篇文章。这听起来是不是很可怕?

Nonce 的作用就像给每个请求加上一个唯一的 "签名",只有拥有正确 "签名" 的请求才能被执行。这样,即使黑客伪造了请求,由于他不知道正确的 Nonce 值,请求也会被 WordPress 拒绝。

wp_verify_nonce() 函数:Nonce 的守门员

wp_verify_nonce() 函数就是负责验证 Nonce 是否有效的 "守门员"。它的主要任务是:

  1. 接收一个 Nonce 值,以及一个可选的动作 (action)。
  2. 根据 action 和当前用户的信息,重新生成一个 Nonce。
  3. 将接收到的 Nonce 值与重新生成的 Nonce 值进行对比。
  4. 如果两个 Nonce 值匹配,则认为 Nonce 有效;否则,认为 Nonce 无效。

源码剖析:wp_verify_nonce() 的内部运作

现在,让我们深入 wp_verify_nonce() 函数的源码,看看它是如何工作的。

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

    $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;
    }

    // Invalid nonce
    return false;
}

让我们逐行解读这段代码:

  1. 类型转换:

    $nonce = (string) $nonce;
    $action = (string) $action;

    首先,函数将接收到的 Nonce 和 action 转换为字符串类型,以确保后续操作的类型一致性。

  2. 获取 Nonce "时间片":

    $i = wp_nonce_tick();

    wp_nonce_tick() 函数返回一个基于时间的整数,我们称之为 "时间片"。它的作用是将 Nonce 的有效期限制在一个较短的时间范围内,通常是 12 小时。

    function wp_nonce_tick() {
        /**
         * Filters the duration of time that nonces are valid for.
         *
         * @since 2.5.0
         *
         * @param int $lifespan Duration in seconds.
         */
        $lifespan = apply_filters( 'nonce_life', DAY_IN_SECONDS );
    
        return ceil( time() / ( $lifespan / 2 ) );
    }

    wp_nonce_tick() 的核心在于计算 time() / ( $lifespan / 2 ),其中 time() 是当前时间戳,$lifespan 默认是 DAY_IN_SECONDS (一天)。 也就是说,默认情况下,Nonce 的有效期是 12 小时。ceil() 函数则保证了结果是一个整数。这个整数会随着时间推移而变化,每隔 12 小时增加 1。

  3. 生成期望的 Nonce:

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

    这行代码是生成 Nonce 的关键。它使用了 wp_hash() 函数,对以下三个元素进行哈希运算:

    • $i:当前 "时间片"。
    • $action:用户自定义的 action。
    • get_current_user_id():当前用户的 ID。

    wp_hash() 函数使用 WordPress 的密钥 (salt) 对这三个元素进行哈希运算,生成一个唯一的哈希值。然后,使用 substr() 函数截取哈希值的最后 10 个字符,作为期望的 Nonce 值。

    为什么只截取最后 10 个字符?这是为了缩短 Nonce 的长度,提高性能。同时,由于哈希算法的特性,即使只截取一部分字符,也能保证 Nonce 的唯一性。

  4. 对比 Nonce:

    if ( hash_equals( $expected, $nonce ) ) {
        return 1;
    }

    hash_equals() 函数用于安全地比较两个字符串。它能够防止时序攻击 (timing attack),保证比较的安全性。如果接收到的 Nonce 值与期望的 Nonce 值匹配,则函数返回 1,表示 Nonce 有效。

  5. 检查过期 Nonce:

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

    为了处理一些网络延迟或用户时钟不准确的情况,函数还会检查 Nonce 是否是前一个 "时间片" 生成的。如果 Nonce 是前一个 "时间片" 生成的,则函数返回 2,表示 Nonce 有效,但已经过期。

  6. Nonce 无效:

    return false;

    如果接收到的 Nonce 值与当前 "时间片" 和前一个 "时间片" 生成的 Nonce 值都不匹配,则函数返回 false,表示 Nonce 无效。

Nonce 的生命周期

Nonce 的生命周期可以简单概括为以下几个阶段:

  1. 生成: 使用 wp_create_nonce() 函数生成 Nonce。
  2. 嵌入: 将 Nonce 嵌入到 HTML 表单或 URL 中。
  3. 提交: 用户提交表单或点击 URL。
  4. 验证: 使用 wp_verify_nonce() 函数验证 Nonce 的有效性。
  5. 使用: 如果 Nonce 有效,则执行相应的操作。

示例代码:生成和验证 Nonce

下面是一个简单的示例,演示如何生成和验证 Nonce:

<?php

// 生成 Nonce
$nonce = wp_create_nonce( 'my_action' );

// 生成包含 Nonce 的 URL
$url = admin_url( 'admin-ajax.php?action=my_ajax_function&_wpnonce=' . $nonce );

echo '<a href="' . esc_url( $url ) . '">Click me!</a>';

// 在 Ajax 函数中验证 Nonce
add_action( 'wp_ajax_my_ajax_function', 'my_ajax_function' );

function my_ajax_function() {
    // 验证 Nonce
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'my_action' ) ) {
        wp_die( 'Invalid Nonce!' );
    }

    // 执行操作
    echo 'Nonce is valid!';

    wp_die();
}

?>

在这个例子中,我们首先使用 wp_create_nonce() 函数生成一个 Nonce,并将其嵌入到一个 URL 中。然后,在 Ajax 函数 my_ajax_function() 中,我们使用 wp_verify_nonce() 函数验证 Nonce 的有效性。如果 Nonce 有效,则执行相应的操作。

wp_create_nonce() 函数:Nonce 的制造者

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

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

这个函数是不是看起来很眼熟?没错,它和 wp_verify_nonce() 函数中生成 Nonce 的部分几乎一模一样! 这也很好理解,生成和验证 Nonce 的算法必须保持一致,才能保证 Nonce 的有效性。

总结:Nonce 的重要性

Nonce 是 WordPress 中一个重要的安全机制,它可以有效地防止 CSRF 攻击。通过理解 wp_verify_nonce() 函数的源码,我们可以更好地理解 Nonce 的工作原理,从而更好地保护我们的 WordPress 网站。

常见问题解答

  1. Nonce 的有效期是多久?

    默认情况下,Nonce 的有效期是 12 小时。可以通过 nonce_life 过滤器来修改 Nonce 的有效期。

  2. Nonce 可以重复使用吗?

    Nonce 的设计理念是 "Number used once",因此,Nonce 不应该重复使用。每次生成一个表单或 URL 时,都应该生成一个新的 Nonce。

  3. 如何选择合适的 action?

    Action 应该能够唯一地标识一个操作。例如,可以使用 "delete_post_" . $post_id 来标识删除文章的操作。

  4. 为什么需要使用 hash_equals() 函数来比较 Nonce?

    hash_equals() 函数可以防止时序攻击,保证比较的安全性。

  5. 如果 Nonce 验证失败,应该怎么办?

    如果 Nonce 验证失败,应该拒绝执行相应的操作,并向用户显示一个错误信息。

表格总结

函数 作用
wp_create_nonce() 生成一个 Nonce。
wp_verify_nonce() 验证 Nonce 的有效性。
wp_nonce_tick() 返回一个基于时间的整数,用于限制 Nonce 的有效期。
wp_hash() 使用 WordPress 的密钥 (salt) 对数据进行哈希运算。
hash_equals() 安全地比较两个字符串,防止时序攻击。

希望今天的分享能够帮助你更好地理解 WordPress 中的 Nonce 机制。记住,安全无小事,保护好你的网站,从理解 Nonce 开始!感谢大家的聆听,下课!

发表回复

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