: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'nonce' => wp_create_nonce('wp-ajax'),
public static function ajax_testAjax_callback(){
public static function ajax_doScan_callback(){
@ignore_user_abort(true);
self::$wordfence_wp_version = false;
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
//This is messy, but not sure of a better way to do this without guaranteeing we get $wp_version
require(ABSPATH . 'wp-includes/version.php'); /** @var string $wp_version */
self::$wordfence_wp_version = $wp_version;
require_once(dirname(__FILE__) . '/wfScan.php');
public static function ajax_lh_callback(){
self::getLog()->canLogHit = false;
$UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
if (wfCrawl::isCrawler($UA) || wfCrawl::isGoogleCrawler()) {
header('Content-type: text/javascript');
header("Connection: close");
header("Content-Length: 0");
header("X-Robots-Tag: noindex");
wfLog::cacheHumanRequester(wfUtils::getIP(), $UA);
if (!$isCrawler && array_key_exists('hid', $_GET)) {
$hid = wfUtils::decrypt($hid);
if (!is_string($hid) || !preg_match('/^\d+$/', $hid)) { exit(); }
$table_wfHits = wfDB::networkTable('wfHits');
$db->queryWrite("update {$table_wfHits} set jsRun=1 where id=%d", $hid);
public static function ajaxReceiver(){
if(! wfUtils::isAdmin()){
wfUtils::send_json(array('errorMsg' => __("You appear to have logged out or you are not an admin. Please sign-out and sign-in again.", 'wordfence')));
$func = (isset($_POST['action']) && $_POST['action']) ? $_POST['action'] : $_GET['action'];
$nonce = (isset($_POST['nonce']) && $_POST['nonce']) ? $_POST['nonce'] : $_GET['nonce'];
if(! wp_verify_nonce($nonce, 'wp-ajax')){
wfUtils::send_json(array('errorMsg' => __("Your browser sent an invalid security token to Wordfence. Please try reloading this page or signing out and in again.", 'wordfence'), 'tokenInvalid' => 1));
//func is e.g. wordfence_ticker so need to munge it
$func = str_replace('wordfence_', '', $func);
$returnArr = call_user_func('wordfence::ajax_' . $func . '_callback');
if($returnArr === false){
$returnArr = array('errorMsg' => __("Wordfence encountered an internal error executing that request.", 'wordfence'));
if(! is_array($returnArr)){
error_log("Function " . wp_kses($func, array()) . " did not return an array and did not generate an error.");
if(isset($returnArr['nonce'])){
error_log("Wordfence ajax function return an array with 'nonce' already set. This could be a bug.");
$returnArr['nonce'] = wp_create_nonce('wp-ajax');
wfUtils::send_json($returnArr);
public static function ajax_remoteVerifySwitchTo2FANew_callback() {
$payload = wfUtils::decodeJWT(wfConfig::get('new2FAMigrationNonce'));
wfUtils::send_json(new stdClass()); //Ensures an object response
$package = wfCrypt::noc1_encrypt($payload);
wfUtils::send_json($package);
public static function ajax_switchTo2FANew_callback() {
$migrate = (isset($_POST['migrate']) && wfUtils::truthyToBoolean($_POST['migrate']));
$twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
if ($migrate && is_array($twoFactorUsers) && !empty($twoFactorUsers)) {
$authenticatorActive = array();
foreach ($twoFactorUsers as &$t) {
if ($t[3] == 'activated') {
$user = new WP_User($t[0]);
if ($user instanceof WP_User && $user->exists()) {
if ((!isset($t[5]) || $t[5] != 'authenticator')) {
$smsActive[] = $user->user_login;
$authenticatorActive[] = $t[6];
if (!empty($smsActive)) {
return array('ok' => 0, 'smsActive' => $smsActive);
$nonce = bin2hex(wfWAFUtils::random_bytes(32));
wfConfig::set('new2FAMigrationNonce', wfUtils::generateJWT(array('nonce' => $nonce), 90));
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
$response = $api->call('twoFactorTOTP_migrate', array(), array('migrateids' => json_encode($authenticatorActive), 'nonce' => $nonce, 'verifyurl' => add_query_arg(array('action' => 'wordfence_remoteVerifySwitchTo2FANew'), admin_url('admin-ajax.php'))));
* A successful response will be in the format
* <id>: true, ... if applicable
* "recovery": <recovery keys>,
* "ctime": <timestamp created>,
* "vtime": <timestamp of last verified TOTP code>
if (!is_array($response) || !isset($response['records']) || !is_array($response['records'])) {
return array('ok' => 0, 'fail' => 1);
$secrets = $response['records'];
if (!isset($secrets['totp']) || !is_array($secrets['totp'])) {
return array('ok' => 0, 'fail' => 2);
foreach ($twoFactorUsers as &$t) {
if ($t[3] == 'activated') {
$user = new WP_User($t[0]);
if ($user instanceof WP_User && $user->exists()) {
if ((!isset($t[5]) || $t[5] != 'authenticator')) {
if (isset($secrets['totp'][$t[6]])) {
$import[$user->ID] = $secrets['totp'][$t[6]];
$import[$user->ID]['type'] = 'authenticator';
$imported = WFLSPHP52Compatability::import_2fa($import);
wordfence::status(4, 'error', sprintf(/* translators: Error message. */ __('2FA Migration Error: %s', 'wordfence'), $e->getMessage()));
return array('ok' => 0, 'fail' => 1);
wfConfig::remove('new2FAMigrationNonce');
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, true);
return array('ok' => 1, 'total' => $total, 'imported' => $imported);
//No legacy 2FA active, just set the option.
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, true);
public static function ajax_switchTo2FAOld_callback() {
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, false);
public static function validateProfileUpdate($errors, $update, $userData){
wordfence::validatePassword($errors, $userData);
public static function validatePassword($errors, $userData) {
$password = (isset($_POST['pass1']) && trim($_POST['pass1'])) ? $_POST['pass1'] : false;
$user_id = isset($userData->ID) ? $userData->ID : false;
$username = isset($_POST["user_login"]) ? $_POST["user_login"] : $userData->user_login;
if ($password == false) { return $errors; }
if ($errors->get_error_data("pass")) { return $errors; }
$enforceStrongPasswds = false;
if (wfConfig::get('loginSec_strongPasswds_enabled')) {
if (wfConfig::get('loginSec_strongPasswds') == 'pubs') {
if (user_can($user_id, 'publish_posts')) {
$enforceStrongPasswds = true;
else if (wfConfig::get('loginSec_strongPasswds') == 'all') {
$enforceStrongPasswds = true;
if ($enforceStrongPasswds && !wordfence::isStrongPasswd($password, $username)) {
$errors->add('pass', __('<strong>ERROR</strong>: The password could not be changed. Please choose a stronger password and try again. A strong password will follow these guidelines: <ul class="wf-password-requirements">
<li>At least 12 characters</li>
<li>Uppercase and lowercase letters</li>
<li>At least one symbol</li>
<li>At least one number</li>
<li>Avoid common words or sequences of letters/numbers</li>
$twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
if (preg_match(self::$passwordCodePattern, $password) && is_array($twoFactorUsers) && count($twoFactorUsers) > 0) {
$errors->add('pass', __('Passwords containing a space followed by "wf" without quotes are not allowed.', 'wordfence'));
$enforceBreachedPasswds = false;
if (wfConfig::get('loginSec_breachPasswds_enabled')) {
if ($user_id !== false && wfConfig::get('loginSec_breachPasswds') == 'admins' && wfUtils::isAdmin($user_id)) {
$enforceBreachedPasswds = true;
else if ($user_id !== false && wfConfig::get('loginSec_breachPasswds') == 'pubs' && user_can($user_id, 'publish_posts')) {
$enforceBreachedPasswds = true;
if ($enforceBreachedPasswds && wfCredentialsController::isLeakedPassword($username, $password)) {
$errors->add('pass', sprintf(/* translators: Support URL. */ __('Please choose a different password. The password you are using exists on lists of passwords leaked in data breaches. Attackers use such lists to break into sites and install malicious code. <a href="%s">Learn More</a>', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_USING_BREACH_PASSWORD)));
else if ($user_id !== false) {
wfAdminNoticeQueue::removeAdminNotice(false, '2faBreachPassword', array($user_id));
wfAdminNoticeQueue::removeAdminNotice(false, 'previousIPBreachPassword', array($user_id));
wfCredentialsController::clearCachedCredentialStatus($userData);
public static function isStrongPasswd($passwd, $username ) {
$lowerPasswd = strtolower($passwd);
$passwdLength = strlen($lowerPasswd);
if ($lowerPasswd == strtolower( $username ) )
if (preg_match('/(?:password|passwd|mypass|wordpress)/i', $passwd))
if (preg_match('/(.)\1{2,}/', $lowerPasswd)) //Disallow any character repeated 3 or more times
* Check for ordered sequences of at least 4 characters for alphabetic sequences and 3 characters for other sequences, ignoring case
for ($i = 0; $i < $passwdLength; $i++) {
$current = ord($lowerPasswd[$i]);
if (abs($current - $last) === 1) {
$alphabetic &= ctype_alpha($lowerPasswd[$i]);
if (++$sequenceLength > ($alphabetic ? 3 : 2))
foreach ($characterTypes as $type) {
if (!preg_match($type, $passwd))
public static function lostPasswordPost($errors = null, $user = null) {
if ($request = self::getLog()->getCurrentRequest()) {
$request->action = 'lostPassword';
if (wfBlock::isWhitelisted($IP)) {
$lockout = wfBlock::lockoutForIP(wfUtils::getIP());
if ($lockout !== false) {
$customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
require(dirname(__FILE__) . '/wfLockedOut.php');
if (empty($_POST['user_login'])) { return; }
$user_login = $_POST['user_login'];
if (is_array($user_login)) { $user_login = wfUtils::array_first($user_login); }
$user_login = trim($user_login);
$user = get_user_by('login', $user_login);
$user = get_user_by('email', $user_login);
if ($user === false && wfConfig::get('loginSec_maskLoginErrors')) {
if (self::hasWoocommerce() && isset($_POST['wc_reset_password'], $_POST['user_login'])) {
$redirectUrl = add_query_arg('reset-link-sent', 'true', wc_get_account_endpoint_url('lost-password'));
$redirectUrl = !empty($_REQUEST['redirect_to']) ? $_REQUEST['redirect_to'] : 'wp-login.php?checkemail=confirm';
wp_safe_redirect($redirectUrl);
$alertCallback = array(new wfLostPasswdFormAlert($user, wfUtils::getIP()), 'send');
do_action('wordfence_security_event', 'lostPasswdForm', array(
'email' => $user->user_email,
'ip' => wfUtils::getIP(),
// do not count password reset attempts if there is a user logged in with the edit_users capability
// because they're probably using the "send password reset" feature in the WP admin and therefore we shouldn't
if(wfConfig::get('loginSecurityEnabled') && !current_user_can( 'edit_users' ) ){
$tKey = self::getForgotPasswordFailureCountTransient($IP);
$forgotAttempts = get_transient($tKey);
if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
self::lockOutIP($IP, sprintf(
/* translators: 1. Password reset limit (number). 2. WordPress username. */
__('Exceeded the maximum number of tries to recover their password which is set at: %1$s. The last username or email they entered before getting locked out was: \'%2$s\'', 'wordfence'),
wfConfig::get('loginSec_maxForgotPasswd'),
$customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
require(dirname(__FILE__) . '/wfLockedOut.php');
set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
public static function lockOutIP($IP, $reason) {
wfBlock::createLockout($reason, $IP, wfBlock::lockoutDuration(), time(), time(), 1);
self::getLog()->tagRequestForLockout($reason);
$alertCallback = array(new wfLoginLockoutAlert($IP, $reason), 'send');
do_action('wordfence_security_event', 'loginLockout', array(
'duration' => wfBlock::lockoutDuration(),
public static function getLoginFailureCountTransient($IP) {
return 'wflginfl_' . bin2hex(wfUtils::inet_pton($IP));
public static function getForgotPasswordFailureCountTransient($IP) {
return 'wffgt_' . bin2hex(wfUtils::inet_pton($IP));
public static function clearLockoutCounters($IP) {
delete_transient(self::getLoginFailureCountTransient($IP));
delete_transient(self::getForgotPasswordFailureCountTransient($IP));
public static function veryFirstAction() {
$wfFunc = isset($_GET['_wfsf']) ? @$_GET['_wfsf'] : false;
if ($wfFunc == 'unlockEmail') {
if (isset($_POST['nonce']) && is_string($_POST['nonce'])) {
$nonceValid = wp_verify_nonce($_POST['nonce'], 'wf-form');
if (!$nonceValid && method_exists(wfWAF::getInstance(), 'createNonce')) {
$nonceValid = wfWAF::getInstance()->verifyNonce($_POST['nonce'], 'wf-form');
die(__("Sorry but your browser sent an invalid security token when trying to use this form.", 'wordfence'));
$numTries = get_transient('wordfenceUnlockTries');
printf("<html><body><h1>%s</h1><p>%s</p></body></html>",
esc_html__('Please wait 3 minutes and try again', 'wordfence'),
esc_html__('You have used this form too much. Please wait 3 minutes and try again.', 'wordfence')
if(! $numTries){ $numTries = 1; } else { $numTries = $numTries + 1; }
set_transient('wordfenceUnlockTries', $numTries, 180);
$email = trim(@$_POST['email']);
$ws = $wpdb->get_results($wpdb->prepare("SELECT ID, user_login FROM $wpdb->users WHERE user_email = %s", $email));
$userDat = get_userdata($user->ID);
if(wfUtils::isAdmin($userDat)){
if($email == $userDat->user_email){
foreach(wfConfig::getAlertEmails() as $alertEmail){
if($alertEmail == $email){
$key = wfUtils::bigRandomHex();
set_transient('wfunlock_' . $key, $IP, 1800);
$content = wfUtils::tmpl('email_unlockRequest.php', array(
'siteName' => get_bloginfo('name', 'raw'),
'siteURL' => wfUtils::getSiteBaseURL(),
'unlockHref' => wfUtils::getSiteBaseURL() . '?_wfsf=unlockAccess&key=' . $key,
wp_mail($email, __("Unlock email requested", 'wordfence'), $content, "Content-Type: text/html");
echo "<html><body><h1>" . esc_html__('Your request was received', 'wordfence') . "</h1><p>" .
esc_html(sprintf(/* translators: Email address. */ __("We received a request to email \"%s\" instructions to unlock their access. If that is the email address of a site administrator or someone on the Wordfence alert list, they have been emailed instructions on how to regain access to this system. The instructions we sent will expire 30 minutes from now.", 'wordfence'), wp_kses($email, array())))
} else if($wfFunc == 'unlockAccess'){
if (!preg_match('/^(?:(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9](?::|$)){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))$/i', get_transient('wfunlock_' . $_GET['key']))) {
_e("Invalid key provided for authentication.", 'wordfence');
if($_GET['func'] == 'unlockMyIP'){
wfBlock::unblockIP(wfUtils::getIP());
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
} else if($_GET['func'] == 'unlockAllIPs'){
wordfence::status(1, 'info', __("Request received via unlock email link to unblock all IPs.", 'wordfence'));
wfBlock::removeAllIPBlocks();
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
} else if($_GET['func'] == 'disableRules'){
wfConfig::set('firewallEnabled', 0);
wfConfig::set('loginSecurityEnabled', 0);
wordfence::status(1, 'info', __("Request received via unlock email link to unblock all IPs via disabling firewall rules.", 'wordfence'));
wfBlock::removeAllIPBlocks();
wfBlock::removeAllCountryBlocks();
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
_e("Invalid function specified. Please check the link we emailed you and make sure it was not cut-off by your email reader.", 'wordfence');
else if ($wfFunc == 'detectProxy') {
if (wfUtils::processDetectProxyCallback()) {
self::getLog()->getCurrentRequest()->action = 'scan:detectproxy'; //Exempt a valid callback from live traffic