各位观众老爷,大家好!今天咱们来聊聊 WordPress 安全机制里一个非常重要的东西:wp_nonce
。 这玩意儿听起来高大上,其实说白了就是 WordPress 为了防止 CSRF (Cross-Site Request Forgery,跨站请求伪造) 攻击而设计的一套验证机制。 咱们今天的目标是:扒开 wp_nonce
的源码,彻底搞清楚它到底是怎么工作的,尤其是 wp_create_nonce()
这个函数,它如何像变魔术一样,用 user_id
,action
和 timestamp
变出一个随机字符串来。准备好了吗? Let’s dive in!
什么是 Nonce? 为什么要用它?
首先,咱们先明确一下 nonce
是个什么东西。 Nonce,全称是 "number used once",顾名思义,就是只能用一次的数字。在安全领域,它通常被用来防止重放攻击 (Replay Attack)。
在 WordPress 中,wp_nonce
实际上是一个加密过的字符串,它包含了用户 ID、一个特定的动作 (action) 和一个时间戳。 它的主要作用就是验证请求的合法性,确保请求真的是用户在知情的情况下发起的,而不是被恶意网站伪造的。
如果没有 wp_nonce
,攻击者就可以伪造一个请求,比如修改你的文章、删除你的评论,甚至更改你的网站设置。 有了 wp_nonce
,WordPress 就能识别出这些伪造的请求,并拒绝执行。
wp_create_nonce()
: Nonce 的制造工厂
咱们今天的主角就是 wp_create_nonce()
函数。这个函数负责创建 Nonce,是整个 Nonce 机制的核心。 让我们先看看它的源码(为了便于理解,我对源码进行了一定的简化,并添加了注释):
function wp_create_nonce( $action = -1 ) {
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
$uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
}
$token = wp_get_session_token();
$i = wp_nonce_tick();
return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
}
哇,代码量不多啊。咱们一行一行地分析:
-
$action = -1
: 这个参数$action
是用来区分 Nonce 的用途的。 你可以把它想象成 Nonce 的 "名字"。 比如,如果你要创建一个用于删除文章的 Nonce,你可以把$action
设置为'delete_post'
。 如果不传参数,默认是 -1。 -
$user = wp_get_current_user();
和$uid = (int) $user->ID;
: 获取当前用户的 ID。 如果用户未登录,$uid
就是 0。 -
if ( ! $uid ) { $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); }
: 如果用户未登录,则应用nonce_user_logged_out
过滤器。允许插件修改未登录用户的UID,默认保持为0。 -
$token = wp_get_session_token();
: 获取用户的会话 Token。这个Token是用来增强Nonce的唯一性和安全性。 -
$i = wp_nonce_tick();
: 这个函数返回一个时间戳,但是它不是直接返回time()
,而是经过了一些处理,咱们稍后会详细讲解。 -
return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
: 这行代码是核心。 它把$i
(时间戳),$action
(动作),$uid
(用户 ID) 和$token
(会话Token) 用管道符|
连接起来,然后用wp_hash()
函数进行哈希,最后截取哈希值的一部分作为 Nonce。 注意到这里使用了 ‘nonce’ 作为$algo
参数,wp_hash
会根据这个参数选择合适的哈希算法(通常是hash_hmac( 'md5', $data, $key )
)。
wp_nonce_tick()
: 时间的魔法师
现在,咱们来详细看看 wp_nonce_tick()
函数。 它不是简单地返回 time()
,而是对时间进行了一些处理。 代码如下:
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces.
*
* @since 4.5.0
*
* @param int $lifespan Lifespan in seconds.
*/
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );
return ceil( time() / ( $nonce_life / 2 ) );
}
-
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );
: 这个函数获取 Nonce 的有效时间。默认情况下,DAY_IN_SECONDS
是 86400 秒 (也就是一天)。 你可以通过nonce_life
过滤器来修改这个值。 -
return ceil( time() / ( $nonce_life / 2 ) );
: 这行代码是关键。 它把当前时间戳除以 Nonce 有效时间的一半,然后向上取整。 这意味着 Nonce 的有效期实际上是 Noncelifespan
的一半,而每次wp_nonce_tick()
函数返回的值会在 Noncelifespan
的一半时间后才会变化一次。默认情况下Nonce的有效期是12小时。
为什么要这么做呢? 这是为了提高 Nonce 的安全性。 如果 Nonce 的有效期太短,用户在填写表单的时候,Nonce 可能就过期了。 如果 Nonce 的有效期太长,攻击者就有更多的时间来破解 Nonce。 所以,WordPress 选择了折中的方案,把 Nonce 的有效期设置为 Nonce lifespan
的一半。
wp_hash()
: 哈希的守护者
咱们再来看看 wp_hash()
函数。这个函数负责对数据进行哈希,生成一个唯一的哈希值。 源码如下(简化版):
function wp_hash( $data, $algo = 'auth' ) {
static $keys;
if ( empty( $keys ) ) {
$keys = wp_hash_salt();
}
$key = $keys[ $algo ];
if ( empty( $key ) ) {
$key = $keys['auth'];
}
$hmac = hash_hmac( 'md5', $data, $key );
return $hmac;
}
-
static $keys;
: 使用静态变量缓存哈希盐值。 -
if ( empty( $keys ) ) { $keys = wp_hash_salt(); }
: 如果$keys
为空,则调用wp_hash_salt()
获取哈希盐值。 -
$key = $keys[ $algo ];
: 根据传入的$algo
参数选择对应的哈希盐值。 -
if ( empty( $key ) ) { $key = $keys['auth']; }
: 如果$algo
对应的哈希盐值不存在,则使用auth
对应的盐值。 -
$hmac = hash_hmac( 'md5', $data, $key );
: 使用hash_hmac()
函数进行哈希。hash_hmac()
函数使用 HMAC (Hash-based Message Authentication Code) 算法,它需要一个密钥 (这里是$key
) 来生成哈希值。 这使得哈希值更加安全,即使攻击者知道哈希算法,也无法轻易地破解哈希值。 -
return $hmac;
: 返回生成的哈希值。
重点是 wp_hash_salt()
函数,它返回一个包含多个哈希盐值的数组。 这些盐值是在 WordPress 安装时随机生成的,存储在 wp-config.php
文件中。 它们是保护 Nonce 安全的关键。
wp_verify_nonce()
: Nonce 的验证官
有了 Nonce,当然也要有验证 Nonce 的方法。 wp_verify_nonce()
函数就是负责验证 Nonce 的。 它的源码如下:
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
$uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
}
$token = wp_get_session_token();
$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.5.0
*
* @param string|int $nonce The nonce that was attempted to be verified.
* @param string|int $action The action that was used when generating the nonce.
*/
do_action( 'wp_nonce_failed', $nonce, $action );
return false;
}
-
$nonce = (string) $nonce;
: 将 Nonce 转换为字符串。 -
$user = wp_get_current_user();
和$uid = (int) $user->ID;
: 获取当前用户的 ID。 -
if ( ! $uid ) { $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); }
: 如果用户未登录,则应用nonce_user_logged_out
过滤器。 -
$token = wp_get_session_token();
: 获取用户的会话 Token。 -
$i = wp_nonce_tick();
: 获取当前的时间戳。 -
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
: 使用当前时间戳生成一个 Nonce。 -
if ( hash_equals( $expected, $nonce ) ) { return 1; }
: 将生成的 Nonce 和传入的 Nonce 进行比较。 这里使用了hash_equals()
函数,它可以防止时序攻击 (Timing Attack)。 如果两个 Nonce 相等,说明 Nonce 是有效的,函数返回 1。 -
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
: 如果当前时间戳生成的 Nonce 无效,再尝试用前一个时间戳生成一个 Nonce。 这是因为 Nonce 的有效期是12小时,而wp_nonce_tick()
函数返回的时间戳每 12 小时才会变化一次。 -
if ( hash_equals( $expected, $nonce ) ) { return 2; }
: 将用前一个时间戳生成的 Nonce 和传入的 Nonce 进行比较。 如果两个 Nonce 相等,说明 Nonce 是有效的,函数返回 2。 -
do_action( 'wp_nonce_failed', $nonce, $action );
: 如果两个 Nonce 都不相等,说明 Nonce 是无效的,触发wp_nonce_failed
action。 -
return false;
: 返回false
,表示 Nonce 验证失败。
总结: Nonce 的工作流程
现在,咱们来总结一下 Nonce 的工作流程:
-
创建 Nonce: 使用
wp_create_nonce()
函数创建一个 Nonce。 这个函数会把用户 ID、动作、时间和会话Token戳用管道符连接起来,然后用wp_hash()
函数进行哈希,最后截取哈希值的一部分作为 Nonce。 -
在表单中加入 Nonce: 把 Nonce 作为一个隐藏字段添加到表单中。
-
提交表单: 用户提交表单,Nonce 也随之被提交到服务器。
-
验证 Nonce: 使用
wp_verify_nonce()
函数验证 Nonce。 这个函数会用当前时间和前一个时间戳生成两个 Nonce,然后和提交的 Nonce 进行比较。 如果有一个 Nonce 相等,说明 Nonce 是有效的。
代码示例
咱们来写一个简单的代码示例,演示如何使用 wp_nonce
:
<?php
// 生成 Nonce
$nonce = wp_create_nonce( 'my_action' );
// 显示表单
echo '<form method="post">';
echo '<input type="hidden" name="my_nonce" value="' . esc_attr( $nonce ) . '">';
echo '<input type="submit" value="Submit">';
echo '</form>';
// 处理表单提交
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
// 验证 Nonce
if ( wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
// Nonce 验证成功,处理表单数据
echo 'Nonce is valid!';
} else {
// Nonce 验证失败
echo 'Nonce is invalid!';
}
}
?>
在这个例子中,咱们首先使用 wp_create_nonce()
函数创建了一个 Nonce,并把它添加到了表单中。 然后,在处理表单提交的时候,咱们使用 wp_verify_nonce()
函数验证 Nonce。 如果 Nonce 验证成功,就处理表单数据,否则就显示一个错误信息。
表格总结
为了方便大家理解,咱们用一个表格来总结一下 wp_create_nonce()
和 wp_verify_nonce()
函数的参数和返回值:
函数 | 参数 | 返回值 |
---|---|---|
wp_create_nonce() |
$action (可选): 一个字符串,用于区分 Nonce 的用途。 如果不传参数,默认是 -1。 |
一个加密过的字符串,包含了用户 ID、动作和时间戳。 |
wp_verify_nonce() |
$nonce (必选): 要验证的 Nonce。$action (可选): 一个字符串,用于区分 Nonce 的用途。 如果不传参数,默认是 -1。 |
1 : Nonce 验证成功,且是用当前时间戳生成的。2 : Nonce 验证成功,且是用前一个时间戳生成的。false : Nonce 验证失败。 |
安全性思考
wp_nonce
机制虽然可以有效地防止 CSRF 攻击,但它并不是万无一失的。 攻击者仍然可以通过一些手段来绕过 Nonce 验证,比如:
-
窃取 Nonce: 如果攻击者能够窃取到用户的 Nonce,就可以伪造请求。 所以,要保护好 Nonce,不要把它泄露出去。
-
重放 Nonce: 如果 Nonce 的有效期太长,攻击者就可以重放 Nonce。 所以,要设置一个合理的 Nonce 有效期。
-
猜测 Nonce: 如果 Nonce 的生成算法不够安全,攻击者就可以猜测 Nonce。 所以,要使用安全的哈希算法来生成 Nonce。
因此,除了使用 wp_nonce
机制之外,还要采取其他的安全措施,比如:
-
使用 HTTPS: 使用 HTTPS 可以防止攻击者窃取 Nonce。
-
验证 Referer: 验证 Referer 可以防止攻击者伪造请求。
-
限制请求频率: 限制请求频率可以防止攻击者进行暴力破解。
总结
今天咱们详细地讲解了 wp_nonce
机制的源码,特别是 wp_create_nonce()
函数是如何生成 Nonce 的。 希望通过今天的讲解,大家能够对 wp_nonce
有一个更深入的了解,并在自己的 WordPress 项目中正确地使用它,保护网站的安全。记住,安全无小事!下次见!