: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
<?php if ($has2fa || $viewerIsUser): ?><p><a href="<?php echo esc_url($manageURL); ?>" class="button"><?php echo (Controller_Users::shared()->has_2fa_active($user) ? esc_html__('Manage 2FA', 'wordfence') : esc_html__('Activate 2FA', 'wordfence')); ?></a></p><?php endif ?>
<?php if ($viewerCanManage2fa): ?>
<?php if (!$userAllowed2fa): ?>
<p><strong><?php esc_html_e('Disabled', 'wordfence'); ?>:</strong> <?php esc_html_e('Two-factor authentication is not currently enabled for this account type. To enable it, visit the Wordfence 2FA Settings page.', 'wordfence'); ?> <a href="#"><?php esc_html_e('Learn More', 'wordfence'); ?></a></p>
<?php if ($lockedOut): ?>
<?php echo Model_View::create(
'common/reset-grace-period',
'gracePeriod' => $inGracePeriod
<?php elseif ($inGracePeriod && Controller_Users::shared()->has_revokable_grace_period($user)): ?>
<?php echo Model_View::create(
'common/revoke-grace-period',
<a href="<?php echo esc_url(is_multisite() ? network_admin_url('admin.php?page=WFLS#top#settings') : admin_url('admin.php?page=WFLS#top#settings')); ?>" class="button"><?php esc_html_e('Manage 2FA Settings', 'wordfence'); ?></a>
private function _is_woocommerce_login() {
if (!$this->has_woocommerce())
foreach (array('woocommerce-login-nonce', '_wpnonce') as $key) {
if (array_key_exists($key, $_REQUEST)) {
$nonceValue = $_REQUEST[$key];
return ( isset( $_POST['login'], $_POST['username'], $_POST['password'] ) && is_string($nonceValue) && wp_verify_nonce( $nonceValue, 'woocommerce-login' ) );
public function _authenticate($user, $username, $password) {
if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST && !Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_XMLRPC_ENABLED)) { //XML-RPC call and we're not enforcing 2FA on it
if (Controller_Whitelist::shared()->is_whitelisted(Model_Request::current()->ip())) { //Whitelisted, so we're not enforcing 2FA
$isLogin = !(defined('WORDFENCE_LS_AUTHENTICATION_CHECK') && WORDFENCE_LS_AUTHENTICATION_CHECK); //Checking for the purpose of prompting for 2FA, don't enforce it here
$isCombinedCheck = (defined('WORDFENCE_LS_CHECKING_COMBINED') && WORDFENCE_LS_CHECKING_COMBINED);
$combinedTwoFactor = false;
* If we don't have a valid $user at this point, it means the $username/$password combo is invalid. We'll check
* to see if the user has provided a combined password in the format `<password><code>`, populating $user from
if (!defined('WORDFENCE_LS_CHECKING_COMBINED') && (!isset($_POST['wfls-token']) || !is_string($_POST['wfls-token'])) && (!is_object($user) || !($user instanceof \WP_User))) {
//Compatibility with WF legacy 2FA
$combinedTOTPRegex = '/((?:[0-9]{3}\s*){2})$/i';
$combinedRecoveryRegex = '/((?:[a-f0-9]{4}\s*){4})$/i';
if ($this->legacy_2fa_active()) {
$combinedTOTPRegex = '/(?<! wf)((?:[0-9]{3}\s*){2})$/i';
$combinedRecoveryRegex = '/(?<! wf)((?:[a-f0-9]{4}\s*){4})$/i';
if (preg_match($combinedTOTPRegex, $password, $matches)) { //Possible TOTP code
if (strlen($password) > strlen($matches[1])) {
$revisedPassword = substr($password, 0, strlen($password) - strlen($matches[1]));
else if (preg_match($combinedRecoveryRegex, $password, $matches)) { //Possible recovery code
if (strlen($password) > strlen($matches[1])) {
$revisedPassword = substr($password, 0, strlen($password) - strlen($matches[1]));
if (isset($revisedPassword)) {
define('WORDFENCE_LS_CHECKING_COMBINED', true); //Avoid recursing into this block
if (!defined('WORDFENCE_LS_AUTHENTICATION_CHECK')) { define('WORDFENCE_LS_AUTHENTICATION_CHECK', true); }
$revisedUser = wp_authenticate($username, $revisedPassword);
if (is_object($revisedUser) && ($revisedUser instanceof \WP_User) && Controller_TOTP::shared()->validate_2fa($revisedUser, $code, $isLogin)) {
define('WORDFENCE_LS_COMBINED_IS_VALID', true); //This will cause the front-end to skip the 2FA prompt
$combinedTwoFactor = true;
* It will be enforced so long as:
* 1. It's enabled and keys are set.
* 2. This is not an XML-RPC request. An XML-RPC request is de facto an automated request, so a CAPTCHA makes
* 3. A filter does not override it. This is to allow plugins with REST endpoints that handle authentication
* themselves to opt out of the requirement.
* 4. The user is not providing a combined credentials + 2FA authentication login request.
* 5. The request is not a WooCommerce login while WC integration is disabled
if (!$combinedTwoFactor && !$isCombinedCheck && !empty($username) && (!$this->_is_woocommerce_login() || Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_ENABLE_WOOCOMMERCE_INTEGRATION))) { //Login attempt, not just a wp-login.php page load
$requireCAPTCHA = Controller_CAPTCHA::shared()->is_captcha_required();
$performVerification = false;
$token = Controller_CAPTCHA::shared()->get_token();
if ($requireCAPTCHA && empty($token) && !Controller_CAPTCHA::shared()->test_mode()) { //No CAPTCHA token means forced additional verification (if neither 2FA nor test mode are active)
$performVerification = true;
if (is_object($user) && $user instanceof \WP_User && $this->validate_email_verification_token($user)) { //Skip the CAPTCHA check if the email address was verified
$performVerification = false;
$identifier = sprintf('wfls-captcha-%d', $user->ID);
$tokenBucket = new Model_TokenBucket('rate:' . $identifier, 3, 1 / (WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES * Model_TokenBucket::MINUTE)); //Maximum of three requests, refilling at a rate of one per token expiration period
if ($requireCAPTCHA && !$performVerification) {
if (is_object($user) && $user instanceof \WP_User) {
$score = Controller_Users::shared()->cached_captcha_score($token, $user, $expired);
return new \WP_Error('wfls_captcha_expired', wp_kses(__('<strong>CAPTCHA EXPIRED</strong>: The CAPTCHA verification for this login attempt has expired. Please try again.', 'wordfence'), array('strong'=>array())));
$score = Controller_CAPTCHA::shared()->score($token);
if ($score !== false && is_object($user) && $user instanceof \WP_User) {
Controller_Users::shared()->cache_captcha_score($token, $score, $user);
Controller_Users::shared()->record_captcha_score($user, $score);
if ($score === false && !Controller_CAPTCHA::shared()->test_mode()) { //An invalid token will require additional verification (if test mode is not active)
$performVerification = true;
if ($performVerification || !Controller_CAPTCHA::shared()->is_human($score)) {
if (is_object($user) && $user instanceof \WP_User) {
$identifier = sprintf('wfls-captcha-%d', $user->ID);
$tokenBucket = new Model_TokenBucket('rate:' . $identifier, 3, 1 / (WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES * Model_TokenBucket::MINUTE)); //Maximum of three requests, refilling at a rate of one per token expiration period
if ($tokenBucket->consume(1)) {
if ($this->has_woocommerce() && array_key_exists('woocommerce-login-nonce', $_POST)) {
$loginUrl = get_permalink(get_option('woocommerce_myaccount_page_id'));
$loginUrl = wp_login_url();
$verificationUrl = add_query_arg(
'wfls-email-verification' => rawurlencode(Controller_Users::shared()->generate_verification_token($user))
$view = new Model_View('email/login-verification', array(
'siteName' => get_bloginfo('name', 'raw'),
'verificationURL' => $verificationUrl,
'ip' => Model_Request::current()->ip(),
'canEnable2FA' => Controller_Users::shared()->can_activate_2fa($user),
wp_mail($user->user_email, __('Login Verification Required', 'wordfence'), $view->render(), "Content-Type: text/html");
Utility_Sleep::sleep(Model_Crypto::random_int(0, 2000) / 1000);
return new \WP_Error('wfls_captcha_verify', wp_kses(__('<strong>VERIFICATION REQUIRED</strong>: Additional verification is required for login. If there is a valid account for the provided login credentials, please check the email address associated with it for a verification link to continue logging in.', 'wordfence'), array('strong' => array())));
if (!$combinedTwoFactor) {
if ($isLogin && $user instanceof \WP_User) {
if (Controller_Users::shared()->has_2fa_active($user)) {
if (Controller_Users::shared()->has_remembered_2fa($user)) {
elseif (array_key_exists('wfls-token', $_POST)) {
if (is_string($_POST['wfls-token']) && Controller_TOTP::shared()->validate_2fa($user, $_POST['wfls-token'])) {
return new \WP_Error('wfls_twofactor_failed', wp_kses(__('<strong>CODE INVALID</strong>: The 2FA code provided is either expired or invalid. Please try again.', 'wordfence'), array('strong'=>array())));
$in2faGracePeriod = false;
if (Controller_Users::shared()->has_2fa_active($user)) {
$legacy2FAActive = Controller_WordfenceLS::shared()->legacy_2fa_active();
return new \WP_Error('wfls_twofactor_required', wp_kses(__('<strong>CODE REQUIRED</strong>: Please enter your 2FA code immediately after your password in the same field.', 'wordfence'), array('strong'=>array())));
return new \WP_Error('wfls_twofactor_required', wp_kses(__('<strong>CODE REQUIRED</strong>: Please provide your 2FA code when prompted.', 'wordfence'), array('strong'=>array())));
else if (Controller_Users::shared()->requires_2fa($user, $in2faGracePeriod, $time2faRequired)) {
return new \WP_Error('wfls_twofactor_blocked', wp_kses(__('<strong>LOGIN BLOCKED</strong>: 2FA is required to be active on your account. Please contact the site administrator.', 'wordfence'), array('strong'=>array())));
else if ($in2faGracePeriod) {
Controller_Notices::shared()->add_notice(Model_Notice::SEVERITY_CRITICAL, new Model_HTML(wp_kses(sprintf(__('You do not currently have two-factor authentication active on your account, which will be required beginning %s. <a href="%s">Configure 2FA</a>', 'wordfence'), Controller_Time::format_local_time('F j, Y g:i A', $time2faRequired), esc_url((is_multisite() && is_super_admin($user->ID)) ? network_admin_url('admin.php?page=WFLS') : admin_url('admin.php?page=WFLS'))), array('a'=>array('href'=>array())))), 'wfls-will-be-required', $user);
public function _set_logged_in_cookie($logged_in_cookie, $expire, $expiration, $user_id) {
$user = new \WP_User($user_id);
if (Controller_Users::shared()->has_2fa_active($user) && isset($_POST['wfls-remember-device']) && $_POST['wfls-remember-device']) {
Controller_Users::shared()->remember_2fa($user);
delete_user_meta($user_id, 'wfls-captcha-nonce');
public function _record_login($user_login/*, $user -- we'd like to use the second parameter instead, but too many plugins call this hook and only provide one of the two required parameters*/) {
$user = get_user_by('login', $user_login);
if (is_object($user) && $user instanceof \WP_User && $user->exists()) {
update_user_meta($user->ID, 'wfls-last-login', Controller_Time::time());
public function _register_post($sanitized_user_login, $user_email, $errors) {
if (!empty($sanitized_user_login)) {
$captchaResult = $this->process_registration_captcha_with_hooks();
if ($captchaResult !== true) {
$errors->add($captchaResult['category'], $captchaResult['message']);
private function validate_email_verification_token($user = null, &$token = null) {
$token = isset($_REQUEST['wfls-email-verification']) ? $_REQUEST['wfls-email-verification'] : null;
return is_string($token) && Controller_Users::shared()->validate_verification_token($token, $user);
* @param \WP_Error $errors
* @param string $redirect_to
public function _wp_login_errors($errors, $redirect_to) {
$has_errors = (method_exists($errors, 'has_errors') ? $errors->has_errors() : !empty($errors->errors)); //has_errors was added in WP 5.1
$emailVerificationTokenValid = $this->validate_email_verification_token();
if (!$has_errors && $emailVerificationTokenValid !== null) {
if ($emailVerificationTokenValid) {
$errors->add('wfls_email_verified', esc_html__('Email verification succeeded. Please continue logging in.', 'wordfence'), 'message');
$errors->add('wfls_email_not_verified', esc_html__('Email verification invalid or expired. Please try again.', 'wordfence'), 'message');
public function legacy_2fa_active() {
$wfLegacy2FAActive = false;
if (class_exists('wfConfig') && \wfConfig::get('isPaid')) {
$twoFactorUsers = \wfConfig::get_ser('twoFactorUsers', array());
if (is_array($twoFactorUsers) && count($twoFactorUsers) > 0) {
foreach ($twoFactorUsers as $t) {
if ($t[3] == 'activated') {
$testUser = get_user_by('ID', $t[0]);
if (is_object($testUser) && $testUser instanceof \WP_User && \wfUtils::isAdmin($testUser)) {
$wfLegacy2FAActive = true;
if ($wfLegacy2FAActive && class_exists('wfCredentialsController') && method_exists('wfCredentialsController', 'useLegacy2FA') && !\wfCredentialsController::useLegacy2FA()) {
$wfLegacy2FAActive = false;
return $wfLegacy2FAActive;
public function _admin_menu() {
$user = wp_get_current_user();
if (Controller_Notices::shared()->has_notice($user)) {
Controller_Users::shared()->requires_2fa($user, $gracePeriod);
Controller_Notices::shared()->remove_notice(false, 'wfls-will-be-required', $user);
Controller_Notices::shared()->enqueue_notices();
$useSubmenu = WORDFENCE_LS_FROM_CORE && current_user_can('activate_plugins');
if (is_multisite() && !is_network_admin()) {
add_submenu_page('Wordfence', __('Login Security', 'wordfence'), __('Login Security', 'wordfence'), Controller_Permissions::CAP_ACTIVATE_2FA_SELF, 'WFLS', array($this, '_menu'));
add_menu_page(__('Login Security', 'wordfence'), __('Login Security', 'wordfence'), Controller_Permissions::CAP_ACTIVATE_2FA_SELF, 'WFLS', array($this, '_menu'), Model_Asset::img('menu.svg'));
public function _menu() {
$user = wp_get_current_user();
if (Controller_Permissions::shared()->can_manage_settings($user)) {
if (user_can($user, Controller_Permissions::CAP_ACTIVATE_2FA_OTHERS)) {
if (isset($_GET['user'])) {
$user = new \WP_User((int) $_GET['user']);
$user = wp_get_current_user();
if (isset($_GET['role']) && $canEditUsers) {
$roleKey = $_GET['role'];
$roles = new \WP_Roles();
$role = $roles->get_role($roleKey);
$roleTitle = $roleKey === 'super-admin' ? __('Super Administrator', 'wordfence') : $roles->role_names[$roleKey];
$requiredAt = Controller_Settings::shared()->get_required_2fa_role_activation_time($roleKey);
'title' => __('Grace Period', 'wordfence'),
'title' => __('Locked Out', 'wordfence'),
foreach ($states as $key => $state) {
$page = isset($_GET[$pageKey]) ? max((int) $_GET[$pageKey], 1) : 1;
$title = $state['title'];
if ($requiredAt === false)
$users = Controller_Users::shared()->get_inactive_2fa_users($roleKey, $state['gracePeriod'], $page, self::USERS_PER_PAGE, $lastPage);
'tab' => new Model_Tab($key, $key, $title, $title),
'title' => new Model_Title($key, sprintf(__('Users without 2FA active (%s)', 'wordfence'), $title) . ' - ' . $roleTitle),
'content' => new Model_View('page/role', array(
'roleTitle' => $roleTitle,
'requiredAt' => $requiredAt,
'tab' => new Model_Tab('manage', 'manage', __('Two-Factor Authentication', 'wordfence'), __('Two-Factor Authentication', 'wordfence')),
'title' => new Model_Title('manage', __('Two-Factor Authentication', 'wordfence'), Controller_Support::supportURL(Controller_Support::ITEM_MODULE_LOGIN_SECURITY_2FA), new Model_HTML(wp_kses(__('Learn more<span class="wfls-hidden-xs"> about Two-Factor Authentication</span>', 'wordfence'), array('span'=>array('class'=>array()))))),
'content' => new Model_View('page/manage', array(
'canEditUsers' => $canEditUsers,
'tab' => new Model_Tab('settings', 'settings', __('Settings', 'wordfence'), __('Settings', 'wordfence')),
'title' => new Model_Title('settings', __('Login Security Settings', 'wordfence'), Controller_Support::supportURL(Controller_Support::ITEM_MODULE_LOGIN_SECURITY), new Model_HTML(wp_kses(__('Learn more<span class="wfls-hidden-xs"> about Login Security</span>', 'wordfence'), array('span'=>array('class'=>array()))))),
'content' => new Model_View('page/settings', array(
'hasWoocommerce' => $this->has_woocommerce()
$view = new Model_View('page/page', array(
private function process_registration_captcha() {
if (Controller_Whitelist::shared()->is_whitelisted(Model_Request::current()->ip())) { //Whitelisted, so we're not enforcing 2FA
$captchaController = Controller_CAPTCHA::shared();
$requireCaptcha = $captchaController->is_captcha_required();
$token = $captchaController->get_token();
if ($token === null && !$captchaController->test_mode()) {
'message' => wp_kses(__('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: This site requires a security token created when the page loads for all registration attempts. Please ensure JavaScript is enabled and try again.', 'wordfence'), array('strong'=>array())),
'category' => 'wfls_captcha_required'
$score = $captchaController->score($token);
if ($score === false && !$captchaController->test_mode()) {
'message' => wp_kses(__('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: The security token for the login attempt was invalid or expired. Please reload the page and try again.', 'wordfence'), array('strong'=>array())),
'category' => 'wfls_captcha_required'
Controller_Users::shared()->record_captcha_score(null, $score);
if (!$captchaController->is_human($score)) {
$encryptedIP = Model_Symmetric::encrypt(Model_Request::current()->ip());
$encryptedScore = Model_Symmetric::encrypt($score);
'category' => 'wfls_registration_blocked'
if ($encryptedIP && $encryptedScore && filter_var(get_site_option('admin_email'), FILTER_VALIDATE_EMAIL)) {
$jwt = new Model_JWT(array('ip' => $encryptedIP, 'score' => $encryptedScore), Controller_Time::time() + 600);
$result['message'] = wp_kses(sprintf(__('<strong>REGISTRATION BLOCKED</strong>: The registration request was blocked because it was flagged as spam. Please try again or <a href="#" class="wfls-registration-captcha-contact" data-token="%s">contact the site owner</a> for help.', 'wordfence'), esc_attr((string)$jwt)), array('strong'=>array(), 'a'=>array('href'=>array(), 'class'=>array(), 'data-token'=>array())));
$result['message'] = wp_kses(__('<strong>REGISTRATION BLOCKED</strong>: The registration request was blocked because it was flagged as spam. Please try again or contact the site owner for help.', 'wordfence'), array('strong'=>array()));
* @param int $endpointType the type of endpoint being processed
* The default value of 1 corresponds to a regular login
* @see wordfence::wfsnEndpointType()
private function process_registration_captcha_with_hooks($endpointType = 1) {
$result = $this->process_registration_captcha();
if ($result['category'] === 'wfls_registration_blocked') {
* Fires just prior to blocking user registration due to a failed CAPTCHA. After firing this action hook
* the registration attempt is blocked.
* @param int $source The source code of the block.
do_action('wfls_registration_blocked', $endpointType);
* Filters the message to show if registration is blocked due to a captcha rejection.
* @param string $message The message to display, HTML allowed.
$result['message'] = apply_filters('wfls_registration_blocked_message', $result['message']);
private function disable_woocommerce_registration($message) {