web-dev-qa-db-fra.com

Gestion des nonces pour les actions des invités aux utilisateurs connectés

Imaginez le scénario suivant:

  1. John possède un compte sur mon site Web, mais n'est actuellement pas connecté.
  2. Il clique sur le bouton "upvote" d'un article, mais il est redirigé vers la page de connexion car seuls les utilisateurs connectés peuvent voter.
  3. Dans l'URL de référence (vers laquelle John est redirigé après la connexion), les paramètres suivants sont passés: action=upvotearticle=1234nonce=gibberish.
  4. Après la connexion, John est redirigé vers l'URL de référence qui contient l'action "upvote".
  5. Le nonce n'est plus valide car il a été généré alors qu'il n'était pas connecté.

Le problème principal ici est que les nonces sont, à ma connaissance, générés avec la session utilisateur. Mais après la connexion, la session change, rendant ainsi le nonce invalide.

WordPress at-il un moyen de gérer des cas comme celui-ci?

2
Swen

J'ai opté pour la solution suggérée par @Rup dans les commentaires. Remplacement des deux fonctions nonce, wp_create_nonce() et wp_verify_nonce() pour préfixer un nonce par 1 ou 0 pour les utilisateurs connectés et déconnectés respectivement.

Les nonces déconnectés fonctionnent par IP plutôt que par ID utilisateur et jeton de session. Ainsi, permettant aux nonces de se poursuivre après une connexion. Pour autant que je sache, cela n'a pas d'impact significatif sur la sécurité.

if ( ! function_exists( 'wp_create_nonce' ) ) {
    /**
    * Creates a cryptographic token tied to a specific action, user, user session,
    * and window of time.
    *
    * @since 2.0.3
    * @since 4.0.0 Session tokens were integrated with nonce creation
    *
    * @param string|int $action Scalar value to add context to the nonce.
    * @return string The token.
    */
    function wp_create_nonce( $action = -1 ) {
        $user = wp_get_current_user();
        $uid  = (int) $user->ID;
        $logged_in = '1-';

        $token = wp_get_session_token();
        $i     = wp_nonce_tick();

        if ( ! $uid ) {
            // Prefix when logged-out nonce
            $logged_in = '0-';

            /** This filter is documented in wp-includes/pluggable.php */
            $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

            // Use IP instead of user_id
            $uid = $_SERVER['REMOTE_ADDR'];
            $token = $_SERVER['REMOTE_ADDR'];
        }

        return $logged_in . substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    }
}

if ( ! function_exists( 'wp_verify_nonce' ) ) {
    /**
    * Verify that correct nonce was used with time limit.
    *
    * The user is given an amount of time to use the token, so therefore, since the
    * UID and $action remain the same, the independent variable is the time.
    *
    * @since 2.0.3
    *
    * @param string     $nonce  Nonce that was used in the form to verify
    * @param string|int $action Should give context to what is taking place and be the same when nonce was created.
    * @return false|int False if the nonce is invalid, 1 if the nonce is valid and generated between
    *                   0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago.
    */
    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();

        // Check if nonce is for logged_in or logged_out ('1-' and '0-' respectively)
        if ( substr( $nonce, 0, 2 ) == '0-' ) {
            // Use IP instead of user_id and session token
            $uid = $_SERVER[ 'REMOTE_ADDR' ];
            $token = $_SERVER['REMOTE_ADDR'];
        }

        // Remove nonce prefix
        $nonce = substr( $nonce, 2 );

        // 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;
    }
}
2
Swen