Skip to content
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

Add logging function when failed to authenticate #462

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Lucisu
Copy link

@Lucisu Lucisu commented Oct 4, 2022

I've added the method is_valid_authcode to the class Two_Factor_Provider so providers can use it when failing to validate the authentication.

By default, it uses the error_log function, but there are actions and filters that can be used to extend it.

Related issue: #459

@jeffpaul jeffpaul linked an issue Oct 4, 2022 that may be closed by this pull request
@jeffpaul jeffpaul added this to the 0.9.0 milestone Oct 4, 2022
@Lucisu
Copy link
Author

Lucisu commented Oct 4, 2022

It's done.

For me, it would make more sense to call a method inside the Two_Factor_Provider class to validate the authentication, which would call the child provider method validate_authentication, getting its result and logging the failure in the parent method if needed, instead of adding to every new provider the code:

if ( ! $success ) {
	$this->log_failure( $user, $response );
}
return $success;

Please, let me know what you think about this.

Copy link
Collaborator

@kasparsd kasparsd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we move the logging to the main conditional here:

if ( true !== $provider->validate_authentication( $user ) ) {
do_action( 'wp_login_failed', $user->user_login );
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( esc_html__( 'Failed to create a login nonce.', 'two-factor' ) );
}
self::login_html( $user, $login_nonce['key'], $_REQUEST['redirect_to'], esc_html__( 'ERROR: Invalid verification code.', 'two-factor' ), $provider );
exit;
}

so that (1) we don't need to modify each provider to add this and (2) providers are not responsible for logging.

We could also update the validate_authentication() methods to return WP_Error objects which could provide their own error messages which can then be used by the logger as necessary.

* @param Two_Factor_Provider $this The Provider class used during the authentication.
*/
if ( apply_filters( 'two_factor_log_failure', true, $user, $code, $log_message, $this ) ) {
error_log( $log_message );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we sent an email to admin_email instead of logging it the error log by default?

The two_factor_user_login_failed could be used to send it to other destinations such as error log or custom logging utility as needed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a few instance where WP core is calling error_log() so we should be OK to use it too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeffpaul, @georgestephanis What are your thoughts on this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only uncertainty is if it's a good idea to include the bad code in this. I don't have any strong opposition, but I'm slightly concerned that if it's one-digit off, or enough times combined with the timestamp being one tick off, something may be able to be reversed.

But I'm fine with email or not (someone trying to brute force the two factor code may be ... uhh ... a lot of emails though) -- but I definitely think error_log should be included.

Also, appreciate the filter so a third-party catcher could also listen for failures for alternate logs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe support the admin_email but have it disabled and filterable to enable? I think I'd want it enabled on sites I'm using, but I could see where that could get VERY chatty for someone with a large amount of users?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also depend on the provider; for TOTP it's probably not uncommon to make a typo, and getting an email for that seems like overkill. Emailing after 3 failed attempts could be more reasonable (as long as there's a cap on how often it's sent), but IMO #477 seems like a better way to address brute force attacks.

If a backup code fails, though, I can see it being more useful to email, because it's very out of the ordinary. Related #476.

WebAuthn keys (#427) could also be worth emailing about, since they should be less prone to accidental failure, especially with user verification/PINs disabled.


I don't think gaining access to several TOTPs would let you reverse the secret, but I'm not sure how useful including it is either.

Copy link
Author

@Lucisu Lucisu Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid that moving the logging function out of the providers will not allow many customizations from them. I can think of some examples where it's useful to allow providers to set their own log data:

  1. An email provider can send the email address used to log in instead of the user ID.
  2. Define their own "failed amount" before adding the log.
  3. Adding their own log methods.

@georgestephanis
Copy link
Collaborator

georgestephanis commented Oct 14, 2022 via email

@iandunn
Copy link
Member

iandunn commented Oct 14, 2022

Once #427 lands, logging should be added there too.

Copy link

@ravinderk ravinderk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lucisu, I left a few suggestions. Feel free to ask questions.

}

if ( ! $success ) {
$this->log_failure( $user, $response );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause an error because $response is an object, but the function accepts string as the second argument.

do_action( 'two_factor_user_login_failed', $user, $code, $this );

/* translators: %1$d: the user's ID %2$s: the code used to authenticate */
$log_message = sprintf( esc_html__( 'The user %1$s (ID: %2$d) failed to login using the code "%3$s"', 'two-factor' ), esc_html( $user->user_login ), $user->ID, esc_html( $code ) );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need to reveal the user id if we print the user name in the message.

*
* @return void
*/
public function log_failure( $user, $code = false ) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should support only string as a second param. Is there any benefit we are getting by supporting boolean as an alternative value?

@@ -305,7 +305,13 @@ public function authentication_page( $user ) {
*/
public function validate_authentication( $user ) {
$backup_code = isset( $_POST['two-factor-backup-code'] ) ? sanitize_text_field( wp_unslash( $_POST['two-factor-backup-code'] ) ) : false;
return $this->validate_code( $user, filter_var( $backup_code, FILTER_SANITIZE_STRING ) );
$success = $this->validate_code( $user, filter_var( $backup_code, FILTER_SANITIZE_STRING ) );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we place logging logic in the core class where we validate the authentication provider:

if ( true !== $provider->validate_authentication( $user ) ) {

Are we getting benefits from adding logging logic here?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not worry about the format of logging data or internal; the log function should dump logging data in an error log file.

@jeffpaul jeffpaul modified the milestones: 0.9.0, 0.10.0 May 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Log or alert on failed 2FA codes
6 participants