| @@ -52,9 +52,9 @@ | ||
| 52 | 52 | } |
| 53 | 53 | |
| 54 | 54 | if ( $ticket_id && $auth_code ) { |
| 55 | 55 | $ticket = new WPSC_Ticket( $ticket_id ); |
| 56 | - self::$url_auth = $ticket->auth_code == $auth_code ? true : false; | |
| 56 | + self::$url_auth = ( $ticket->auth_code && hash_equals( (string) $ticket->auth_code, $auth_code ) ); | |
| 57 | 57 | } |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | ob_start();?> |
| @@ -389,8 +389,18 @@ | ||
| 389 | 389 | * @return void |
| 390 | 390 | */ |
| 391 | 391 | public static function confirm_open_ticket_auth() { |
| 392 | 392 | |
| 393 | + // Rate limiting (mirrors WPSC_Current_User::confirm_guest_login()). | |
| 394 | + $ip_address = WPSC_DF_IP_Address::get_current_user_ip(); | |
| 395 | + $attempt_key = 'wpsc_otp_attempts_' . md5( $ip_address ); | |
| 396 | + $attempts = get_transient( $attempt_key ); | |
| 397 | + $attempts = $attempts ? $attempts : 1; | |
| 398 | + | |
| 399 | + if ( $attempts >= 5 ) { | |
| 400 | + wp_send_json_error( 'Too many attempts. Please try again later.', 429 ); | |
| 401 | + } | |
| 402 | + | |
| 393 | 403 | if ( check_ajax_referer( 'wpsc_confirm_open_ticket_auth', '_ajax_nonce', false ) != 1 ) { |
| 394 | 404 | wp_send_json_error( 'Unauthorized request!', 401 ); |
| 395 | 405 | } |
| 396 | 406 | |
| @@ -411,11 +421,30 @@ | ||
| 411 | 421 | wp_send_json_error( 'Bad request', 400 ); |
| 412 | 422 | } |
| 413 | 423 | |
| 414 | 424 | if ( ! $otp->is_valid( $verification_otp ) ) { |
| 425 | + | |
| 426 | + // Increment attempt counter. | |
| 427 | + ++$attempts; | |
| 428 | + set_transient( $attempt_key, $attempts, 300 ); // 5 minute lockout. | |
| 429 | + | |
| 430 | + // Add per-OTP attempt tracking. | |
| 431 | + $otp_attempt_key = 'wpsc_otp_' . $id . '_attempts'; | |
| 432 | + $otp_attempts = get_transient( $otp_attempt_key ); | |
| 433 | + $otp_attempts = $otp_attempts ? $otp_attempts + 1 : 1; | |
| 434 | + set_transient( $otp_attempt_key, $otp_attempts, 600 ); | |
| 435 | + | |
| 436 | + if ( $otp_attempts >= 3 ) { | |
| 437 | + WPSC_Email_OTP::destroy( $otp ); | |
| 438 | + wp_send_json_error( 'OTP has been invalidated due to too many failed attempts', 403 ); | |
| 439 | + } | |
| 440 | + | |
| 415 | 441 | echo wp_json_encode( array( 'isSuccess' => 0 ) ); |
| 416 | 442 | wp_die(); |
| 417 | 443 | } |
| 444 | + | |
| 445 | + // Clear rate limiting on success. | |
| 446 | + delete_transient( $attempt_key ); | |
| 418 | 447 | |
| 419 | 448 | $data = json_decode( $otp->data, true ); |
| 420 | 449 | $data['auth_token'] = WPSC_Functions::get_random_string( 100 ); |
| 421 | 450 | $data['auth_type'] = 'open-ticket'; |