[wp-trac] [WordPress Trac] #48356: wp_create_nonce(...) and check_ajax_referer(...) fails on the 2nd AJAX call if that is two-action AJAX with AJAX-LOGIN as the first action

WordPress Trac noreply at wordpress.org
Thu Oct 17 15:34:26 UTC 2019


#48356: wp_create_nonce(...) and check_ajax_referer(...) fails on the 2nd AJAX call
if that is two-action AJAX with AJAX-LOGIN as the first action
--------------------------+-------------------------
 Reporter:  KestutisIT    |       Owner:  (none)
     Type:  defect (bug)  |      Status:  reopened
 Priority:  normal        |   Milestone:
Component:  General       |     Version:  5.2.3
 Severity:  normal        |  Resolution:
 Keywords:  has-patch     |     Focuses:  javascript
--------------------------+-------------------------
Changes (by KestutisIT):

 * keywords:   => has-patch
 * status:  closed => reopened
 * focuses:   => javascript
 * resolution:  invalid =>
 * version:   => 5.2.3


Comment:

 As I asked for 2nd opinion, I please to wait someone else on this topic.
 In meanwhile I'm posting A PATCH to WordPress core regarding this.
 The problem introducted in WP 4.0.0 when it started to based it on User
 Session.
 So I made an alternative functions with 'persistent' prefix and based the
 token on User IP instead. All the rest remained the same. I'm proposing
 this to add to WordPress core, or to merge it as an 2nd parameter of
 WordPress core.
 In meanwhile I added 'pluggable.php' as the additional file to root folder
 of plugin to fix WordPress core issues.

 The fact that I'm using SolidMVC micro-framework, does not mean that I
 have to reinvent the security. As if you see where WordPress went with new
 'WP_Notify' team they now KNOW THAT, it is bad to do - so I believe the
 security has to be trusted by WordPress, not by individual methods on
 every plugin.

 I also re-add the 'javascript' keyword because you again did not described
 what is the reason why you removing it. It is directly related to
 Javascript. And I even wrote a patch for WordPress don't below. You just
 need to integrate it. That's it.

 The copy of Pluggable.php is below:
 {{{#!php
 <?php

 if ( ! function_exists( 'wp_get_persistent_token' ) ) :
     /**
      * Function to get the client IP address
      * Purpose - an alternative to wp_get_session_token()
      *
      * @see wp_get_session_token
      * @return string
      */
     function wp_get_persistent_token() {
         if (isset($_SERVER['HTTP_CLIENT_IP'])) {
             $ip_address = $_SERVER['HTTP_CLIENT_IP'];
         } else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
             $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
         } else if(isset($_SERVER['HTTP_X_FORWARDED'])) {
             $ip_address = $_SERVER['HTTP_X_FORWARDED'];
         } else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) {
             $ip_address = $_SERVER['HTTP_FORWARDED_FOR'];
         } else if(isset($_SERVER['HTTP_FORWARDED'])) {
             $ip_address = $_SERVER['HTTP_FORWARDED'];
         } else if(isset($_SERVER['REMOTE_ADDR'])) {
             $ip_address = $_SERVER['REMOTE_ADDR'];
         } else {
             $ip_address = 'UNKNOWN';
         }
         $token = crc32($ip_address);

         return $token;
     }
 endif;

 if ( ! function_exists( 'check_persistent_ajax_referer' ) ) :
     /**
      * Verifies the Ajax request to prevent processing requests external
 of the blog.
      *
      * @since 2.0.3
      *
      * @param int|string   $action    Action nonce.
      * @param false|string $query_arg Optional. Key to check for the nonce
 in `$_REQUEST` (since 2.5). If false,
      *                                `$_REQUEST` values will be evaluated
 for '_ajax_nonce', and '_wpnonce'
      *                                (in that order). Default false.
      * @param bool         $die       Optional. Whether to die early when
 the nonce cannot be verified.
      *                                Default true.
      * @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 check_persistent_ajax_referer( $action = -1, $query_arg =
 false, $die = true ) {
         if ( -1 == $action ) {
             _doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce
 action to be verified by using the first parameter.' ), '4.7' );
         }

         $nonce = '';

         if ( $query_arg && isset( $_REQUEST[ $query_arg ] ) ) {
             $nonce = $_REQUEST[ $query_arg ];
         } elseif ( isset( $_REQUEST['_ajax_nonce'] ) ) {
             $nonce = $_REQUEST['_ajax_nonce'];
         } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
             $nonce = $_REQUEST['_wpnonce'];
         }

         $result = wp_verify_persistent_nonce( $nonce, $action );

         /**
          * Fires once the Ajax request has been validated or not.
          *
          * @since 2.1.0
          *
          * @param string    $action The Ajax nonce action.
          * @param false|int $result 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.
          */
         do_action( 'check_persistent_ajax_referer', $action, $result );

         if ( $die && false === $result ) {
             if ( wp_doing_ajax() ) {
                 wp_die( -1, 403 );
             } else {
                 die( '-1' );
             }
         }

         return $result;
     }
 endif;

 if ( ! function_exists( 'wp_verify_persistent_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
      * USER_IP and $action remain the same, the independent variable is
 the time.
      *
      * @note For persistent nonce, user is not involved
      *
      * @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_persistent_nonce( $nonce, $action = -1 ) {
         $nonce = (string) $nonce;
         //

         if ( empty( $nonce ) ) {
             return false;
         }

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

         // Nonce generated 0-12 hours ago
         $expected = substr( wp_hash( $i . '|' . $action . '|' . $token,
 'nonce' ), -12, 10 );
         if ( hash_equals( $expected, $nonce ) ) {
             return 1;
         }

         // Nonce generated 12-24 hours ago
         $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' .
 $token, 'nonce' ), -12, 10 );
         if ( hash_equals( $expected, $nonce ) ) {
             return 2;
         }

         /**
          * Fires when persistent nonce verification fails.
          * @note $uid is not included here
          *
          * @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_persistent_nonce_failed', $nonce, $action,
 $token );

         // Invalid nonce
         return false;
     }
 endif;

 if ( ! function_exists( 'wp_create_persistent_nonce' ) ) :
     /**
      * Creates a cryptographic token tied to a specific action, user, user
 session,
      * and window of time.
      *
      * @note For persistent nonce, user is not involved
      *
      * @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_persistent_nonce( $action = -1 ) {
         $token = wp_get_persistent_token();
         $i     = wp_nonce_tick();

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

 }}}

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/48356#comment:5>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list