public static function authenticateFilter($authUser, $username, $passwd) { wfConfig::inc('totalLoginHits'); //The total hits to wp-login.php including logins, logouts and just hits. $IP = wfUtils::getIP(); $secEnabled = wfConfig::get('loginSecurityEnabled'); $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array()); $userDat = isset($_POST['wordfence_userDat']) ? $_POST['wordfence_userDat'] : false; $checkTwoFactor = $secEnabled && !self::getLog()->isWhitelisted($IP) && wfConfig::get('isPaid') && isset($twoFactorUsers) && is_array($twoFactorUsers) && sizeof($twoFactorUsers) > 0 && is_object($userDat) && get_class($userDat) == 'WP_User'; if ($checkTwoFactor) { $twoFactorRecord = false; $hasActivatedTwoFactorUser = false; foreach ($twoFactorUsers as &$t) { if ($t[3] == 'activated') { $userID = $t[0]; $testUser = get_user_by('ID', $userID); if (is_object($testUser) && wfUtils::isAdmin($testUser)) { $hasActivatedTwoFactorUser = true; } if ($userID == $userDat->ID) { $twoFactorRecord =& $t; } } } if (isset($_POST['wordfence_authFactor']) && $_POST['wordfence_authFactor'] && $twoFactorRecord) { //User authenticated with name and password, 2FA code ready to check $userID = $userDat->ID; if (get_class($authUser) == 'WP_User' && $authUser->ID == $userID) { //Do nothing. This is the code path the old method of including the code in the password field will take -- since we already have a valid $authUser, skip the nonce verification portion } else { if (isset($_POST['wordfence_twoFactorNonce'])) { $twoFactorNonce = preg_replace('/[^a-f0-9]/i', '', $_POST['wordfence_twoFactorNonce']); if (!self::verifyTwoFactorIntermediateValues($userID, $twoFactorNonce)) { self::$authError = new WP_Error('twofactor_required', __('<strong>VERIFICATION FAILED</strong>: Two factor authentication verification failed. Please try again.')); return self::processBruteForceAttempt(self::$authError, $username, $passwd); } } else { //Code path for old method, invalid password the second time self::$authError = $authUser; if (is_wp_error(self::$authError) && (self::$authError->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'invalid_email' || self::$authError->get_error_code() == 'incorrect_password' || $authUser->get_error_code() == 'authentication_failed') && wfConfig::get('loginSec_maskLoginErrors')) { self::$authError = new WP_Error('incorrect_password', sprintf(__('<strong>ERROR</strong>: The username or password you entered is incorrect. <a href="%2$s" title="Password Lost and Found">Lost your password</a>?'), $username, wp_lostpassword_url())); } return self::processBruteForceAttempt(self::$authError, $username, $passwd); } } if (isset($twoFactorRecord[5])) { //New method TOTP $mode = $twoFactorRecord[5]; $code = preg_replace('/[^a-f0-9]/i', '', $_POST['wordfence_authFactor']); $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); try { $codeResult = $api->call('twoFactorTOTP_verify', array(), array('totpid' => $twoFactorRecord[6], 'code' => $code, 'mode' => $mode)); if (isset($codeResult['notPaid']) && $codeResult['notPaid']) { //No longer a paid key, let them sign in without two factor } else { if (isset($codeResult['ok']) && $codeResult['ok']) { //Everything's good, let the sign in continue } else { if (get_class($authUser) == 'WP_User' && $authUser->ID == $userID) { //Using the old method of appending the code to the password if ($mode == 'authenticator') { self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: Please sign in again and add a space, the letters <code>wf</code>, and the code from your authenticator app to the end of your password (e.g., <code>wf123456</code>).')); } else { self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: Please sign in again and add a space, the letters <code>wf</code>, and the code sent to your phone to the end of your password (e.g., <code>wf123456</code>).')); } } else { $loginNonce = wfWAFUtils::random_bytes(20); if ($loginNonce === false) { //Should never happen but is technically possible self::$authError = new WP_Error('twofactor_required', __('<strong>AUTHENTICATION FAILURE</strong>: A temporary failure was encountered while trying to log in. Please try again.')); return self::$authError; } $loginNonce = bin2hex($loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonce', $loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonceTime', time()); if ($mode == 'authenticator') { self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: You need to enter the code generated by your authenticator app. The code should be a six digit number (e.g., 123456).') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); } else { self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: You need to enter the code generated sent to your phone. The code should be a six digit number (e.g., 123456).') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); } } return self::processBruteForceAttempt(self::$authError, $username, $passwd); } } } catch (Exception $e) { if (self::isDebugOn()) { error_log('TOTP validation error: ' . $e->getMessage()); } } // Couldn't connect to noc1, let them sign in since the password was correct. } else { //Old method phone authentication $authFactor = $_POST['wordfence_authFactor']; if (strlen($authFactor) == 4) { $authFactor = 'wf' . $authFactor; } if ($authFactor == $twoFactorRecord[2] && $twoFactorRecord[4] > time()) { // Set this 2FA code to expire in 30 seconds (for other plugins hooking into the auth process) $twoFactorRecord[4] = time() + 30; wfConfig::set_ser('twoFactorUsers', $twoFactorUsers); } else { if ($authFactor == $twoFactorRecord[2]) { $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); try { $codeResult = $api->call('twoFactor_verification', array(), array('phone' => $twoFactorRecord[1])); if (isset($codeResult['notPaid']) && $codeResult['notPaid']) { //No longer a paid key, let them sign in without two factor } else { if (isset($codeResult['ok']) && $codeResult['ok']) { $twoFactorRecord[2] = $codeResult['code']; $twoFactorRecord[4] = time() + 1800; //30 minutes until code expires wfConfig::set_ser('twoFactorUsers', $twoFactorUsers); //save the code the user needs to enter and return an error. $loginNonce = wfWAFUtils::random_bytes(20); if ($loginNonce === false) { //Should never happen but is technically possible self::$authError = new WP_Error('twofactor_required', __('<strong>AUTHENTICATION FAILURE</strong>: A temporary failure was encountered while trying to log in. Please try again.')); return self::$authError; } $loginNonce = bin2hex($loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonce', $loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonceTime', time()); self::$authError = new WP_Error('twofactor_required', __('<strong>CODE EXPIRED. CHECK YOUR PHONE:</strong> The code you entered has expired. Codes are only valid for 30 minutes for security reasons. We have sent you a new code. Please sign in using your username, password, and the new code we sent you.') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); return self::$authError; } } //else: No new code was received. Let them sign in with the expired code. } catch (Exception $e) { // Couldn't connect to noc1, let them sign in since the password was correct. } } else { //Bad code, so cancel the login and return an error to user. $loginNonce = wfWAFUtils::random_bytes(20); if ($loginNonce === false) { //Should never happen but is technically possible self::$authError = new WP_Error('twofactor_required', __('<strong>AUTHENTICATION FAILURE</strong>: A temporary failure was encountered while trying to log in. Please try again.')); return self::$authError; } $loginNonce = bin2hex($loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonce', $loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonceTime', time()); self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: You need to enter your password and the code we sent to your phone. The code should start with \'wf\' and should be four characters (e.g., wfAB12).') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); return self::processBruteForceAttempt(self::$authError, $username, $passwd); } } } delete_user_meta($userDat->ID, '_wf_twoFactorNonce'); delete_user_meta($userDat->ID, '_wf_twoFactorNonceTime'); $authUser = $userDat; //Log in as the user we saved in the wp_authenticate action } else { if (get_class($authUser) == 'WP_User') { //User authenticated with name and password, prompt for the 2FA code //Verify at least one administrator has 2FA enabled $requireAdminTwoFactor = $hasActivatedTwoFactorUser && wfConfig::get('loginSec_requireAdminTwoFactor'); if ($twoFactorRecord) { if ($twoFactorRecord[0] == $userDat->ID && $twoFactorRecord[3] == 'activated') { //Yup, enabled, so require the code $loginNonce = wfWAFUtils::random_bytes(20); if ($loginNonce === false) { //Should never happen but is technically possible, allow login $requireAdminTwoFactor = false; } else { $loginNonce = bin2hex($loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonce', $loginNonce); update_user_meta($userDat->ID, '_wf_twoFactorNonceTime', time()); if (isset($twoFactorRecord[5])) { //New method TOTP authentication if ($twoFactorRecord[5] == 'authenticator') { if (self::hasGDLimitLoginsMUPlugin() && function_exists('limit_login_get_address')) { $retries = get_option('limit_login_retries', array()); $ip = limit_login_get_address(); if (!is_array($retries)) { $retries = array(); } if (isset($retries[$ip]) && is_int($retries[$ip])) { $retries[$ip]--; } else { $retries[$ip] = 0; } update_option('limit_login_retries', $retries); } $allowSeparatePrompt = ini_get('output_buffering') > 0; if (wfConfig::get('loginSec_enableSeparateTwoFactor') && $allowSeparatePrompt) { self::$authError = new WP_Error('twofactor_required', __('<strong>CODE REQUIRED</strong>: Please check your authenticator app for the current code. Enter it below to sign in.') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); return self::$authError; } else { self::$authError = new WP_Error('twofactor_required', __('<strong>CODE REQUIRED</strong>: Please check your authenticator app for the current code. Please sign in again and add a space, the letters <code>wf</code>, and the code to the end of your password (e.g., <code>wf123456</code>).')); return self::$authError; } } else { //Phone TOTP $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); try { $codeResult = $api->call('twoFactorTOTP_sms', array(), array('totpid' => $twoFactorRecord[6])); if (isset($codeResult['notPaid']) && $codeResult['notPaid']) { $requireAdminTwoFactor = false; //Let them sign in without two factor if their API key has expired or they're not paid and for some reason they have this set up. } else { if (isset($codeResult['ok']) && $codeResult['ok']) { if (self::hasGDLimitLoginsMUPlugin() && function_exists('limit_login_get_address')) { $retries = get_option('limit_login_retries', array()); $ip = limit_login_get_address(); if (!is_array($retries)) { $retries = array(); } if (isset($retries[$ip]) && is_int($retries[$ip])) { $retries[$ip]--; } else { $retries[$ip] = 0; } update_option('limit_login_retries', $retries); } $allowSeparatePrompt = ini_get('output_buffering') > 0; if (wfConfig::get('loginSec_enableSeparateTwoFactor') && $allowSeparatePrompt) { self::$authError = new WP_Error('twofactor_required', __('<strong>CHECK YOUR PHONE</strong>: A code has been sent to your phone and will arrive within 30 seconds. Enter it below to sign in.') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); return self::$authError; } else { self::$authError = new WP_Error('twofactor_required', __('<strong>CHECK YOUR PHONE</strong>: A code has been sent to your phone and will arrive within 30 seconds. Please sign in again and add a space, the letters <code>wf</code>, and the code to the end of your password (e.g., <code>wf123456</code>).')); return self::$authError; } } else { //oops, our API returned an error. $requireAdminTwoFactor = false; //Let them sign in without two factor because the API is broken and we don't want to lock users out of their own systems. } } } catch (Exception $e) { if (self::isDebugOn()) { error_log('TOTP SMS error: ' . $e->getMessage()); } $requireAdminTwoFactor = false; // Couldn't connect to noc1, let them sign in since the password was correct. } } } else { //Old method phone authentication $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); try { $codeResult = $api->call('twoFactor_verification', array(), array('phone' => $twoFactorRecord[1])); if (isset($codeResult['notPaid']) && $codeResult['notPaid']) { $requireAdminTwoFactor = false; //Let them sign in without two factor if their API key has expired or they're not paid and for some reason they have this set up. } else { if (isset($codeResult['ok']) && $codeResult['ok']) { $twoFactorRecord[2] = $codeResult['code']; $twoFactorRecord[4] = time() + 1800; //30 minutes until code expires wfConfig::set_ser('twoFactorUsers', $twoFactorUsers); //save the code the user needs to enter and return an error. if (self::hasGDLimitLoginsMUPlugin() && function_exists('limit_login_get_address')) { $retries = get_option('limit_login_retries', array()); $ip = limit_login_get_address(); if (!is_array($retries)) { $retries = array(); } if (isset($retries[$ip]) && is_int($retries[$ip])) { $retries[$ip]--; } else { $retries[$ip] = 0; } update_option('limit_login_retries', $retries); } $allowSeparatePrompt = ini_get('output_buffering') > 0; if (wfConfig::get('loginSec_enableSeparateTwoFactor') && $allowSeparatePrompt) { self::$authError = new WP_Error('twofactor_required', __('<strong>CHECK YOUR PHONE</strong>: A code has been sent to your phone and will arrive within 30 seconds. Enter it below to sign in.') . '<!-- wftwofactornonce:' . $userDat->ID . '/' . $loginNonce . ' -->'); return self::$authError; } else { self::$authError = new WP_Error('twofactor_required', __('<strong>CHECK YOUR PHONE</strong>: A code has been sent to your phone and will arrive within 30 seconds. Please sign in again and add a space and the code to the end of your password (e.g., <code>wfABCD</code>).')); return self::$authError; } } else { //oops, our API returned an error. $requireAdminTwoFactor = false; //Let them sign in without two factor because the API is broken and we don't want to lock users out of their own systems. } } } catch (Exception $e) { $requireAdminTwoFactor = false; // Couldn't connect to noc1, let them sign in since the password was correct. } } //end: Old method phone authentication } } } if ($requireAdminTwoFactor && wfUtils::isAdmin($authUser)) { $username = $authUser->user_login; self::getLog()->logLogin('loginFailValidUsername', 1, $username); wordfence::alert("Admin Login Blocked", "A user with username \"{$username}\" who has administrator access tried to sign in to your WordPress site. Access was denied because all administrator accounts are required to have Cellphone Sign-in enabled but this account does not.", wfUtils::getIP()); self::$authError = new WP_Error('twofactor_disabled_required', __('<strong>Cellphone Sign-in Required</strong>: Cellphone Sign-in is required for all administrator accounts. Please contact the site administrator to enable it for your account.')); return self::$authError; } //User is not configured for two factor. Sign in without two factor. } } } //End: if ($checkTwoFactor) return self::processBruteForceAttempt($authUser, $username, $passwd); }
public static function requestDetectProxyCallback($timeout = 0.01, $blocking = false) { $nonce = bin2hex(wfWAFUtils::random_bytes(32)); $callback = self::getSiteBaseURL() . '?_wfsf=detectProxy'; wfConfig::set('detectProxyNonce', $nonce, wfConfig::DONT_AUTOLOAD); wfConfig::set('detectProxyRecommendation', '', wfConfig::DONT_AUTOLOAD); $payload = array('nonce' => $nonce, 'callback' => $callback); $siteurl = ''; if (function_exists('get_bloginfo')) { if (is_multisite()) { $siteurl = network_home_url(); $siteurl = rtrim($siteurl, '/'); //Because previously we used get_bloginfo and it returns http://example.com without a '/' char. } else { $siteurl = home_url(); } } wp_remote_post(WFWAF_API_URL_SEC . "?" . http_build_query(array('action' => 'detect_proxy', 'k' => wfConfig::get('apiKey'), 's' => $siteurl, 't' => microtime(true)), null, '&'), array('body' => json_encode($payload), 'headers' => array('Content-Type' => 'application/json'), 'timeout' => $timeout, 'blocking' => $blocking)); //Asynchronous so we don't care about a response at this point. }