: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
private function checkKnownPluginFile($properties) {
if (in_array($properties->shac, $this->knownFiles[self::KNOWN_FILE_PLUGIN][$properties->wordpressPath])) {
if ($this->pluginsEnabled) {
$options = $this->engine->scanController()->scanOptions();
$shouldGenerateIssue = true;
if (!$options['scansEnabled_highSense'] && preg_match('~/readme\.(?:txt|md)$~i', $properties->wordpressPath)) { //Don't generate issues for changed readme files unless high sensitivity is on
$shouldGenerateIssue = false;
if ($shouldGenerateIssue) {
$itemName = $this->knownFiles['plugins'][$properties->wordpressPath][0];
$itemVersion = $this->knownFiles['plugins'][$properties->wordpressPath][1];
$cKey = $this->knownFiles['plugins'][$properties->wordpressPath][2];
$this->engine->addPendingIssue(
wfIssues::SEVERITY_MEDIUM,
'modifiedplugin' . $properties->wordpressPath,
'modifiedplugin' . $properties->wordpressPath . $properties->md5,
sprintf(/* translators: File path. */ __('Modified plugin file: %s', 'wordfence'), $properties->wordpressPath),
/* translators: 1. Plugin name. 2. Plugin version. 3. Support URL. */
__('This file belongs to plugin "%1$s" version "%2$s" and has been modified from the file that is distributed by WordPress.org for this version. Please use the link to see how the file has changed. If you have modified this file yourself, you can safely ignore this warning. If you see a lot of changed files in a plugin that have been made by the author, then try uninstalling and reinstalling the plugin to force an upgrade. Doing this is a workaround for plugin authors who don\'t manage their code correctly. <a href="%3$s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'),
wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_MODIFIED_PLUGIN)
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
'cVersion' => $itemVersion,
'haveIssues' => 'plugins'
private function checkKnownThemeFile($properties) {
if (in_array($properties->shac, $this->knownFiles[self::KNOWN_FILE_THEME][$properties->wordpressPath])) {
if ($this->themesEnabled) {
$options = $this->engine->scanController()->scanOptions();
$shouldGenerateIssue = true;
if (!$options['scansEnabled_highSense'] && preg_match('~/readme\.(?:txt|md)$~i', $properties->wordpressPath)) { //Don't generate issues for changed readme files unless high sensitivity is on
$shouldGenerateIssue = false;
if ($shouldGenerateIssue) {
$itemName = $this->knownFiles['themes'][$properties->wordpressPath][0];
$itemVersion = $this->knownFiles['themes'][$properties->wordpressPath][1];
$cKey = $this->knownFiles['themes'][$properties->wordpressPath][2];
$this->engine->addPendingIssue(
wfIssues::SEVERITY_MEDIUM,
'modifiedtheme' . $properties->wordpressPath,
'modifiedtheme' . $properties->wordpressPath . $properties->md5,
sprintf(/* translators: File path. */ __('Modified theme file: %s', 'wordfence'), $properties->wordpressPath),
/* translators: 1. Plugin name. 2. Plugin version. 3. Support URL. */
__('This file belongs to theme "%1$s" version "%2$s" and has been modified from the original distribution. It is common for site owners to modify their theme files, so if you have modified this file yourself you can safely ignore this warning. <a href="%3$s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'),
wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_MODIFIED_THEME)
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
'cVersion' => $itemVersion,
private function checkKnownFileOther($properties) {
$restrictedWordPressFolders = array(ABSPATH . 'wp-admin/', ABSPATH . WPINC . '/');
foreach ($restrictedWordPressFolders as $path) {
if (strpos($properties->realPath, $path) === 0) {
if ($this->isPreviousCoreFile($properties->shac)) {
$added = $this->engine->addIssue(
'coreUnknown' . $properties->wordpressPath,
'coreUnknown' . $properties->wordpressPath . $properties->md5,
sprintf(/* translators: File path. */ __('Old WordPress core file not removed during update: %s', 'wordfence'), $properties->wordpressPath),
__('This file is in a WordPress core location but is from an older version of WordPress and not used with your current version. Hosting or permissions issues can cause these files to get left behind when WordPress is updated and they should be removed if possible.', 'wordfence'),
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
else if (preg_match('#/php\.ini$#', $properties->wordpressPath)) {
$this->engine->addPendingIssue(
'coreUnknown' . $properties->wordpressPath,
'coreUnknown' . $properties->wordpressPath . $properties->md5,
sprintf(/* translators: File path. */ __('Unknown file in WordPress core: %s', 'wordfence'), $properties->wordpressPath),
__('This file is in a WordPress core location but is not distributed with this version of WordPress. This scan often includes files left over from a previous WordPress version, but it may also find files added by another plugin, files added by your host, or malicious files added by an attacker.', 'wordfence'),
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
'learnMore' => wfSupportController::supportURL(wfSupportController::ITEM_SCAN_RESULT_UNKNOWN_FILE_CORE),
'haveIssues' => 'coreUnknown',
$added = $this->engine->addIssue(
'coreUnknown' . $properties->wordpressPath,
'coreUnknown' . $properties->wordpressPath . $properties->md5,
sprintf(/* translators: File path. */ __('Unknown file in WordPress core: %s', 'wordfence'), $properties->wordpressPath),
sprintf(/* translators: Support URL. */ __('This file is in a WordPress core location but is not distributed with this version of WordPress. This scan often includes files left over from a previous WordPress version, but it may also find files added by another plugin, files added by your host, or malicious files added by an attacker. <a href="%s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_UNKNOWN_FILE_CORE)),
'file' => $properties->wordpressPath,
'realFile' => $properties->realPath,
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $this->haveIssues['coreUnknown'] = wfIssues::STATUS_PROBLEM; }
else if ($this->haveIssues['coreUnknown'] != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $this->haveIssues['coreUnknown'] = wfIssues::STATUS_IGNORED; }
private function checkKnownFile($properties, $type) {
case self::KNOWN_FILE_CORE:
return $this->checkKnownCoreFile($properties);
case self::KNOWN_FILE_PLUGIN:
return $this->checkKnownPluginFile($properties);
case self::KNOWN_FILE_THEME:
return $this->checkKnownThemeFile($properties);
case self::KNOWN_FILE_OTHER:
return $this->checkKnownFileOther($properties);
private function recordFile($properties) {
$this->fileRecords[$properties->wordpressPathMd5] = $properties;
$this->fileRecordCount++;
if ($this->fileRecordCount >= self::MAX_QUEUED_RECORDS)
$this->processFileRecords();
private function processFileRecords($check = true) {
if ($this->fileRecordCount == 0)
wfDB::networkTable('wfFileMods'),
'filenameMD5' => 'UNHEX(%s)',
array_map(function($properties) {
$properties->wordpressPath,
$properties->wordpressPathMd5,
(int) $properties->known,
$this->fileRecordCount = 0;
private function processFile($file) {
$properties = $file->initializeProperties();
$properties->realPath = $file->getRealPath();
$wordpressPath = $file->getWordpressPath();
if (wfUtils::fileTooBig($properties->realPath, $fileSize, $properties->handle)) {
wordfence::status(4, 'info', sprintf(/* translators: File path. */ __("Skipping file larger than max size: %s", 'wordfence'), $properties->realPath));
call_user_func($this->scanFileLogger, $properties->realPath);
$knownFileType = $this->getKnownFileType($properties);
$allowKnownFileScan = $knownFileType !== null;
$hashed = self::hashFile($properties->realPath, $properties);
$this->engine->scanController()->incrementSummaryItem(wfScanner::SUMMARY_SCANNED_FILES);
//wordfence::status(2, 'error', "Could not gen hash for file (probably because we don't have permission to access the file): $properties->realPath");
$properties->known = $allowKnownFileScan && $this->checkKnownFile($properties, $knownFileType);
if($this->malwareEnabled && $this->isMalwarePrefix($properties->md5)){
$this->possibleMalware[] = array($properties->wordpressPath, $properties->md5);
$this->pathMap[$properties->wordpressPath] = $properties->realPath;
$this->recordFile($properties);
$this->totalData += $fileSize;
if($this->totalFiles % 100 === 0){
wordfence::status(2, 'info', sprintf(
/* translators: 1. Number of files. 2. Data in bytes. */
__('Analyzed %1$d files containing %2$s of data so far', 'wordfence'),
wfUtils::formatBytes($this->totalData)
$properties->releaseHandle();
private function flagSafeFiles($filenames) {
$fileModsTable = wfDB::networkTable('wfFileMods');
$existingSafeFiles = $this->db->selectAll(
'filename' => $filenames,
foreach ($existingSafeFiles as $row) {
$allSafeFiles[$row[0]] = true;
$remainingFilenames = [];
foreach ($filenames as $filename) {
if (!array_key_exists($filename, $allSafeFiles))
$remainingFilenames[] = $filename;
$filenames = $remainingFilenames;
$results = $this->db->select(
'filename' => $filenames,
$hashes = array_column($results, 1);
$safeHashes = array_flip($this->engine->isSafeFile($hashes));
foreach ($results as $row) {
$filenameMD5Hex = $row[0];
if (array_key_exists($row[1], $safeHashes)) {
$safeFiles[] = $filenameMD5Hex;
$allSafeFiles[$row[2]] = true;
$unsafeFiles[] = $filenameMD5Hex;
foreach (['1' => $safeFiles, '0' => $unsafeFiles] as $safe => $files) {
'isSafeFile' => [ '%s', $safe ]
'filenameMD5' => 'UNHEX(%s)'
} while (!empty($results));
private function _processPendingIssues() {
$count = $this->engine->getPendingIssueCount();
while ($offset < $count) {
$issues = $this->engine->getPendingIssues($offset);
if (count($issues) == 0) {
$safeFiles = $this->flagSafeFiles(array_map(function($i) { return $i['data']['file']; }, $issues));
//Migrate non-safe file issues to official issues and begin coalescing tagged issues
foreach ($issues as &$i) {
if (!array_key_exists($i['data']['file'], $safeFiles)) {
$haveIssuesType = $i['data']['haveIssues'];
if (isset($i['data']['coalesce'])) {
$key = $i['data']['coalesce'];
if (!isset($this->coalescingIssues[$key])) { $this->coalescingIssues[$key] = array('count' => 0, 'issue' => $i); }
$this->coalescingIssues[$key]['count']++;
$added = $this->engine->addIssue(
true //Prevent ignoreP and ignoreC from being hashed again
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $this->haveIssues[$haveIssuesType] = wfIssues::STATUS_PROBLEM; }
else if ($this->haveIssues[$haveIssuesType] != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $this->haveIssues[$haveIssuesType] = wfIssues::STATUS_IGNORED; }
$offset += count($issues);
$this->engine->checkForKill();
//Insert the coalesced issues (currently just multiple php.ini in system directories)
foreach ($this->coalescingIssues as $c) {
$haveIssuesType = $i['data']['haveIssues'];
$added = $this->engine->addIssue(
$i['shortMsg'] . ($count > 1 ? ' ' . sprintf(/* translators: Number of scan results. */ __('(+ %d more)', 'wordfence'), $count - 1) : ''),
$i['longMsg'] . ($count > 1 ? ' ' . ($count > 2 ? sprintf(/* translators: Number of files. */ __('%d more similar files were found.', 'wordfence'), $count - 1) : __('1 more similar file was found.', 'wordfence')) : '') . (isset($i['data']['learnMore']) ? ' ' . sprintf(__('<a href="%s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'), esc_attr($i['data']['learnMore'])) : ''),
true //Prevent ignoreP and ignoreC from being hashed again
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $this->haveIssues[$haveIssuesType] = wfIssues::STATUS_PROBLEM; }
else if ($this->haveIssues[$haveIssuesType] != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $this->haveIssues[$haveIssuesType] = wfIssues::STATUS_IGNORED; }
public static function hashFile($file, &$properties) {
if (!$properties->resetHandle()) {
$md5Context = hash_init('md5');
$sha256Context = hash_init('sha256');
while (!feof($properties->handle)) {
$data = fread($properties->handle, 65536);
hash_update($md5Context, $data);
hash_update($sha256Context, str_replace(array("\n","\r","\t"," "),"", $data));
$properties->md5 = strtoupper(hash_final($md5Context, false));
$properties->shac = strtoupper(hash_final($sha256Context, false));
private function _shouldHashFile($file) {
$wordpressPath = $file->getWordpressPath();
if ((isset($this->knownFiles['core']) && isset($this->knownFiles['core'][$wordpressPath])) ||
(isset($this->knownFiles['plugins']) && isset($this->knownFiles['plugins'][$wordpressPath])) ||
(isset($this->knownFiles['themes']) && isset($this->knownFiles['themes'][$wordpressPath]))) {
//Excluded file, return false
$excludePatterns = wordfenceScanner::getExcludeFilePattern(wordfenceScanner::EXCLUSION_PATTERNS_USER | wordfenceScanner::EXCLUSION_PATTERNS_MALWARE);
foreach ($excludePatterns as $pattern) {
if (preg_match($pattern, $wordpressPath)) {
//Unknown file in a core location
if ($this->coreUnknownEnabled && !$this->alertedOnUnknownWordPressVersion) {
$restrictedWordPressFolders = array(ABSPATH . 'wp-admin/', ABSPATH . WPINC . '/');
foreach ($restrictedWordPressFolders as $path) {
if (strpos($file->getRealPath(), $path) === 0) {
if (preg_match('/\.([a-zA-Z\d\-]{1,7})$/', $wordpressPath, $matches)) {
$fileExt = strtolower($matches[1]);
if (preg_match('/\.(?:php(?:\d+)?|phtml)(\.|$)/i', $wordpressPath)) {
if (preg_match('/\.(?:html?)(\.|$)/i', $wordpressPath)) {
if (preg_match('/\.(?:js|svg)(\.|$)/i', $wordpressPath)) {
$options = $this->engine->scanController()->scanOptions();
//If scan images is disabled, only allow .js through
if (!$isPHP && preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|mov|mp4|gif|png|tiff?|svg|sql|js|tbz2?|bz2?|xz|zip|tgz|gz|tar|log|err\d+)$/', $fileExt)) {
if (!$options['scansEnabled_scanImages'] && !$isJS) {
//If high sensitivity is disabled, don't allow .sql
if (strtolower($fileExt) == 'sql') {
if (!$options['scansEnabled_highSense']) {
//Treating as binary, return true
$treatAsBinary = ($isPHP || $isHTML || $options['scansEnabled_scanImages']);
//Will be malware scanned, return true
private function isMalwarePrefix($hexMD5){
$hasPrefix = $this->_binaryListContains($this->malwareData, wfUtils::hex2bin($hexMD5), 4);
return $hasPrefix !== false;