: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
require_once(__DIR__ . '/wordfenceClass.php');
require_once(__DIR__ . '/wordfenceHash.php');
require_once(__DIR__ . '/wfAPI.php');
require_once(__DIR__ . '/wordfenceScanner.php');
require_once(__DIR__ . '/wfIssues.php');
require_once(__DIR__ . '/wfDB.php');
require_once(__DIR__ . '/wfUtils.php');
require_once(__DIR__ . '/wfFileUtils.php');
require_once(__DIR__ . '/wfScanPath.php');
require_once(__DIR__ . '/wfScanFile.php');
require_once(__DIR__ . '/wfScanFileListItem.php');
require_once(__DIR__ . '/wfScanEntrypoint.php');
require_once(__DIR__ . '/wfCurlInterceptor.php');
const SCAN_MANUALLY_KILLED = -999;
const SCAN_CHECK_INTERVAL = 10; //Seconds
private static $scanIsRunning = false; //Indicates that the scan is running in this specific process
private $dictWords = array();
private $forkRequested = false;
//Beginning of serialized properties on sleep
/** @var wordfenceHash */
private $jobList = array();
private $wp_version = false;
public $maxExecTime = false; //If more than $maxExecTime has elapsed since last check, fork a new scan process and continue
private $publicScanEnabled = false;
private $fileContentsResults = false;
* @var bool|wordfenceScanner
private $scanner = false;
private $scanQueue = array();
* @var bool|wordfenceURLHoover
private $scanData = array();
private $statusIDX = array(
private $userPasswdQueue = "";
private $passwdHasIssues = wfIssues::STATUS_SECURE;
private $suspectedFiles = false; //Files found with the ".suspected" extension
private $gsbMultisiteBlogOffset = 0;
private $updateCheck = false;
private $pluginRepoStatus = array();
private $malwarePrefixesHash;
private $scanMode = wfScanner::SCAN_TYPE_STANDARD;
private $pluginsCounted = false;
private $themesCounted = false;
private $scanController; //Not serialized
* @var wordfenceDBScanner
* @var wfScanKnownFilesLoader
private $knownFilesLoader;
private $metrics = array();
private $checkHowGetIPsRequestTime = 0;
* Returns whether or not the Wordfence scan is running. When $inThisProcessOnly is true, it returns true only
* if the scan is running in this process. Otherwise it returns true if the scan is running at all.
* @param bool $inThisProcessOnly
public static function isScanRunning($inThisProcessOnly = true) {
if ($inThisProcessOnly) {
return self::$scanIsRunning;
return wfScanner::shared()->isRunning();
public static function testForFullPathDisclosure($url = null, $filePath = null) {
if ($url === null && $filePath === null) {
$url = includes_url('rss-functions.php');
$filePath = ABSPATH . WPINC . '/rss-functions.php';
$response = wp_remote_get($url);
$html = wp_remote_retrieve_body($response);
return preg_match("/" . preg_quote(realpath($filePath), "/") . "/i", $html);
public static function isDirectoryListingEnabled($url = null) {
$uploadPaths = wp_upload_dir();
$url = $uploadPaths['baseurl'];
$response = wp_remote_get($url);
return !is_wp_error($response) && ($responseBody = wp_remote_retrieve_body($response)) &&
stripos($responseBody, '<title>Index of') !== false;
public static function refreshScanNotification($issuesInstance = null) {
if ($issuesInstance === null) {
$issuesInstance = new wfIssues();
$message = wfConfig::get('lastScanCompleted', false);
if ($message === false || empty($message)) {
$n = wfNotification::getNotificationForCategory('wfplugin_scan');
} else if ($message == 'ok') {
$issueCount = $issuesInstance->getIssueCount();
new wfNotification(null, wfNotification::PRIORITY_HIGH_WARNING, "<a href=\"" . wfUtils::wpAdminURL('admin.php?page=WordfenceScan') . "\">" .
/* translators: Number of scan results. */
sprintf(_n('%d issue found in most recent scan', '%d issues found in most recent scan', $issueCount, 'wordfence'), $issueCount)
. '</a>', 'wfplugin_scan');
$n = wfNotification::getNotificationForCategory('wfplugin_scan');
$failureType = wfConfig::get('lastScanFailureType');
if ($failureType == 'duration') {
new wfNotification(null, wfNotification::PRIORITY_HIGH_WARNING, '<a href="' . wfUtils::wpAdminURL('admin.php?page=WordfenceScan') . '">Scan aborted due to duration limit</a>', 'wfplugin_scan');
} else if ($failureType == 'versionchange') {
//No need to create a notification
$trimmedError = substr($message, 0, 100) . (strlen($message) > 100 ? '...' : '');
new wfNotification(null, wfNotification::PRIORITY_HIGH_WARNING, '<a href="' . wfUtils::wpAdminURL('admin.php?page=WordfenceScan') . '">Scan failed: ' . esc_html($trimmedError) . '</a>', 'wfplugin_scan');
public function __sleep() { //Same order here as above for properties that are included in serialization
return array('hasher', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'maxExecTime', 'publicScanEnabled', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues', 'suspectedFiles', 'dbScanner', 'knownFilesLoader', 'metrics', 'checkHowGetIPsRequestTime', 'gsbMultisiteBlogOffset', 'updateCheck', 'pluginRepoStatus', 'malwarePrefixesHash', 'coreHashesHash', 'scanMode', 'pluginsCounted', 'themesCounted');
public function __construct($malwarePrefixesHash = '', $coreHashesHash = '', $scanMode = wfScanner::SCAN_TYPE_STANDARD) {
$this->startTime = time();
$this->recordMetric('scan', 'start', $this->startTime);
$this->maxExecTime = self::getMaxExecutionTime();
$this->i = new wfIssues();
$this->cycleStartTime = time();
$this->wp_version = wfUtils::getWPVersion();
$this->apiKey = wfConfig::get('apiKey');
$this->api = new wfAPI($this->apiKey, $this->wp_version);
$this->malwarePrefixesHash = $malwarePrefixesHash;
$this->coreHashesHash = $coreHashesHash;
include(dirname(__FILE__) . '/wfDict.php'); //$dictWords
$this->dictWords = $dictWords;
$this->scanMode = $scanMode;
$this->scanController = new wfScanner($scanMode);
$jobs = $this->scanController->jobs();
foreach ($jobs as $job) {
if (method_exists($this, 'scan_' . $job . '_init')) {
foreach (array('init', 'main', 'finish') as $op) {
$this->jobList[] = $job . '_' . $op;
} else if (method_exists($this, 'scan_' . $job)) {
public function scanController() {
return $this->scanController;
* Deletes all new issues. To only delete specific types, provide an array of issue types.
* @param null|array $types
public function deleteNewIssues($types = null) {
$this->i->deleteNew($types);
public function __wakeup() {
$this->cycleStartTime = time();
$this->api = new wfAPI($this->apiKey, $this->wp_version);
include(dirname(__FILE__) . '/wfDict.php'); //$dictWords
$this->dictWords = $dictWords;
$this->scanController = new wfScanner($this->scanMode);
public function isFullScan() {
return $this->scanMode != wfScanner::SCAN_TYPE_QUICK;
self::$scanIsRunning = true;
wfConfig::set('lastScanCompleted', 'ok');
wfConfig::set('lastScanFailureType', false);
//updating this scan ID will trigger the scan page to load/reload the results.
$this->scanController->recordLastScanTime();
//scan ID only incremented at end of scan to make UI load new results
if ($this->isFullScan()) {
$this->recordMetric('scan', 'duration', (time() - $this->startTime));
$this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
wfScanEngine::refreshScanNotification($this->i);
if (wfCentral::isConnected()) {
wfCentral::updateScanStatus();
self::$scanIsRunning = false;
} catch (wfScanEngineDurationLimitException $e) {
wfConfig::set('lastScanCompleted', $e->getMessage());
wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_DURATION_REACHED);
$this->scanController->recordLastScanTime();
$this->emailNewIssues(true);
$this->recordMetric('scan', 'duration', (time() - $this->startTime));
$this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
wfScanEngine::refreshScanNotification($this->i);
self::$scanIsRunning = false;
} catch (wfScanEngineCoreVersionChangeException $e) {
wfConfig::set('lastScanCompleted', $e->getMessage());
wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_VERSION_CHANGE);
$this->scanController->recordLastScanTime();
$this->recordMetric('scan', 'duration', (time() - $this->startTime));
$this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
$this->deleteNewIssues();
wfScanEngine::refreshScanNotification($this->i);
self::$scanIsRunning = false;
} catch (wfScanEngineTestCallbackFailedException $e) {
wfConfig::set('lastScanCompleted', $e->getMessage());
wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
$this->scanController->recordLastScanTime();
$this->recordMetric('scan', 'duration', (time() - $this->startTime));
$this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
$this->recordMetric('scan', 'failure', $e->getMessage());
wfScanEngine::refreshScanNotification($this->i);
self::$scanIsRunning = false;
if ($e->getCode() != wfScanEngine::SCAN_MANUALLY_KILLED) {
wfConfig::set('lastScanCompleted', $e->getMessage());
wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_GENERAL);
$this->recordMetric('scan', 'duration', (time() - $this->startTime));
$this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
$this->recordMetric('scan', 'failure', $e->getMessage());
wfScanEngine::refreshScanNotification($this->i);
self::$scanIsRunning = false;
public function checkForDurationLimit() {
static $timeLimit = false;
if ($timeLimit === false) {
$timeLimit = intval(wfConfig::get('scan_maxDuration'));
$timeLimit = WORDFENCE_DEFAULT_MAX_SCAN_TIME;
if ((time() - $this->startTime) > $timeLimit) {
/* translators: 1. Time duration. 2. Support URL. */
__('The scan time limit of %1$s has been exceeded and the scan will be terminated. This limit can be customized on the options page. <a href="%2$s" target="_blank" rel="noopener noreferrer">Get More Information<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'),
wfUtils::makeDuration($timeLimit),
wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_TIME_LIMIT)
$this->addIssue('timelimit', wfIssues::SEVERITY_HIGH, md5($this->startTime), md5($this->startTime), __('Scan Time Limit Exceeded', 'wordfence'), $error, array());
$this->status(1, 'info', '-------------------');
$this->status(1, 'info', sprintf(
/* translators: 1. Number of files. 2. Number of plugins. 3. Number of themes. 4. Number of posts. 5. Number of comments. 6. Number of URLs. 7. Time duration. */
__('Scan interrupted. Scanned %1$d files, %2$d plugins, %3$d themes, %4$d posts, %5$d comments and %6$d URLs in %7$s.', 'wordfence'),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_FILES, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_PLUGINS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_THEMES, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_POSTS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_COMMENTS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_URLS, 0),
wfUtils::makeDuration(time() - $this->startTime, true)
if ($this->i->totalIssues > 0) {
$this->status(10, 'info', "SUM_FINAL:" . sprintf(
/* translators: Number of scan results. */
"Scan interrupted. You have %d new issue to fix. See below.",
"Scan interrupted. You have %d new issues to fix. See below.",
$this->status(10, 'info', "SUM_FINAL:" . __('Scan interrupted. No problems found prior to stopping.', 'wordfence'));
throw new wfScanEngineDurationLimitException($error);
public function checkForCoreVersionChange() {
$startVersion = wfConfig::get('wfScanStartVersion');
$currentVersion = wfUtils::getWPVersion(true);
if (version_compare($startVersion, $currentVersion) != 0) {
throw new wfScanEngineCoreVersionChangeException(sprintf(
/* translators: 1. Software version. 2. Software version. */
__('Aborting scan because WordPress updated from version %1$s to %2$s. The scan will be reattempted later.', 'wordfence'), $startVersion, $currentVersion));
private function checkScanStatus() {
wfIssues::updateScanStillRunning();
$this->checkForCoreVersionChange();
$this->checkForDurationLimit();
public function shouldFork() {
if ($timestamp - $this->cycleStartTime > $this->maxExecTime) {
$this->checkScanStatus();
if ($this->lastCheck > $timestamp - $this->maxExecTime) {
if ($timestamp - $this->lastCheck > self::SCAN_CHECK_INTERVAL)
$this->checkScanStatus();
$this->lastCheck = $timestamp;
public function forkIfNeeded() {
if ($this->shouldFork()) {
wordfence::status(4, 'info', __("Forking during malware scan to ensure continuity.", 'wordfence'));
wordfence::status(4, 'info', __("Entered fork()", 'wordfence'));
if (wfConfig::set_ser('wfsd_engine', $this, true, wfConfig::DONT_AUTOLOAD)) {
$this->scanController->flushSummaryItems();
wordfence::status(4, 'info', __("Calling startScan(true)", 'wordfence'));
self::startScan(true, $this->scanMode);
} //Otherwise there was an error so don't start another scan.
public function emailNewIssues($timeLimitReached = false) {
if (!wfCentral::pluginAlertingDisabled()) {
$this->i->emailNewIssues($timeLimitReached, $this->scanController);
public function submitMetrics() {
if (wfConfig::get('other_WFNet', true)) {
//Trim down the malware matches if needed to allow the report call to succeed
if (isset($this->metrics['malwareSignature'])) {
foreach ($this->metrics['malwareSignature'] as $rule => $payloads) {
$count += count($payloads);
$extra_count += (count($payloads) - 1);
if (count($payloads) > 1) {
//Trim additional matches
$overage = $extra_count - WORDFENCE_SCAN_ISSUES_MAX_REPORT;
foreach ($this->metrics['malwareSignature'] as $rule => $payloads) {
$percent = min(1, (count($payloads) - 1) / $extra_count); //Percentage of the overage this rule is responsible for
$to_remove = min(count($payloads) - 1, ceil($percent * $overage)); //Remove the lesser of (all but one, the percentage of the overage)
$sliced = array_slice($this->metrics['malwareSignature'][$rule], 0, max(1, count($payloads) - $to_remove));
$count -= (count($this->metrics['malwareSignature'][$rule]) - count($sliced));
$this->metrics['malwareSignature'][$rule] = $sliced;
if ($count > WORDFENCE_SCAN_ISSUES_MAX_REPORT) {
$sliced = array_slice($this->metrics['malwareSignature'], 0, WORDFENCE_SCAN_ISSUES_MAX_REPORT, true);
$this->metrics['malwareSignature'] = $sliced;
$this->api->call('record_scan_metrics', array(), array('metrics' => $this->metrics));
private function doScan() {
if ($this->scanController->useLowResourceScanning()) {
$isFork = ($_GET['isFork'] == '1' ? true : false);
wfConfig::set('lowResourceScanWaitStep', !wfConfig::get('lowResourceScanWaitStep'));
if ($isFork && wfConfig::get('lowResourceScanWaitStep')) {
sleep((int) round($this->maxExecTime / 2));
while (sizeof($this->jobList) > 0) {
$jobName = $this->jobList[0];
$callback = array($this, 'scan_' . $jobName);
if (is_callable($callback)) {
call_user_func($callback);
array_shift($this->jobList); //only shift once we're done because we may pause halfway through a job and need to pick up where we left off
if ($this->forkRequested) {
$this->status(1, 'info', '-------------------');
$peakMemory = wfScan::logPeakMemory();
$this->status(2, 'info', sprintf(
/* translators: 1. Memory in bytes. 2. Memory in bytes. */
__('Wordfence used %1$s of memory for scan. Server peak memory usage was: %2$s', 'wordfence'),
wfUtils::formatBytes($peakMemory - wfScan::$peakMemAtStart),
wfUtils::formatBytes($peakMemory)
wfScanMonitor::endMonitoring();
if ($this->isFullScan()) {
$this->status(1, 'info', sprintf(
/* translators: 1. Number of files. 2. Number of plugins. 3. Number of themes. 4. Number of posts. 5. Number of comments. 6. Number of URLs. 7. Time duration. */
__('Scan Complete. Scanned %1$d files, %2$d plugins, %3$d themes, %4$d posts, %5$d comments and %6$d URLs in %7$s.', 'wordfence'),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_FILES, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_PLUGINS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_THEMES, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_POSTS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_COMMENTS, 0),
$this->scanController->getSummaryItem(wfScanner::SUMMARY_SCANNED_URLS, 0),
wfUtils::makeDuration(time() - $this->startTime, true)
$this->status(1, 'info', sprintf(
/* translators: 1. Time duration. */
__("Quick Scan Complete. Scanned in %s.", 'wordfence'),
wfUtils::makeDuration(time() - $this->startTime, true)