: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Prevent WF auto-update if the user has enabled auto-update through the plugins page.
if (version_compare(wfUtils::getWPVersion(), '5.5-x', '>=')) {
$autoUpdatePlugins = get_site_option('auto_update_plugins');
if (is_array($autoUpdatePlugins) && in_array(WORDFENCE_BASENAME, $autoUpdatePlugins)) {
if (!wfConfig::get('other_bypassLitespeedNoabort', false) && getenv('noabort') != '1' && stristr($_SERVER['SERVER_SOFTWARE'], 'litespeed') !== false) {
$lastEmail = self::get('lastLiteSpdEmail', false);
if( (! $lastEmail) || (time() - (int)$lastEmail > (86400 * 30))){
self::set('lastLiteSpdEmail', time());
/* translators: Support URL. */
__("Wordfence Upgrade not run. Please modify your .htaccess", 'wordfence'), sprintf(__("To preserve the integrity of your website we are not running Wordfence auto-update.\n" .
"You are running the LiteSpeed web server which has been known to cause a problem with Wordfence auto-update.\n" .
"Please go to your website now and make a minor change to your .htaccess to fix this.\n" .
"You can find out how to make this change at:\n" .
"\nAlternatively you can disable auto-update on your website to stop receiving this message and upgrade Wordfence manually.\n", 'wordfence'), wfSupportController::supportURL(wfSupportController::ITEM_DASHBOARD_OPTION_LITESPEED_WARNING)),
$update_plugins = get_site_transient('update_plugins');
if ($update_plugins && is_array($update_plugins->response) && isset($update_plugins->response[WORDFENCE_BASENAME])) {
$status = $update_plugins->response[WORDFENCE_BASENAME];
if (is_object($status) && property_exists($status, 'new_version')) {
$runUpdate = (version_compare($status->new_version, WORDFENCE_VERSION) > 0);
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
$response = $api->call('should_auto_update', array(), array('currentVersion' => WORDFENCE_VERSION));
if (!(is_array($response) && isset($response['ok']) && wfUtils::truthyToBoolean($response['ok']))) {
wfConfig::inc('autoUpdateAttempts');
if (!$runUpdate && wfConfig::get('autoUpdateAttempts') < 7) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
require_once(ABSPATH . 'wp-admin/includes/misc.php');
/* We were creating show_message here so that WP did not write to STDOUT. This had the strange effect of throwing an error about redeclaring show_message function, but only when a crawler hit the site and triggered the cron job. Not a human. So we're now just require'ing misc.php which does generate output, but that's OK because it is a loopback cron request.
if(! function_exists('show_message')){
function show_message($msg = 'null'){}
if(! defined('FS_METHOD')){
define('FS_METHOD', 'direct'); //May be defined already and might not be 'direct' so this could cause problems. But we were getting reports of a warning that this is already defined, so this check added.
require_once(ABSPATH . 'wp-includes/update.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
if (!self::createLock('wfAutoUpdate')) {
$upgrader = new Plugin_Upgrader();
$upret = $upgrader->upgrade(WORDFENCE_BASENAME);
$cont = file_get_contents(WORDFENCE_FCPATH);
preg_match('/Version: (\d+\.\d+\.\d+)/', $cont, $matches);
$version = !empty($matches) ? $matches[1] : null;
$alertCallback = array(new wfAutoUpdatedAlert($version), 'send');
do_action('wordfence_security_event', 'autoUpdate', array(
wfConfig::set('autoUpdateAttempts', 0);
$output = @ob_get_contents();
self::releaseLock('wfAutoUpdate');
* .htaccess file contents to disable all script execution in a given directory.
private static $_disable_scripts_htaccess = '# BEGIN Wordfence code execution protection
AddHandler cgi-script .php .phtml .php3 .pl .py .jsp .asp .htm .shtml .sh .cgi
# END Wordfence code execution protection
private static $_disable_scripts_regex = '/# BEGIN Wordfence code execution protection.+?# END Wordfence code execution protection/s';
private static function _uploadsHtaccessFilePath() {
$upload_dir = wp_upload_dir();
return $upload_dir['basedir'] . '/.htaccess';
* Add/Merge .htaccess file in the uploads directory to prevent code execution.
* @throws wfConfigException
public static function disableCodeExecutionForUploads() {
$uploads_htaccess_file_path = self::_uploadsHtaccessFilePath();
$uploads_htaccess_has_content = false;
if (file_exists($uploads_htaccess_file_path)) {
$htaccess_contents = file_get_contents($uploads_htaccess_file_path);
// htaccess exists and contains our htaccess code to disable script execution, nothing more to do
if (strpos($htaccess_contents, self::$_disable_scripts_htaccess) !== false) {
$uploads_htaccess_has_content = strlen(trim($htaccess_contents)) > 0;
if (@file_put_contents($uploads_htaccess_file_path, ($uploads_htaccess_has_content ? "\n\n" : "") . self::$_disable_scripts_htaccess, FILE_APPEND | LOCK_EX) === false) {
throw new wfConfigException(__("Unable to save the .htaccess file needed to disable script execution in the uploads directory. Please check your permissions on that directory.", 'wordfence'));
self::set('disableCodeExecutionUploadsPHP7Migrated', true);
public static function migrateCodeExecutionForUploadsPHP7() {
if (self::get('disableCodeExecutionUploads')) {
if (!self::get('disableCodeExecutionUploadsPHP7Migrated')) {
$uploads_htaccess_file_path = self::_uploadsHtaccessFilePath();
if (file_exists($uploads_htaccess_file_path)) {
$htaccess_contents = file_get_contents($uploads_htaccess_file_path);
if (preg_match(self::$_disable_scripts_regex, $htaccess_contents)) {
$htaccess_contents = preg_replace(self::$_disable_scripts_regex, self::$_disable_scripts_htaccess, $htaccess_contents);
@file_put_contents($uploads_htaccess_file_path, $htaccess_contents);
self::set('disableCodeExecutionUploadsPHP7Migrated', true);
* Remove script execution protections for our the .htaccess file in the uploads directory.
* @throws wfConfigException
public static function removeCodeExecutionProtectionForUploads() {
$uploads_htaccess_file_path = self::_uploadsHtaccessFilePath();
if (file_exists($uploads_htaccess_file_path)) {
$htaccess_contents = file_get_contents($uploads_htaccess_file_path);
// Check that it is in the file
if (preg_match(self::$_disable_scripts_regex, $htaccess_contents)) {
$htaccess_contents = preg_replace(self::$_disable_scripts_regex, '', $htaccess_contents);
$error_message = __("Unable to remove code execution protections applied to the .htaccess file in the uploads directory. Please check your permissions on that file.", 'wordfence');
if (strlen(trim($htaccess_contents)) === 0) {
if (!@unlink($uploads_htaccess_file_path)) {
throw new wfConfigException($error_message);
} elseif (@file_put_contents($uploads_htaccess_file_path, $htaccess_contents, LOCK_EX) === false) {
throw new wfConfigException($error_message);
* Validates the array of configuration changes without applying any. All bounds checks must be performed here.
* @return bool|array Returns true if valid, otherwise a displayable error message per error encountered.
* @throws wfWAFStorageFileException
public static function validate($changes) {
$waf = wfWAF::getInstance();
$wafConfig = $waf->getStorageEngine();
foreach ($changes as $key => $value) {
case 'learningModeGracePeriod':
//If currently in or will be in learning mode, restrict the grace period to be in the future
$wafStatus = (isset($changes['wafStatus']) ? $changes['wafStatus'] : $wafConfig->getConfig('wafStatus'));
$gracePeriodEnd = strtotime($value);
if ($wafStatus == wfFirewall::FIREWALL_MODE_LEARNING && $gracePeriodEnd <= time()) {
$errors[] = array('option' => $key, 'error' => __('The grace period end time must be in the future.', 'wordfence'));
if ($value != wfFirewall::FIREWALL_MODE_ENABLED && $value != wfFirewall::FIREWALL_MODE_LEARNING && $value != wfFirewall::FIREWALL_MODE_DISABLED) {
$errors[] = array('option' => $key, 'error' => __('Unknown firewall mode.', 'wordfence'));
$dirtyEmails = explode(',', preg_replace('/[\r\n\s\t]+/', '', $value));
$dirtyEmails = array_filter($dirtyEmails);
foreach ($dirtyEmails as $email) {
if (!wfUtils::isValidEmail($email)) {
if (count($badEmails) > 0) {
$errors[] = array('option' => $key, 'error' => __('The following emails are invalid: ', 'wordfence') . esc_html(implode(', ', $badEmails), array()));
case 'scan_include_extra':
$dirtyRegexes = explode("\n", $value);
foreach ($dirtyRegexes as $regex) {
if (@preg_match("/$regex/", "") === false) {
$errors[] = array('option' => $key, 'error' => sprintf(
/* translators: Regular expression. */
__('"%s" is not a valid regular expression.', 'wordfence'), esc_html($regex)));
$dirtyWhitelisted = explode(',', preg_replace('/[\r\n\s\t]+/', ',', $value));
$dirtyWhitelisted = array_filter($dirtyWhitelisted);
$range = new wfUserIPRange();
foreach ($dirtyWhitelisted as $whiteIP) {
$range->setIPString($whiteIP);
if (!$range->isValidRange()) {
$badWhiteIPs[] = $whiteIP;
if (count($badWhiteIPs) > 0) {
$errors[] = array('option' => $key, 'error' => __('Please make sure you separate your IP addresses with commas. The following allowlisted IP addresses are invalid: ', 'wordfence') . esc_html(implode(', ', $badWhiteIPs), array()));
case 'liveTraf_ignoreUsers':
$dirtyUsers = explode(',', $value);
foreach ($dirtyUsers as $val) {
if (!get_user_by('login', $val)) {
if (count($invalidUsers) > 0) {
$errors[] = array('option' => $key, 'error' => __('The following users you selected to ignore in live traffic reports are not valid on this system: ', 'wordfence') . esc_html(implode(', ', $invalidUsers), array()));
case 'liveTraf_ignoreIPs':
$dirtyIPs = explode(',', preg_replace('/[\r\n\s\t]+/', '', $value));
$dirtyIPs = array_filter($dirtyIPs);
foreach ($dirtyIPs as $val) {
if (!wfUtils::isValidIP($val)) {
if (count($invalidIPs) > 0) {
$errors[] = array('option' => $key, 'error' => __('The following IPs you selected to ignore in live traffic reports are not valid: ', 'wordfence') . esc_html(implode(', ', $invalidIPs), array()));
case 'howGetIPs_trusted_proxies':
$dirtyIPs = preg_split('/[\r\n,]+/', $value);
$dirtyIPs = array_filter($dirtyIPs);
foreach ($dirtyIPs as $val) {
if (!(wfUtils::isValidIP($val) || wfUtils::isValidCIDRRange($val))) {
if (count($invalidIPs) > 0) {
$errors[] = array('option' => $key, 'error' => __('The following IPs/ranges you selected to trust as proxies are not valid: ', 'wordfence') . esc_html(implode(', ', $invalidIPs), array()));
case 'howGetIPs_trusted_proxy_preset':
$presets = wfConfig::getJSON('ipResolutionList', array());
if (!is_array($presets)) {
if (!(empty($value) /* "None" */ || isset($presets[$value]))) {
$errors[] = array('option' => $key, 'error' => __('The selected trusted proxy preset is not valid: ', 'wordfence') . esc_html($value));
$errors[] = array('option' => $key, 'error' => __('An empty license key was entered.', 'wordfence'));
else if ($value && !preg_match('/^[a-fA-F0-9]+$/', $value)) {
$errors[] = array('option' => $key, 'error' => __('The license key entered is not in a valid format. It must contain only numbers and the letters A-F.', 'wordfence'));
$exclusionList = explode("\n", trim($value));
foreach ($exclusionList as $exclusion) {
$exclusion = trim($exclusion);
if ($exclusion === '*') {
$errors[] = array('option' => $key, 'error' => __('A wildcard cannot be used to exclude all files from the scan.', 'wordfence'));
case 'scan_max_resume_attempts':
wfScanMonitor::validateResumeAttempts($value, $valid);
$errors[] = array('option' => $key, 'error' => sprintf(__('Invalid number of scan resume attempts specified: %d', 'wordfence'), $value));
public static function clean($changes) {
foreach ($changes as $key => $value) {
if (preg_match('/^whitelistedServices\.([a-z0-9]+)$/i', $key, $matches)) {
if (!isset($cleaned['whitelistedServices']) || !is_array($cleaned['whitelistedServices'])) {
$cleaned['whitelistedServices'] = wfConfig::getJSON('whitelistedServices', array());
$cleaned['whitelistedServices'][$matches[1]] = wfUtils::truthyToBoolean($value);
* Saves the array of configuration changes in the correct place. This may currently be the wfConfig table, the WAF's config file, or both. The
* validation function will handle all bounds checks and this will be limited to normalizing the values as needed.
* @throws wfConfigException
* @throws wfWAFStorageFileException
public static function save($changes) {
$waf = wfWAF::getInstance();
$wafConfig = $waf->getStorageEngine();
if (isset($changes['apiKey'])) { //Defer to end
$apiKey = $changes['apiKey'];
unset($changes['apiKey']);
foreach ($changes as $key => $value) {
case 'learningModeGracePeriod':
$wafStatus = (isset($changes['wafStatus']) ? $changes['wafStatus'] : $wafConfig->getConfig('wafStatus'));
if ($wafStatus == wfFirewall::FIREWALL_MODE_LEARNING) {
$dt = wfUtils::parseLocalTime($value);
$gracePeriodEnd = $dt->format('U');
$wafConfig->setConfig($key, $gracePeriodEnd);
case 'learningModeGracePeriodEnabled':
$wafStatus = (isset($changes['wafStatus']) ? $changes['wafStatus'] : $wafConfig->getConfig('wafStatus'));
if ($wafStatus == wfFirewall::FIREWALL_MODE_LEARNING) {
$wafConfig->setConfig($key, wfUtils::truthyToInt($value));
$wafConfig->setConfig($key, $value);
if ($value != wfFirewall::FIREWALL_MODE_LEARNING) {
$wafConfig->setConfig('learningModeGracePeriodEnabled', 0);
$wafConfig->unsetConfig('learningModeGracePeriod');
$firewall = new wfFirewall();
$firewall->syncStatus(true);
if ($value == wfFirewall::FIREWALL_MODE_DISABLED) {
$currentUser = wp_get_current_user();
$username = $currentUser->user_login;
$alertCallback = array(new wfWafDeactivatedAlert($username, wfUtils::getIP()), 'send');
do_action('wordfence_security_event', 'wafDeactivated', array(
'ip' => wfUtils::getIP(),
$disabledRules = (array) $wafConfig->getConfig('disabledRules');
foreach ($value as $ruleID => $ruleEnabled) {
unset($disabledRules[$ruleID]);
$disabledRules[$ruleID] = true;
$wafConfig->setConfig('disabledRules', $disabledRules);
case 'whitelistedURLParams':
$whitelistedURLParams = (array) $wafConfig->getConfig('whitelistedURLParams', null, 'livewaf');
if (isset($value['delete'])) {
foreach ($value['delete'] as $whitelistKey => $unused) {
unset($whitelistedURLParams[$whitelistKey]);