-
Notifications
You must be signed in to change notification settings - Fork 177
#831 Fix nonce verification in 2FA flow #833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -452,7 +452,7 @@ public static function get_user_two_factor_revalidate_url( $interim = false ) { | |||||
| * @return boolean | ||||||
| */ | ||||||
| public static function is_valid_user_action( $user_id, $action ) { | ||||||
| $request_nonce = isset( $_REQUEST[ self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG ] ) ? wp_unslash( $_REQUEST[ self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG ] ) : ''; | ||||||
| $request_nonce = isset( $_REQUEST[ self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG ] ) ? wp_unslash( $_REQUEST[ self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG ] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value only passed to wp_verify_nonce(). | ||||||
|
|
||||||
| if ( ! $user_id || ! $action || ! $request_nonce ) { | ||||||
| return false; | ||||||
|
|
@@ -473,8 +473,8 @@ public static function is_valid_user_action( $user_id, $action ) { | |||||
| */ | ||||||
| public static function current_user_being_edited() { | ||||||
| // Try to resolve the user ID from the request first. | ||||||
| if ( ! empty( $_REQUEST['user_id'] ) ) { | ||||||
| $user_id = intval( $_REQUEST['user_id'] ); | ||||||
| if ( ! empty( $_REQUEST['user_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in trigger_user_settings_action() via is_valid_user_action() before any state change. | ||||||
| $user_id = intval( $_REQUEST['user_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in trigger_user_settings_action() via is_valid_user_action() before any state change. | ||||||
|
|
||||||
| if ( current_user_can( 'edit_user', $user_id ) ) { | ||||||
| return $user_id; | ||||||
|
|
@@ -493,7 +493,7 @@ public static function current_user_being_edited() { | |||||
| * @return void | ||||||
| */ | ||||||
| public static function trigger_user_settings_action() { | ||||||
| $action = isset( $_REQUEST[ self::USER_SETTINGS_ACTION_QUERY_VAR ] ) ? wp_unslash( $_REQUEST[ self::USER_SETTINGS_ACTION_QUERY_VAR ] ) : ''; | ||||||
| $action = isset( $_REQUEST[ self::USER_SETTINGS_ACTION_QUERY_VAR ] ) ? wp_unslash( $_REQUEST[ self::USER_SETTINGS_ACTION_QUERY_VAR ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in is_valid_user_action() before do_action. | ||||||
| $user_id = self::current_user_being_edited(); | ||||||
|
|
||||||
| if ( self::is_valid_user_action( $user_id, $action ) ) { | ||||||
|
||||||
|
|
@@ -906,7 +906,7 @@ public static function show_two_factor_login( $user ) { | |||||
| wp_die( esc_html__( 'Failed to create a login nonce.', 'two-factor' ) ); | ||||||
| } | ||||||
|
|
||||||
| $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : admin_url(); | ||||||
| $redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : admin_url(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value only used for redirect; auth protected by 2FA login nonce later. | ||||||
|
|
||||||
|
PANawkar marked this conversation as resolved.
|
||||||
| self::login_html( $user, $login_nonce['key'], $redirect_to ); | ||||||
| } | ||||||
|
|
@@ -960,6 +960,12 @@ public static function maybe_show_reset_password_notice( $errors ) { | |||||
| return $errors; | ||||||
| } | ||||||
|
|
||||||
| // Verify login form nonce when present (e.g. wp-login.php); skip only when nonce is not sent (custom login forms). | ||||||
| if ( isset( $_POST['_wpnonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'log-in' ) ) { | ||||||
| return $errors; | ||||||
| } | ||||||
|
PANawkar marked this conversation as resolved.
|
||||||
|
|
||||||
| // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above when _wpnonce present; absent for custom login forms. | ||||||
| $user_name = sanitize_user( wp_unslash( $_POST['log'] ) ); | ||||||
| $attempted_user = get_user_by( 'login', $user_name ); | ||||||
| if ( ! $attempted_user && str_contains( $user_name, '@' ) ) { | ||||||
|
|
@@ -1487,11 +1493,11 @@ public static function rest_api_can_edit_user_and_update_two_factor_options( $us | |||||
| * @since 0.2.0 | ||||||
| */ | ||||||
| public static function login_form_validate_2fa() { | ||||||
| $wp_auth_id = ! empty( $_REQUEST['wp-auth-id'] ) ? absint( $_REQUEST['wp-auth-id'] ) : 0; | ||||||
| $nonce = ! empty( $_REQUEST['wp-auth-nonce'] ) ? wp_unslash( $_REQUEST['wp-auth-nonce'] ) : ''; | ||||||
| $provider = ! empty( $_REQUEST['provider'] ) ? wp_unslash( $_REQUEST['provider'] ) : ''; | ||||||
| $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? wp_unslash( $_REQUEST['redirect_to'] ) : ''; | ||||||
| $is_post_request = ( 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ); | ||||||
| $wp_auth_id = ! empty( $_REQUEST['wp-auth-id'] ) ? absint( $_REQUEST['wp-auth-id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in _login_form_validate_2fa() via verify_login_nonce() before any use. | ||||||
|
PANawkar marked this conversation as resolved.
|
||||||
| $nonce = ! empty( $_REQUEST['wp-auth-nonce'] ) ? wp_unslash( $_REQUEST['wp-auth-nonce'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in _login_form_validate_2fa() before any use. | ||||||
|
||||||
| $nonce = ! empty( $_REQUEST['wp-auth-nonce'] ) ? wp_unslash( $_REQUEST['wp-auth-nonce'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in _login_form_validate_2fa() before any use. | |
| $nonce = ( isset( $_REQUEST['wp-auth-nonce'] ) && is_scalar( $_REQUEST['wp-auth-nonce'] ) ) ? sanitize_text_field( wp_unslash( (string) $_REQUEST['wp-auth-nonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in _login_form_validate_2fa() before any use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if ( isset( $_REQUEST['rememberme'] ) && $_REQUEST['rememberme'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Request read only after successful verify_login_nonce() in this request. | |
| if ( ! empty( $_REQUEST['rememberme'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Request read only after successful verify_login_nonce() in this request. |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phpcs ignore reason here says the value is only used after the 2FA login nonce is verified by the caller, but rememberme() is also used while rendering the initial 2FA form (login_html()), before any 2FA nonce verification happens. Please update the ignore rationale to reflect the real trust boundary (e.g., it’s a non-destructive display/flow flag) and/or normalize the value explicitly to a boolean/int.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -171,11 +171,11 @@ public static function get_code( $length = 8, $chars = '1234567890' ) { | |||||
| * @return false|string Auth code on success, false if the field is not set or not expected length. | ||||||
| */ | ||||||
| public static function sanitize_code_from_request( $field, $length = 0 ) { | ||||||
| if ( empty( $_REQUEST[ $field ] ) ) { | ||||||
| if ( empty( $_REQUEST[ $field ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Caller (core) verifies nonce before provider processing. | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| $code = wp_unslash( $_REQUEST[ $field ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, handled by the core method already. | ||||||
| $code = wp_unslash( $_REQUEST[ $field ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Caller (core) verifies nonce; value sanitized below. | ||||||
|
||||||
| $code = wp_unslash( $_REQUEST[ $field ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Caller (core) verifies nonce; value sanitized below. | |
| $code = wp_unslash( $_REQUEST[ $field ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Caller (core) verifies nonce; value normalized below. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$request_noncecomes from$_REQUESTand may be an array (e.g....?arg[]=x), which can trigger notices or unexpected behavior when passed intowp_verify_nonce(). Consider guarding withis_scalar()/ casting to string and sanitizing (similar to the_wpnoncehandling elsewhere) before verifying.