各位观众老爷,大家好!我是你们的老朋友,今天咱们来聊聊 WordPress 里一个非常重要,但又经常被大家忽略的小东西:wp_verify_nonce()
函数。 别看它名字平平无奇,但它可是 WordPress 安全体系中不可或缺的一环,负责验证 Nonce 的有效性和合法性,防止各种 CSRF 攻击。 今天咱们就来扒一扒它的源码,看看它到底是怎么工作的,以及我们又该如何在实际开发中正确使用它。
啥是 Nonce? 先来个小科普
在深入 wp_verify_nonce()
之前,我们先来搞清楚 Nonce 到底是个啥玩意儿。 Nonce,全称是 "Number used once",顾名思义,就是一个只使用一次的数字。 在 WordPress 中,Nonce 主要用于防止跨站请求伪造 (CSRF) 攻击。
简单来说,CSRF 攻击就是攻击者伪装成你的身份,偷偷地执行一些你不希望执行的操作。 比如,攻击者可能在你不知情的情况下,发布一篇博客文章,或者修改你的账户信息。
Nonce 的作用就是给每个敏感的操作加上一个 "通行证",只有拥有这个通行证的人,才能执行这个操作。 这个通行证就是 Nonce,它是根据用户会话、操作名称和时间戳等信息生成的,并且每次使用都会不一样。
wp_verify_nonce()
源码大揭秘
好了,有了 Nonce 的概念,我们就可以开始研究 wp_verify_nonce()
的源码了。 这个函数位于 wp-includes/pluggable.php
文件中。
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$uid = (int) get_current_user_id();
$token = wp_get_session_token();
if ( is_numeric( $action ) ) {
$action = (int) $action;
} else {
$action = wp_nonce_tick() . $action;
}
$i = wp_nonce_tick();
// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, '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 action name.
*/
do_action( 'wp_nonce_failed', $nonce, $action );
return false;
}
现在,我们一行一行地来解读这段代码:
-
类型转换:
$nonce = (string) $nonce; $uid = (int) get_current_user_id(); $token = wp_get_session_token(); if ( is_numeric( $action ) ) { $action = (int) $action; } else { $action = wp_nonce_tick() . $action; }
$nonce = (string) $nonce;
: 将传入的 Nonce 转换为字符串类型,确保后续操作的类型一致性。$uid = (int) get_current_user_id();
: 获取当前用户的 ID,并转换为整数类型。 如果用户未登录,get_current_user_id()
将返回 0。$token = wp_get_session_token();
: 获取用户的会话 Token。 这个 Token 可以帮助进一步验证用户的身份。if ( is_numeric( $action ) ) { ... } else { ... }
: 检查$action
参数是否为数字。 如果是数字,则直接转换为整数类型;如果不是数字,则使用wp_nonce_tick()
函数生成一个时间戳,并将其与$action
拼接起来。 这样做的目的是为了确保每个操作都有一个唯一的名字,即使操作的名称相同,只要时间戳不同,生成的 Nonce 也会不一样。
-
获取时间片:
$i = wp_nonce_tick();
$i = wp_nonce_tick();
: 调用wp_nonce_tick()
函数获取当前的时间片。 这个时间片是一个整数,表示当前时间所属的 12 小时周期。
function wp_nonce_tick() { /** * Filters the lifespan of nonces. * * @since 4.5.0 * * @param int $lifespan The amount of time to live in seconds. Default 12 hours. */ $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 ); return ceil( time() / ( $nonce_life ) ); }
DAY_IN_SECONDS
是一个常量,表示一天有多少秒 (86400)。/ 2
表示 Nonce 的有效期是 12 小时。apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 )
允许开发者通过nonce_life
过滤器来修改 Nonce 的有效期。ceil( time() / ( $nonce_life ) )
计算当前时间所属的时间片。 比如,如果当前时间是 2023-10-27 10:00:00,那么time()
函数会返回一个 Unix 时间戳,然后除以 12 小时的秒数,最后使用ceil()
函数向上取整,得到当前的时间片。
-
生成预期 Nonce 并进行比较:
// Nonce generated 0-12 hours ago $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) { return 1; } // Nonce generated 12-24 hours ago $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) { return 2; }
wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' )
: 使用wp_hash()
函数生成一个哈希值。 这个哈希值是根据时间片、操作名称、用户 ID 和会话 Token 生成的。wp_hash
函数内部使用 WordPress 的盐 (salt) 来增加哈希值的安全性。substr( ..., -12, 10 )
: 截取哈希值的最后 12 位,然后取前 10 位作为预期的 Nonce。 这样做是为了缩短 Nonce 的长度,同时保留足够的随机性。hash_equals( $expected, $nonce )
: 使用hash_equals()
函数比较预期的 Nonce 和传入的 Nonce 是否相等。hash_equals()
函数是一个安全的字符串比较函数,可以防止时序攻击。- 代码会检查两个时间片:当前时间片 (
$i
) 和前一个时间片 ($i - 1
)。 这样做的目的是为了允许 Nonce 在 12 小时的有效期内被使用。 如果 Nonce 是在 12-24 小时前生成的,那么它仍然可以被验证通过。
-
Nonce 验证失败:
/** * 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 action name. */ do_action( 'wp_nonce_failed', $nonce, $action ); return false;
- 如果传入的 Nonce 和预期的 Nonce 不相等,那么
wp_verify_nonce()
函数会触发wp_nonce_failed
动作。 开发者可以通过监听这个动作来记录 Nonce 验证失败的信息,或者执行其他自定义的操作。 - 最后,函数返回
false
,表示 Nonce 验证失败。
- 如果传入的 Nonce 和预期的 Nonce 不相等,那么
wp_verify_nonce()
的返回值
1
: Nonce 验证成功,并且是在 0-12 小时内生成的。2
: Nonce 验证成功,并且是在 12-24 小时内生成的。false
: Nonce 验证失败。
实际应用场景
了解了 wp_verify_nonce()
的源码之后,我们来看看它在实际开发中是如何使用的。
假设我们有一个表单,允许用户删除一篇博客文章。 为了防止 CSRF 攻击,我们需要在表单中添加一个 Nonce。
-
生成 Nonce:
在显示表单的页面中,我们需要使用
wp_nonce_field()
函数生成一个 Nonce 字段。<?php wp_nonce_field( 'delete_post', 'delete_post_nonce' ); ?>
'delete_post'
是一个字符串,表示操作的名称。 建议为每个操作都使用一个唯一的名称。'delete_post_nonce'
是 Nonce 字段的名称。 你可以自定义这个名称,但要确保它在整个网站中是唯一的。
wp_nonce_field()
函数会生成一个隐藏的表单字段,其中包含 Nonce 的值。 它还会生成一个_wp_http_referer
字段,用来记录来源页面,也起到一定的安全作用。 生成的 HTML 代码类似这样:<input type="hidden" id="delete_post_nonce" name="delete_post_nonce" value="a1b2c3d4e5" /> <input type="hidden" name="_wp_http_referer" value="/wp-admin/post.php?post=123&action=edit" />
-
验证 Nonce:
在处理表单提交的页面中,我们需要使用
wp_verify_nonce()
函数验证 Nonce 的有效性。if ( isset( $_POST['delete_post_nonce'] ) && wp_verify_nonce( $_POST['delete_post_nonce'], 'delete_post' ) ) { // 删除文章 wp_delete_post( $_POST['post_id'] ); } else { // Nonce 验证失败 wp_die( 'Nonce verification failed' ); }
- 首先,我们检查
$_POST['delete_post_nonce']
是否存在。 这是为了防止用户直接访问处理表单提交的页面,而没有提交表单。 - 然后,我们调用
wp_verify_nonce()
函数验证 Nonce 的有效性。 - 如果 Nonce 验证成功,那么我们可以安全地删除文章。
- 如果 Nonce 验证失败,那么我们应该显示一个错误信息,或者执行其他适当的操作。
- 首先,我们检查
更高级的用法和注意事项
-
自定义 Nonce 生命周期:
可以通过
nonce_life
过滤器来修改 Nonce 的有效期。 例如,如果你希望 Nonce 的有效期是 1 小时,可以这样写:add_filter( 'nonce_life', function( $lifespan ) { return HOUR_IN_SECONDS; } );
-
使用不同的 Action:
为每个操作都使用一个唯一的 Action 名称,可以增加 Nonce 的安全性。 如果不同的操作使用相同的 Action 名称,那么攻击者可能会利用一个操作的 Nonce 来执行另一个操作。
-
用户未登录的情况:
如果用户未登录,
get_current_user_id()
函数会返回 0。 这意味着所有未登录的用户都会使用相同的 Nonce。 为了避免这种情况,可以考虑使用 IP 地址或其他用户标识符来生成 Nonce。 但是,需要注意保护用户的隐私,避免泄露用户的敏感信息。 -
HTTPS:
强烈建议在网站上启用 HTTPS。 HTTPS 可以加密用户和服务器之间的通信,防止中间人攻击。 如果网站没有启用 HTTPS,那么攻击者可能会截获 Nonce,并利用它来执行 CSRF 攻击。
-
结合其他安全措施:
Nonce 只是 WordPress 安全体系中的一部分。 为了提高网站的安全性,还需要结合其他安全措施,例如输入验证、输出转义、SQL 注入防护等。
总结
wp_verify_nonce()
函数是 WordPress 中一个非常重要的安全函数,用于验证 Nonce 的有效性和合法性,防止 CSRF 攻击。 了解它的源码和使用方法,可以帮助我们更好地保护 WordPress 网站的安全。
记住,安全无小事,一定要重视 WordPress 的安全,定期更新 WordPress 和插件,使用强密码,并采取其他必要的安全措施。
好了,今天的讲座就到这里。 希望大家能够从中受益,并在实际开发中正确使用 wp_verify_nonce()
函数。 感谢大家的观看! 下次再见!