From e4d19b09257f48a8be49568c10a15ad345374a98 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 6 Jul 2026 21:49:50 +0530 Subject: [PATCH 1/2] Fix #705: Persist custom fields during 2FA challenge This safely copies unknown variables into hidden fields on the 2FA interstitial screen, ensuring third-party login forms function properly without leaking standard WordPress fields or plaintext passwords. --- class-two-factor-core.php | 61 +++++++++++++++++++++++++++++++++ tests/class-two-factor-core.php | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/class-two-factor-core.php b/class-two-factor-core.php index 1f039866..0c362d09 100644 --- a/class-two-factor-core.php +++ b/class-two-factor-core.php @@ -1161,6 +1161,7 @@ public static function login_html( $user, $login_nonce, $redirect_to, $error_msg + authentication_page( $user ); ?> @@ -2624,5 +2625,65 @@ public static function filter_session_information( $session, $user_id ) { return $session; } + + /** + * Output custom $_POST fields as hidden inputs. + * + * Iterates over $_POST and outputs hidden inputs for fields added by third-party plugins, + * ensuring that standard WordPress and Two-Factor fields (especially sensitive ones like `pwd`) + * are explicitly ignored. + * + * @since 0.17.0 + */ + private static function print_custom_post_fields() { + $blocklist = array( + // Standard WP Login fields. + 'log', + 'pwd', + 'wp-submit', + 'redirect_to', + 'rememberme', + 'interim-login', + 'testcookie', + '_wpnonce', + '_wp_http_referer', + // Additional common login fields (e.g., WooCommerce, custom forms). + 'password', + 'user_pass', + 'username', + 'user_login', + // Two Factor specific fields. + 'provider', + 'wp-auth-id', + 'wp-auth-nonce', + 'action', + ); + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + foreach ( $_POST as $key => $value ) { + if ( in_array( $key, $blocklist, true ) ) { + continue; + } + self::print_hidden_inputs( $key, $value ); + } + } + + /** + * Recursively output hidden inputs for a given key/value. + * + * @since 0.17.0 + * + * @param string $name Input name. + * @param string|array $value Input value. + */ + private static function print_hidden_inputs( $name, $value ) { + if ( is_array( $value ) ) { + foreach ( $value as $k => $v ) { + self::print_hidden_inputs( $name . '[' . $k . ']', $v ); + } + } else { + echo '' . "\n"; + } + } } diff --git a/tests/class-two-factor-core.php b/tests/class-two-factor-core.php index 38052106..d24b89d6 100644 --- a/tests/class-two-factor-core.php +++ b/tests/class-two-factor-core.php @@ -2707,4 +2707,65 @@ public function test_add_settings_action_link() { $this->assertStringContainsString( 'Settings', $first ); $this->assertStringContainsString( 'options-general.php', $first ); } + + /** + * @covers Two_Factor_Core::print_custom_post_fields + */ + public function test_print_custom_post_fields_includes_custom_fields() { + $original_post = $_POST; + $_POST = array( + 'custom_field_1' => 'value1', + 'custom_array' => array( + 'key1' => 'val1', + 'key2' => 'val2', + ), + ); + + $method = new ReflectionMethod( 'Two_Factor_Core', 'print_custom_post_fields' ); + $method->setAccessible( true ); + + ob_start(); + $method->invoke( null ); + $output = ob_get_clean(); + + $_POST = $original_post; + + $this->assertStringContainsString( '', $output ); + $this->assertStringContainsString( '', $output ); + $this->assertStringContainsString( '', $output ); + } + + /** + * @covers Two_Factor_Core::print_custom_post_fields + */ + public function test_print_custom_post_fields_excludes_blocked_fields() { + $original_post = $_POST; + $_POST = array( + 'pwd' => 'my_secret_password', + 'password' => 'my_other_password', + 'log' => 'admin', + 'user_pass' => 'secret', + 'rememberme' => '1', + 'custom_ok' => 'allowed', + ); + + $method = new ReflectionMethod( 'Two_Factor_Core', 'print_custom_post_fields' ); + $method->setAccessible( true ); + + ob_start(); + $method->invoke( null ); + $output = ob_get_clean(); + + $_POST = $original_post; + + $this->assertStringContainsString( '', $output ); + $this->assertStringNotContainsString( 'my_secret_password', $output ); + $this->assertStringNotContainsString( 'my_other_password', $output ); + $this->assertStringNotContainsString( 'secret', $output ); + $this->assertStringNotContainsString( 'pwd', $output ); + $this->assertStringNotContainsString( 'password', $output ); + $this->assertStringNotContainsString( 'log', $output ); + $this->assertStringNotContainsString( 'user_pass', $output ); + $this->assertStringNotContainsString( 'rememberme', $output ); + } } From b5411186241a23a83dc2ccb79614c440bad6de30 Mon Sep 17 00:00:00 2001 From: Chetan Upare Date: Mon, 27 Jul 2026 06:50:18 +0000 Subject: [PATCH 2/2] Fix nested array vulnerabilities and wp_unslash issues with custom post fields --- class-two-factor-core.php | 14 +++++++++----- tests/class-two-factor-core.php | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/class-two-factor-core.php b/class-two-factor-core.php index 0c362d09..6ac59ec3 100644 --- a/class-two-factor-core.php +++ b/class-two-factor-core.php @@ -2664,7 +2664,7 @@ private static function print_custom_post_fields() { if ( in_array( $key, $blocklist, true ) ) { continue; } - self::print_hidden_inputs( $key, $value ); + self::print_hidden_inputs( $key, wp_unslash( $value ), $blocklist ); } } @@ -2673,13 +2673,17 @@ private static function print_custom_post_fields() { * * @since 0.17.0 * - * @param string $name Input name. - * @param string|array $value Input value. + * @param string $name Input name. + * @param string|array $value Input value. + * @param array $blocklist Array of keys to skip. */ - private static function print_hidden_inputs( $name, $value ) { + private static function print_hidden_inputs( $name, $value, $blocklist = array() ) { if ( is_array( $value ) ) { foreach ( $value as $k => $v ) { - self::print_hidden_inputs( $name . '[' . $k . ']', $v ); + if ( in_array( $k, $blocklist, true ) ) { + continue; + } + self::print_hidden_inputs( $name . '[' . $k . ']', $v, $blocklist ); } } else { echo '' . "\n"; diff --git a/tests/class-two-factor-core.php b/tests/class-two-factor-core.php index d24b89d6..bba5d421 100644 --- a/tests/class-two-factor-core.php +++ b/tests/class-two-factor-core.php @@ -2747,6 +2747,10 @@ public function test_print_custom_post_fields_excludes_blocked_fields() { 'user_pass' => 'secret', 'rememberme' => '1', 'custom_ok' => 'allowed', + 'nested' => array( + 'pwd' => 'nested_secret_password', + 'ok' => 'nested_allowed', + ), ); $method = new ReflectionMethod( 'Two_Factor_Core', 'print_custom_post_fields' ); @@ -2759,8 +2763,10 @@ public function test_print_custom_post_fields_excludes_blocked_fields() { $_POST = $original_post; $this->assertStringContainsString( '', $output ); + $this->assertStringContainsString( '', $output ); $this->assertStringNotContainsString( 'my_secret_password', $output ); $this->assertStringNotContainsString( 'my_other_password', $output ); + $this->assertStringNotContainsString( 'nested_secret_password', $output ); $this->assertStringNotContainsString( 'secret', $output ); $this->assertStringNotContainsString( 'pwd', $output ); $this->assertStringNotContainsString( 'password', $output );