解释 `wp_nonce` 机制的源码,它是如何防止 CSRF 攻击的?

各位技术大侠,欢迎来到今天的“WordPress 安全之巅”研讨会!我是你们今天的导游,带大家一起扒一扒 WordPress 安全机制中的扛把子——wp_nonce。放心,这次我们不搞玄学,直接撸源码,保证让你看得懂,学得会,以后在代码里耍起来也倍儿有面儿。

开场白:CSRF 的那些事儿

在深入 wp_nonce 之前,咱们先聊聊 CSRF(Cross-Site Request Forgery),也就是跨站请求伪造。这玩意儿就像一个潜伏在你网站的间谍,专门替坏人干坏事儿。

想象一下:你登录了银行网站,正在浏览账户余额。同时,你在另一个标签页打开了一个恶意网站。这个恶意网站偷偷地在你不知情的情况下,向银行网站发起了一个转账请求,把你的钱转走了!是不是想想就冒冷汗?

CSRF 的原理很简单:攻击者利用你已经登录的身份,伪造请求发送到服务器,服务器一看,Cookie 没问题,就以为是用户自己发起的请求,然后就执行了。

wp_nonce:骑士登场,专治 CSRF!

WordPress 为了防止 CSRF 攻击,引入了 wp_nonce 机制。nonce 这个词,英文是 "number used once" 的缩写,意思是“一次性使用的数字”。也就是说,wp_nonce 是一个随机生成的、只能使用一次的令牌(token)。

它的工作原理是这样的:

  1. 生成 Nonce: 当你发起一个需要保护的请求时(比如删除文章、修改用户资料),WordPress 会生成一个 wp_nonce,并把它嵌入到表单或者 URL 中。
  2. 验证 Nonce: 当服务器收到请求时,会验证这个 wp_nonce 是否有效。如果有效,就执行请求;如果无效,就拒绝请求。

这样一来,攻击者就算能伪造请求,也拿不到有效的 wp_nonce,也就无法成功发起攻击了。

源码剖析:wp_nonce 的前世今生

接下来,咱们深入 WordPress 的源码,看看 wp_nonce 是如何生成的,又是如何验证的。

1. wp_create_nonce():Nonce 的诞生

这个函数负责生成 wp_nonce。它的源码位于 wp-includes/pluggable.php 文件中。

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

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

让我们来解读一下:

  • $action: 一个字符串,用于标识这个 nonce 的用途。比如,如果是删除文章的 nonce$action 可以是 'delete_post'。 不同的action会生成不同的nonce
  • wp_nonce_tick(): 这个函数返回一个基于时间的整数,用于增加 nonce 的唯一性。 它会根据时间间隔(默认是12小时)进行更新。
  • get_current_user_id(): 获取当前用户的 ID。
  • wp_get_session_token(): 获取当前用户的会话令牌。
  • wp_hash(): 使用 WordPress 的哈希算法(默认是 wp_hash 函数,它实际上使用了 hash_hmac 函数)对字符串进行哈希。
  • substr(): 截取哈希结果的后 12 位,然后取前10位作为最终的 nonce

简单来说,wp_create_nonce() 函数将时间戳、action、用户 ID 和会话令牌组合在一起,然后进行哈希,最后截取一部分作为 nonce

2. 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 12 hours.
     */
    $lifespan = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );

    return ceil( time() / ( $lifespan ) );
}
  • DAY_IN_SECONDS: 一个常量,表示一天有多少秒(86400)。
  • apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 ): 允许开发者通过 nonce_life 过滤器修改 nonce 的有效期,默认是 12 小时。
  • ceil( time() / ( $lifespan ) ): 将当前时间戳除以 nonce 的有效期,然后向上取整。

这个函数返回一个整数,每隔 nonce 的有效期(默认是 12 小时)就会增加 1。

3. wp_verify_nonce():Nonce 验证,火眼金睛

这个函数负责验证 nonce 是否有效。它的源码也位于 wp-includes/pluggable.php 文件中。

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

    if ( empty( $nonce ) ) {
        return false;
    }

    // Nonce generated 0-12 hours ago
    $expected = substr( wp_hash( $i . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token(), '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() . '|' . wp_get_session_token(), 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
        return 2;
    }

    // Invalid nonce
    return false;
}

让我们来解读一下:

  • $nonce: 要验证的 nonce 值。
  • $action: 用于标识这个 nonce 的用途,必须和生成 nonce 时的 $action 相同。
  • wp_nonce_tick(): 获取当前的时间戳。
  • hash_equals(): 这是一个防止时序攻击的函数,用于比较两个字符串是否相等。

这个函数会尝试使用当前的时间戳和前一个时间戳来生成 nonce,然后与传入的 $nonce 进行比较。如果匹配,就说明 nonce 有效。

代码示例:实战演练

光说不练假把式,咱们来写一个简单的例子,演示如何使用 wp_nonce

1. 生成 Nonce(在 PHP 文件中)

<?php
// 假设这是一个删除文章的操作
$action = 'delete_post';

// 生成 nonce
$nonce = wp_create_nonce( $action );

// 输出包含 nonce 的 URL
$delete_url = admin_url( 'admin-ajax.php' ) . '?action=my_delete_post&post_id=123&_wpnonce=' . $nonce;

echo '<a href="' . esc_url( $delete_url ) . '">删除文章</a>';
?>

这段代码会生成一个包含 nonce 的 URL,点击这个链接,就会触发删除文章的操作。

2. 验证 Nonce(在 PHP 文件中,处理 AJAX 请求)

<?php
// 验证 nonce
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'delete_post' ) ) {
    wp_die( '权限不足,无法删除文章!' );
}

// 如果 nonce 验证通过,就执行删除文章的操作
$post_id = intval( $_GET['post_id'] );
wp_delete_post( $post_id, true );

wp_send_json_success( '文章删除成功!' );
?>

这段代码会验证 URL 中的 nonce 是否有效。如果无效,就拒绝执行删除操作;如果有效,就执行删除操作。

wp_nonce 的优缺点

优点 缺点
有效防止 CSRF 攻击 需要服务器端和客户端都进行相应的修改才能使用
使用简单,WordPress 提供了方便的函数来生成和验证 nonce nonce 的有效期有限,默认是 12 小时。如果用户长时间停留在页面上,nonce 可能会过期
可以通过 action 参数来区分不同的操作 如果 action 参数设置不当,可能会导致 nonce 被滥用
与用户 ID 绑定,可以防止用户冒充攻击 如果服务器时间不准确,可能会导致 nonce 验证失败。

最佳实践:如何正确使用 wp_nonce

  1. 为每个需要保护的操作都生成一个独立的 nonce 不要把同一个 nonce 用于多个不同的操作。
  2. 使用有意义的 action 参数。 action 参数应该能够清晰地标识这个 nonce 的用途。
  3. 在服务器端和客户端都进行 nonce 验证。 不要只在客户端验证 nonce,因为客户端的验证很容易被绕过。
  4. 注意 nonce 的有效期。 如果你的网站有用户长时间停留在页面的场景,可以考虑缩短 nonce 的有效期,或者在用户提交表单时重新生成 nonce
  5. 使用 hash_equals() 函数进行字符串比较。 hash_equals() 函数可以防止时序攻击。
  6. 确保服务器时间准确。 如果服务器时间不准确,可能会导致 nonce 验证失败。
  7. 避免在 GET 请求中使用敏感操作。 如果必须使用,确保使用 wp_nonce_url() 生成包含 nonce 的 URL。

wp_nonce_url()wp_nonce_field()

WordPress 提供了两个方便的函数来处理 nonce

  • wp_nonce_url(): 用于生成包含 nonce 的 URL。
  • wp_nonce_field(): 用于生成包含 nonce 的隐藏表单字段。

使用这两个函数可以简化 nonce 的生成和验证过程。

1. wp_nonce_url() 的用法

<?php
$action = 'delete_post';
$post_id = 123;
$url = admin_url( 'admin-ajax.php' ) . '?action=my_delete_post&post_id=' . $post_id;

// 生成包含 nonce 的 URL
$nonce_url = wp_nonce_url( $url, $action );

echo '<a href="' . esc_url( $nonce_url ) . '">删除文章</a>';
?>

wp_nonce_url() 函数会自动生成 nonce,并把它添加到 URL 中。

2. wp_nonce_field() 的用法

<form action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post">
    <input type="hidden" name="action" value="my_update_profile">
    <input type="text" name="name" value="<?php echo esc_attr( $user->name ); ?>">
    <?php wp_nonce_field( 'update_profile' ); ?>
    <input type="submit" value="保存">
</form>

wp_nonce_field() 函数会自动生成一个包含 nonce 的隐藏表单字段。

总结:wp_nonce,安全卫士,值得信赖

wp_nonce 机制是 WordPress 安全体系中非常重要的一环。它可以有效地防止 CSRF 攻击,保护你的网站免受恶意攻击。只要你理解了 wp_nonce 的原理,掌握了它的使用方法,就能为你的 WordPress 网站增加一道坚固的防线。

记住,安全无小事,时刻保持警惕,才能让你的网站在互联网的汪洋大海中安全航行!

最后,感谢大家的参与,希望今天的研讨会对你有所帮助!

发表回复

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