好的,各位观众老爷,咱们今天来聊聊 WordPress 里的一个神秘小玩意儿:wp_nonce
。这玩意儿看着不起眼,但却肩负着保护咱们网站安全的重任。今天咱们就深入源码,扒一扒 wp_create_nonce()
到底是怎么变戏法,生成那些看似随机的字符串的。
开场白:Nonce 是个啥?
Nonce,英文全称 "number used once",顾名思义,就是“一次性使用的数字”。在 WordPress 中,Nonce 被用来防止 CSRF (Cross-Site Request Forgery,跨站请求伪造) 攻击。简单来说,CSRF 攻击就是坏人诱导你点击一个链接,然后偷偷地以你的身份发送请求,比如删除文章、修改密码等等。
wp_nonce
机制通过在表单或者 URL 中添加一个随机的、只能使用一次的字符串,来验证请求是否来自真正的用户操作,而不是恶意的第三方。
wp_create_nonce()
:Nonce 的制造者
wp_create_nonce()
函数就是负责生成这个随机字符串的核心人物。咱们先来看一下它的源码(简化版,重点突出):
function wp_create_nonce( $action = -1 ) {
$user = wp_get_current_user();
$uid = (int) $user->ID;
$token = wp_get_session_token();
$i = wp_nonce_tick();
return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
}
看着代码是不是有点懵?没关系,咱们一步一步来拆解它。
第一步:准备材料
wp_create_nonce()
函数首先要收集一些“材料”,这些材料会影响最终生成的 Nonce 值,增加破解难度。
-
$action
:动作(Action)这个参数是可选的,但强烈建议你使用它。
$action
是一个字符串,用来描述你要保护的操作是什么,比如 ‘edit_post’ (编辑文章)、’delete_post’ (删除文章) 等等。 不同的 action 会产生不同的 Nonce 值。如果不传,默认为 -1。 -
$user
:当前用户通过
wp_get_current_user()
获取当前用户对象,然后从中提取用户 ID ($uid
)。 这样,不同的用户生成的 Nonce 值也会不同。 -
$token
:会话令牌wp_get_session_token()
获取当前用户的会话令牌。 这个令牌存储在用户的浏览器 cookie 中,并且会随着用户登录状态的变化而变化。有了这个令牌,即使是同一个用户,在不同的会话中生成的 Nonce 值也会不同。 -
$i
:时间戳(Tick)这个比较特殊,是通过
wp_nonce_tick()
函数生成的。咱们稍后详细分析它。
第二步:wp_nonce_tick()
:时间的奥秘
wp_nonce_tick()
函数返回一个基于时间戳的整数。这个整数会定期更新,也就是说,Nonce 的有效期是有限的。
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces in seconds.
*
* @since 4.5.0
*
* @param int $lifespan The lifespan in seconds. Default is DAY_IN_SECONDS.
*/
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );
return ceil( time() / ( $nonce_life / 2 ) );
}
简单解释一下:
-
$nonce_life
:Nonce 的有效期默认情况下,Nonce 的有效期是
DAY_IN_SECONDS
,也就是一天(86400 秒)。但是,你可以通过nonce_life
过滤器来修改这个值。 -
time()
:当前时间戳获取当前的 Unix 时间戳(从 1970 年 1 月 1 日 00:00:00 UTC 到现在的秒数)。
-
ceil( time() / ( $nonce_life / 2 ) )
:计算 Tick 值这行代码是关键。它将当前时间戳除以
($nonce_life / 2)
,然后向上取整。这意味着,每隔($nonce_life / 2)
秒,wp_nonce_tick()
函数的返回值就会增加 1。由于默认的nonce_life
是一天,所以wp_nonce_tick()
函数的返回值每 12 小时会更新一次。举个例子:
时间戳 (time()) nonce_life (秒) nonce_life / 2 time() / (nonce_life / 2) ceil() 结果 (Tick) 1678886400 (Mar 15 2023 00:00:00 UTC) 86400 43200 38863.111 38864 1678929600 (Mar 15 2023 12:00:00 UTC) 86400 43200 38864.111 38865 1678972800 (Mar 16 2023 00:00:00 UTC) 86400 43200 38865.111 38866 可以看到,每隔 12 个小时,Tick 值就会增加 1。
第三步:wp_hash()
:哈希算法的魔力
wp_hash()
函数是生成 Nonce 字符串的核心。它使用 WordPress 的哈希算法,将前面收集到的所有材料组合在一起,生成一个唯一的哈希值。
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 );
}
简单解释一下:
-
$data
:要哈希的数据这就是前面收集到的所有材料,它们被拼接成一个字符串:
$i . '|' . $action . '|' . $uid . '|' . $token
。 例如:38864|edit_post|1|e5b9a86b7033709672e5f880b6a39b31
-
$scheme
:哈希方案这里指定的是 ‘nonce’,但实际上
wp_hash()
函数并不直接使用这个参数。 它的作用主要是为了兼容旧版本的 WordPress。 -
PasswordHash
类WordPress 使用
PasswordHash
类来进行哈希运算。 这个类来自 PHPass 库,它使用一种叫做 Portable Hash 的哈希算法。这种算法的特点是安全性高,并且可以在不同的 PHP 环境下使用。 -
$wp_hasher->HashPassword( $data )
:生成哈希值这一步就是真正生成哈希值的过程。
HashPassword()
方法会将$data
经过一系列复杂的运算,最终生成一个长度为 64 位的哈希字符串。
第四步:substr()
:截取 Nonce 字符串
最后一步,substr()
函数从哈希字符串中截取一部分作为最终的 Nonce 值。
substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 )
这里截取的是哈希字符串的倒数第 12 位开始,长度为 10 的子字符串。 这样做的目的是为了缩短 Nonce 的长度,方便在 URL 中传递。
总结:wp_create_nonce()
的工作流程
咱们用一张表格来总结一下 wp_create_nonce()
的工作流程:
步骤 | 函数/操作 | 说明 | 示例 |
---|---|---|---|
1 | 收集材料 | 获取动作、用户 ID、会话令牌和 Tick 值 | $action = 'edit_post' , $uid = 1 , $token = 'e5b9a86b7033709672e5f880b6a39b31' , $i = 38864 |
2 | 拼接字符串 | 将所有材料拼接成一个字符串 | $data = '38864|edit_post|1|e5b9a86b7033709672e5f880b6a39b31' |
3 | 哈希运算 | 使用 wp_hash() 函数对字符串进行哈希运算 |
wp_hash($data) = 'f4c2f1d6a3c96f427a4383e5c9b1d3f26d89e9b84d7c4f1a6e6f7d9c1b4a2c3' |
4 | 截取字符串 | 从哈希字符串中截取一部分作为 Nonce 值 | substr('f4c2f1d6a3c96f427a4383e5c9b1d3f26d89e9b84d7c4f1a6e6f7d9c1b4a2c3', -12, 10) = 'a6e6f7d9c1' |
Nonce 的验证:wp_verify_nonce()
光生成 Nonce 还不够,还需要验证它是否有效。 WordPress 提供了 wp_verify_nonce()
函数来完成这个任务。
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$max_age = 2 * DAY_IN_SECONDS;
$user = wp_get_current_user();
$uid = (int) $user->ID;
$token = wp_get_session_token();
if ( empty( $nonce ) ) {
return false;
}
$i = wp_nonce_tick();
// Nonce generated 0-12 hours ago
if ( wp_hash_equals( substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ), $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
if ( wp_hash_equals( substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ), $nonce ) ) {
return 2;
}
// Nonce generated 24-36 hours ago
if ( wp_hash_equals( substr( wp_hash( ( $i - 2 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ), $nonce ) ) {
return 3;
}
return false;
}
这个函数的工作原理是:
- 获取参数: 传入 Nonce 值和 Action 值。
- 计算当前 Tick 值: 使用
wp_nonce_tick()
计算当前的 Tick 值。 - 尝试验证: 使用当前 Tick 值和前两个 Tick 值(
$i
,$i - 1
,$i - 2
)重新生成 Nonce 值,并与传入的 Nonce 值进行比较。 - 返回结果: 如果验证成功,则返回 1, 2 或 3,表示 Nonce 的有效时间范围。 如果验证失败,则返回
false
。
为什么 wp_verify_nonce()
要验证三个 Tick 值?
这是因为 Nonce 的有效期是 24 小时(两个 Tick 周期)。 为了防止时间差导致验证失败,wp_verify_nonce()
函数会尝试使用当前 Tick 值以及前两个 Tick 值来验证 Nonce。 这样,即使 Nonce 刚好在 Tick 值更新的时候被提交,也能保证验证成功。 实际上,如果nonce_life
被更改,验证会继续验证之前所有的tick直到超过最大存活时间max_age
。
Nonce 的使用示例
// 生成 Nonce
$nonce = wp_create_nonce( 'my_action' );
// 在 URL 中添加 Nonce
$url = add_query_arg( array(
'_wpnonce' => $nonce,
'action' => 'my_action',
'data' => 'some_data',
), admin_url( 'admin-ajax.php' ) );
// 或者在表单中添加 Nonce
?>
<form action="<?php echo admin_url( 'admin-ajax.php' ); ?>" method="post">
<input type="hidden" name="action" value="my_action">
<input type="hidden" name="data" value="some_data">
<?php wp_nonce_field( 'my_action' ); ?>
<input type="submit" value="Submit">
</form>
<?php
// 验证 Nonce
if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'my_action' ) {
check_admin_referer( 'my_action' ); // 这是个更高级的验证函数,内部会调用 wp_verify_nonce
// 或者
if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'my_action' ) ) {
wp_die( 'Nonce verification failed!' );
}
// 处理数据
echo 'Data received: ' . $_REQUEST['data'];
}
安全建议
- 使用 Action: 始终为你的 Nonce 指定一个具体的 Action 值。 这样可以防止 Nonce 被用于其他不相关的操作。
- 不要泄露 Nonce: Nonce 应该只在表单或者 URL 中传递,不要在其他地方泄露它。
- 定期更新 Nonce: Nonce 的有效期是有限的,所以要定期更新它。
- 使用
check_admin_referer()
或wp_verify_nonce()
验证 Nonce: 在处理请求之前,一定要验证 Nonce 的有效性。check_admin_referer()
函数在后台管理界面使用,会自动检查_wpnonce
和_wp_http_referer
字段,并验证 Nonce。wp_verify_nonce()
函数则更加灵活,可以用于任何场景。
总结
wp_nonce
机制是 WordPress 中一种重要的安全措施,它可以有效地防止 CSRF 攻击。 通过深入了解 wp_create_nonce()
函数的源码,我们可以更好地理解 Nonce 的生成原理,从而更好地使用它来保护我们的网站安全。
好了,今天的讲座就到这里。 希望大家能够对 wp_nonce
机制有一个更深入的了解。 谢谢大家!