Affected plugin | WP 2FA |
Active installs | 30.000+ |
Vulnerable version | <= 2.2.0 |
Audited version | 2.0.0 |
Fully patched version | 2.2.1 |
Recommended remediation | Upgrade to version 2.2.1 or higher |
Description
An attacker can bypass all two-factor authentication checks by deleting a hidden input field in the 2FA form.
Proof of concept
The Login::login_form_validate_2fa method contains a logical flaw
which allows an attacker to completely bypass the check of a user’s configured two-factor authentication provider.
The plugin supports the following 2FA methods, each of which is assigned an identifier in the code:
- Emergency code sent to the user’s email (Key: “email”)
- TOTP (Key: “totp”)
- Backup codes that the user printed out (Key: “backup_codes”)
The trimmed-down logic of the method is as follows:
public function login_form_validate_2fa()
{
// EDITOR: nonce is verified here.
if (isset($_POST['provider'])) { // phpcs:ignore
$provider = sanitize_textarea_field(wp_unslash($_POST['provider'])); // phpcs:ignore
}
// Validate TOTP.
if ('totp' === $provider && true !== self::validate_totp_authentication($user)) {
// EDITOR: TOTP is validated here.
// EDITOR: This branch calls exit() on validation failure only.
}
// Backup Codes.
if ('backup_codes' === $provider && true !== self::validate_backup_codes($user)) {
// EDITOR: backup codes are validated here.
// EDITOR: This branch calls exit() on validation failure only.
}
// Validate Email.
if ('email' === $provider && true !== self::validate_email_authentication($user)) {
// EDITOR: backup codes are validated here.
// EDITOR: This branch calls exit() on validation failure only.
}
// EDITOR: The user is logged in here
}
The critical part is here:
if (isset($_POST['provider'])) {
$provider = sanitize_textarea_field(wp_unslash($_POST['provider']));
}
The “$provider” variable is only assigned if the post request
contains a “provider” key. Otherwise, it is treated as NULL by PHP.
Thus, an attacker can skip all checks (since $provider is always NULL) and he will be logged in directly.
By merely removing the “provider” hidden input field (or setting it to an arbitrary value) in the browser’s dev tools the entire plugin can be bypassed.
Of course, this also works by sending a curl request that does not specify a provider key.
Proposed patch
There needs to be much better input validation in this method.
An example fix could look like this:
$valid_providers = [
'email',
'totp',
'backup_codes',
];
if (
! isset($_POST['provider'])
|| ! is_string($_POST['provider'])
|| ! in_array($_POST['provider'], $valid_providers, true )) {
wp_die('Forbidden', 'Forbidden', 403);
}
Timeline
Vendor contacted | The issue was already fixed before we could submit the report. |
First Response | – |
Fully patched at | May 02, 2022 |
Publicly disclosed | April 24, 2023 |
Miscellaneous
- The vendor did not disclose this critical vulnerability whatsoever in the release notes of the plugin. We think it was either fixed by accident or withheld.
Leave a Reply