← All changes
|
front-end/default-fields/turnstile/turnstile.php
+144
-43
3.16.0
→
4.0.3
View file →
| @@ -13,10 +13,13 @@ | ||
| 13 | 13 | $response = wp_remote_post( $path, array( |
| 14 | 14 | 'body' => $data |
| 15 | 15 | ) ); |
| 16 | 16 | |
| 17 | - if ( ! is_wp_error( $response ) ) | |
| 18 | - return $response["body"]; | |
| 17 | + if ( is_wp_error( $response ) ) { | |
| 18 | + return ''; | |
| 19 | + } | |
| 20 | + | |
| 21 | + return isset( $response['body'] ) ? $response['body'] : ''; | |
| 19 | 22 | } |
| 20 | 23 | |
| 21 | 24 | /** |
| 22 | 25 | * Gets the challenge HTML wrapper for Turnstile. |
| @@ -69,15 +72,16 @@ | ||
| 69 | 72 | /* if we do not have a turnstile field do nothing */ |
| 70 | 73 | if( empty( $field ) ) |
| 71 | 74 | return; |
| 72 | 75 | |
| 73 | - //do not add script if there is no shortcode | |
| 76 | + global $wppb_turnstile_present; | |
| 74 | 77 | global $wppb_shortcode_on_front; |
| 75 | - if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) ) | |
| 78 | + | |
| 79 | + //do not add script on regular frontend pages unless a PB shortcode or Turnstile HTML is present | |
| 80 | + if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) && ( !isset( $wppb_turnstile_present ) || $wppb_turnstile_present === false ) ) | |
| 76 | 81 | return; |
| 77 | 82 | |
| 78 | 83 | //do not add script if the html for the field has not been added |
| 79 | - global $wppb_turnstile_present; | |
| 80 | 84 | if( !isset( $wppb_turnstile_present ) || $wppb_turnstile_present === false ) |
| 81 | 85 | return; |
| 82 | 86 | |
| 83 | 87 | //we don't have jquery on the backend |
| @@ -183,15 +187,16 @@ | ||
| 183 | 187 | 'response' => $response |
| 184 | 188 | ) |
| 185 | 189 | ); |
| 186 | 190 | |
| 187 | - $answers = json_decode($getResponse, true); | |
| 191 | + $answers = json_decode( $getResponse, true ); | |
| 188 | 192 | $turnstileResponse = new wppb_TurnstileResponse(); |
| 189 | 193 | |
| 190 | - if (trim($answers ['success']) == true) { | |
| 194 | + // Fail closed when the HTTP call fails or the body is not valid JSON. | |
| 195 | + if ( ! is_array( $answers ) || empty( $answers['success'] ) ) { | |
| 196 | + $turnstileResponse->is_valid = false; | |
| 197 | + } else { | |
| 191 | 198 | $turnstileResponse->is_valid = true; |
| 192 | - } else { | |
| 193 | - $turnstileResponse->is_valid = false; | |
| 194 | 199 | } |
| 195 | 200 | |
| 196 | 201 | return $turnstileResponse; |
| 197 | 202 | |
| @@ -198,8 +203,15 @@ | ||
| 198 | 203 | } |
| 199 | 204 | |
| 200 | 205 | /* the function to validate the Turnstile response with the API */ |
| 201 | 206 | function wppb_validate_turnstile_response( $publickey, $privatekey ){ |
| 207 | + /* If the Turnstile keys are not configured the widget cannot work for anyone, so do not enforce - | |
| 208 | + otherwise an incomplete setup would lock every visitor out of the form. These keys are admin-side | |
| 209 | + configuration, not attacker controlled, so this cannot be used to bypass a properly configured Turnstile. */ | |
| 210 | + if ( empty( $publickey ) || empty( $privatekey ) ) { | |
| 211 | + return true; | |
| 212 | + } | |
| 213 | + | |
| 202 | 214 | if (isset($_POST['cf-turnstile-response'])){ |
| 203 | 215 | $turnstile_response_field = sanitize_textarea_field( $_POST['cf-turnstile-response'] ); |
| 204 | 216 | } else { |
| 205 | 217 | $turnstile_response_field = ''; |
| @@ -228,15 +240,16 @@ | ||
| 228 | 240 | } |
| 229 | 241 | |
| 230 | 242 | } |
| 231 | 243 | |
| 232 | - // Save valid results when they are being triggered from an ajax request | |
| 233 | - if( wp_doing_ajax() && isset( $_POST['action'] ) && $_POST['action'] == 'pms_validate_checkout' ){ | |
| 244 | + // Save valid results when they are being triggered from an ajax request that only pre-validates the | |
| 245 | + // credentials, so the same single use token is still accepted on the form submission that follows it | |
| 246 | + if( wppb_is_captcha_prevalidation_request() ){ | |
| 234 | 247 | |
| 235 | - $saved = get_option( 'wppb_turnstile_validations', array() ); | |
| 248 | + $saved = wppb_prune_captcha_prevalidations( get_option( 'wppb_turnstile_validations', array() ) ); | |
| 236 | 249 | |
| 237 | 250 | if( $already_validated === true ) |
| 238 | - $saved[ $turnstile_response_field ] = true; | |
| 251 | + $saved[ $turnstile_response_field ] = time(); | |
| 239 | 252 | |
| 240 | 253 | update_option( 'wppb_turnstile_validations', $saved, false ); |
| 241 | 254 | |
| 242 | 255 | } |
| @@ -288,9 +301,12 @@ | ||
| 288 | 301 | global $wppb_turnstile_response; |
| 289 | 302 | if (!isset($wppb_turnstile_response)){ |
| 290 | 303 | $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); |
| 291 | 304 | } |
| 292 | - if ( ( $wppb_turnstile_response == false ) && ( $field['required'] == 'Yes' ) ){ | |
| 305 | + /* Turnstile must fail closed: whenever it is configured to display on this form it has to be | |
| 306 | + verified, regardless of the "required" toggle. A missing/empty token makes | |
| 307 | + wppb_validate_turnstile_response() return false, so bots that omit cf-turnstile-response are blocked. */ | |
| 308 | + if ( $wppb_turnstile_response == false ){ | |
| 293 | 309 | return __('Cloudflare Turnstile could not be verified. Please try again.', 'profile-builder'); |
| 294 | 310 | } |
| 295 | 311 | } |
| 296 | 312 | } |
| @@ -466,34 +482,34 @@ | ||
| 466 | 482 | if ( isset( $_POST['log'] ) && !is_wp_error($user) && !isset( $_POST['pms_login'] ) ) { |
| 467 | 483 | |
| 468 | 484 | $field = wppb_get_turnstile_field(); |
| 469 | 485 | if ( !empty($field) ){ |
| 470 | - global $wppb_turnstile_response; | |
| 486 | + /* Work out whether Turnstile is enabled for the form that was actually submitted before verifying | |
| 487 | + anything. The Cloudflare token is single use, so verifying it on a form where our widget was never | |
| 488 | + displayed spends a token that belongs to whatever else protects that form, and that plugin\'s own | |
| 489 | + check then fails with timeout-or-duplicate. */ | |
| 490 | + if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) { | |
| 491 | + // it\'s a PB login form, check if we have Turnstile on it | |
| 492 | + $turnstile_enabled = ( isset($field['turnstile-pb-forms']) && (strpos($field['turnstile-pb-forms'], 'pb_login') !== false) ); | |
| 493 | + } | |
| 494 | + else { | |
| 495 | + // default WP login form | |
| 496 | + $turnstile_enabled = ( isset($field['turnstile-wp-forms']) && (strpos($field['turnstile-wp-forms'], 'default_wp_login') !== false) ); | |
| 497 | + } | |
| 471 | 498 | |
| 472 | - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 499 | + if ( $turnstile_enabled ) { | |
| 500 | + global $wppb_turnstile_response; | |
| 473 | 501 | |
| 474 | - $turnstile_error_message = __('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 502 | + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 475 | 503 | |
| 476 | - //Turnstile error for displaying on the PB login form | |
| 477 | - if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) { | |
| 504 | + $turnstile_error_message = __('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 478 | 505 | |
| 479 | - // it\'s a PB login form, check if we have Turnstile on it and display error if not valid | |
| 480 | - if ((isset($field['turnstile-pb-forms'])) && (strpos($field['turnstile-pb-forms'], 'pb_login') !== false) && ($wppb_turnstile_response == false)) { | |
| 506 | + if ( $wppb_turnstile_response == false ) { | |
| 481 | 507 | $user = new WP_Error('wppb_turnstile_error', $turnstile_error_message); |
| 482 | 508 | remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); |
| 483 | 509 | remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); |
| 484 | 510 | } |
| 485 | - | |
| 486 | 511 | } |
| 487 | - else { | |
| 488 | - //Turnstile error for displaying on the default WP login form | |
| 489 | - if (isset($field['turnstile-wp-forms']) && (strpos($field['turnstile-wp-forms'], 'default_wp_login') !== false) && ($wppb_turnstile_response == false)) { | |
| 490 | - $user = new WP_Error('wppb_turnstile_error', $turnstile_error_message); | |
| 491 | - remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); | |
| 492 | - remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); | |
| 493 | - } | |
| 494 | - | |
| 495 | - } | |
| 496 | 512 | } |
| 497 | 513 | } |
| 498 | 514 | return $user; |
| 499 | 515 | } |
| @@ -548,16 +564,21 @@ | ||
| 548 | 564 | return; |
| 549 | 565 | |
| 550 | 566 | $field = wppb_get_turnstile_field(); |
| 551 | 567 | if ( !empty($field) ){ |
| 552 | - global $wppb_turnstile_response; | |
| 553 | - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 568 | + /* Only verify where Turnstile is configured for the form being submitted. The Cloudflare token is | |
| 569 | + single use, so verifying it on a form our widget was never displayed on spends a token that another | |
| 570 | + plugin protecting that form still needs, and its own check then fails with timeout-or-duplicate. */ | |
| 571 | + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_recover_password' ) !== false ) ) { | |
| 572 | + global $wppb_turnstile_response; | |
| 573 | + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 554 | 574 | |
| 555 | - $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 575 | + $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 556 | 576 | |
| 557 | - // If Turnstile not entered or incorrect Turnstile answer | |
| 558 | - if ( isset( $_REQUEST['cf-turnstile-response'] ) && ( ( "" === $_REQUEST['cf-turnstile-response'] ) || ( $wppb_turnstile_response == false ) ) ) { | |
| 559 | - wp_die( esc_html( $turnstile_error_message ) . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ; | |
| 577 | + // Fail closed: a missing token is treated as a failed verification. | |
| 578 | + if ( $wppb_turnstile_response == false ) { | |
| 579 | + wp_die( esc_html( $turnstile_error_message ) . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ; | |
| 580 | + } | |
| 560 | 581 | } |
| 561 | 582 | } |
| 562 | 583 | } |
| 563 | 584 | add_action('lostpassword_post','wppb_verify_turnstile_default_wp_recover_password'); |
| @@ -593,16 +614,21 @@ | ||
| 593 | 614 | function wppb_verify_turnstile_default_wp_register( $errors ){ |
| 594 | 615 | |
| 595 | 616 | $field = wppb_get_turnstile_field(); |
| 596 | 617 | if ( !empty($field) ){ |
| 597 | - global $wppb_turnstile_response; | |
| 598 | - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 618 | + /* Only verify where Turnstile is configured for the form being submitted. The Cloudflare token is | |
| 619 | + single use, so verifying it on a form our widget was never displayed on spends a token that another | |
| 620 | + plugin protecting that form still needs, and its own check then fails with timeout-or-duplicate. */ | |
| 621 | + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_register' ) !== false ) ) { | |
| 622 | + global $wppb_turnstile_response; | |
| 623 | + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 599 | 624 | |
| 600 | - $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 625 | + $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder'); | |
| 601 | 626 | |
| 602 | - // If Turnstile not entered or incorrect Turnstile answer | |
| 603 | - if ( isset( $_REQUEST['cf-turnstile-response'] ) && ( ( "" === $_REQUEST['cf-turnstile-response'] ) || ( $wppb_turnstile_response == false ) ) ) { | |
| 604 | - $errors->add( 'wppb_turnstile_error', $turnstile_error_message ); | |
| 627 | + // Fail closed: a missing token is treated as a failed verification. | |
| 628 | + if ( $wppb_turnstile_response == false ) { | |
| 629 | + $errors->add( 'wppb_turnstile_error', $turnstile_error_message ); | |
| 630 | + } | |
| 605 | 631 | } |
| 606 | 632 | } |
| 607 | 633 | |
| 608 | 634 | return $errors; |
| @@ -607,8 +633,83 @@ | ||
| 607 | 633 | |
| 608 | 634 | return $errors; |
| 609 | 635 | } |
| 610 | 636 | add_filter('registration_errors','wppb_verify_turnstile_default_wp_register'); |
| 637 | + | |
| 638 | +/* Display Turnstile html on default WP Comments form */ | |
| 639 | +function wppb_display_turnstile_default_wp_comments(){ | |
| 640 | + $field = wppb_get_turnstile_field(); | |
| 641 | + | |
| 642 | + if ( !empty( $field ) ) { | |
| 643 | + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) !== false ) ) { | |
| 644 | + $publickey = trim( $field['turnstile-site-key'] ); | |
| 645 | + $item_title = apply_filters( 'wppb_comments_turnstile_custom_field_' . $field['id'] . '_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true ) ); | |
| 646 | + $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true ); | |
| 647 | + | |
| 648 | + global $wppb_turnstile_present; | |
| 649 | + $wppb_turnstile_present = true; | |
| 650 | + | |
| 651 | + $turnstile_output = '<label for="turnstile_response_field">' . $item_title . '</label>' . wppb_turnstile_get_html( $publickey, 'default_wp_comments' ); | |
| 652 | + if ( !empty( $item_description ) ) | |
| 653 | + $turnstile_output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>'; | |
| 654 | + | |
| 655 | + echo '<div class="wppb-form-field wppb-turnstile">' . $turnstile_output . '</div>'; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ /* properly escaped when constructing the var */ | |
| 656 | + } | |
| 657 | + } | |
| 658 | +} | |
| 659 | +add_action( 'comment_form_after_fields', 'wppb_display_turnstile_default_wp_comments' ); | |
| 660 | +add_action( 'comment_form_logged_in_after', 'wppb_display_turnstile_default_wp_comments' ); | |
| 661 | + | |
| 662 | +function wppb_get_turnstile_default_wp_comments_error_message(){ | |
| 663 | + return __( 'Cloudflare Turnstile could not be verified. Please try again.', 'profile-builder' ); | |
| 664 | +} | |
| 665 | + | |
| 666 | +function wppb_display_turnstile_default_wp_comments_error(){ | |
| 667 | + if ( !isset( $_GET['wppb_comment_turnstile_error'] ) ) | |
| 668 | + return; | |
| 669 | + | |
| 670 | + $field = wppb_get_turnstile_field(); | |
| 671 | + | |
| 672 | + if ( empty( $field ) || !isset( $field['turnstile-wp-forms'] ) || ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) === false ) ) | |
| 673 | + return; | |
| 674 | + | |
| 675 | + echo '<p class="wppb-error wppb-comment-captcha-error" id="wppb_comment_turnstile_error">' . esc_html( wppb_get_turnstile_default_wp_comments_error_message() ) . '</p>'; | |
| 676 | +} | |
| 677 | +add_action( 'comment_form_top', 'wppb_display_turnstile_default_wp_comments_error' ); | |
| 678 | + | |
| 679 | +// Verify Turnstile for default WP Comments form | |
| 680 | +function wppb_verify_turnstile_default_wp_comments( $approved, $commentdata ){ | |
| 681 | + if ( !isset( $_POST['comment_post_ID'] ) ) | |
| 682 | + return $approved; | |
| 683 | + | |
| 684 | + $field = wppb_get_turnstile_field(); | |
| 685 | + | |
| 686 | + if ( !empty( $field ) ) { | |
| 687 | + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) !== false ) ) { | |
| 688 | + global $wppb_turnstile_response; | |
| 689 | + if ( !isset( $wppb_turnstile_response ) ) | |
| 690 | + $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) ); | |
| 691 | + | |
| 692 | + if ( $wppb_turnstile_response == false ) { | |
| 693 | + $redirect_to = wp_get_referer(); | |
| 694 | + | |
| 695 | + if ( empty( $redirect_to ) && isset( $commentdata['comment_post_ID'] ) ) | |
| 696 | + $redirect_to = get_permalink( absint( $commentdata['comment_post_ID'] ) ); | |
| 697 | + | |
| 698 | + if ( !empty( $redirect_to ) && !wp_doing_ajax() ) { | |
| 699 | + $redirect_to = preg_replace( '/#.*$/', '', remove_query_arg( array( 'wppb_comment_recaptcha_error', 'wppb_comment_turnstile_error' ), $redirect_to ) ); | |
| 700 | + wp_safe_redirect( add_query_arg( 'wppb_comment_turnstile_error', '1', $redirect_to ) . '#respond' ); | |
| 701 | + exit; | |
| 702 | + } | |
| 703 | + | |
| 704 | + return new WP_Error( 'wppb_turnstile_error', wppb_get_turnstile_default_wp_comments_error_message(), 200 ); | |
| 705 | + } | |
| 706 | + } | |
| 707 | + } | |
| 708 | + | |
| 709 | + return $approved; | |
| 710 | +} | |
| 711 | +add_filter( 'pre_comment_approved', 'wppb_verify_turnstile_default_wp_comments', 10, 2 ); | |
| 611 | 712 | |
| 612 | 713 | // set default values in case there's already an existing Turnstile field in Manage fields (when upgrading) |
| 613 | 714 | function wppb_turnstile_set_default_values() { |
| 614 | 715 | $manage_fields = get_option('wppb_manage_fields', 'not_set'); |