WordPress で CSRF 対策をする場合、一時的なトークン、いわゆる nonce を使用する方法が一般的です。
WordPress には nonce の生成と検証用の関数が組み込まれているのでこれを使えば簡単に対策を施すことができます。
以下の関数はプラガブル関数であり、プラグインなどから上書きすることができます。
wp_create_nonce()
https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/pluggable.php#L2044
function wp_create_nonce($action = -1) {
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
/** This filter is documented in wp-includes/pluggable.php */
$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 );
}
wp_create_nonce 関数で nonce を生成することができます。引数の $action はオプション引数ですが、指定することが推奨されています。
この関数では、時刻、アクション、ユーザーID、セッショントークンの4つの情報から nonce が生成されます。
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces in seconds.
*
* @since 2.5.0
*
* @param int $lifespan Lifespan of nonces in seconds. Default 86,400 seconds, or one day.
*/
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );
return ceil(time() / ( $nonce_life / 2 ));
}
そのうちの 時刻 は nonce の寿命(デフォルトでは1日)の半分の時間ごとに変化します。
nonce の寿命は nonce_life フィルタで変更することができます。
wp_verify_nonce()
https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/pluggable.php#L1981
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
/**
* Filters whether the user who generated the nonce is logged out.
*
* @since 3.5.0
*
* @param int $uid ID of the nonce-owning user.
* @param string $action The nonce action.
*/
$uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
}
if ( empty( $nonce ) ) {
return false;
}
$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 nonce verification fails.
*
* @since 4.4.0
*
* @param string $nonce The invalid nonce.
* @param string|int $action The nonce action.
* @param WP_User $user The current user object.
* @param string $token The user's session token.
*/
do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );
// Invalid nonce
return false;
}
wp_verify_nonce では与えられた nonce の検証を行います。
この関数では現在の時刻の nonce と、一つ前の時刻(つまり nonce の寿命の半分前)の nonce を作成し、その両方と比較します。いずれか片方に一致すれば、与えられた nonce は有効ということになります。
nonce の寿命は、生成されてからの経過時間ではないことに注意が必要です。生成されたタイミングによっては、設定した寿命の半分で無効になってしまう可能性もあります。
未ログインユーザーやパスワードが漏れてしまったユーザの場合、セッションフィクセーションなどの手法で破られる可能性があります。ケースに応じて、必要な場合は他の対策も組み合わせましょう。
未ログインユーザーの場合は referer を見るとかしかないのかな? 明確な答えがなくてすみません。深刻な被害が出そうにないのであれば受容するケースもあるかもしれません。