Wordfence Security – Firewall & Malware Scan - Version 6.2.4

Version Description

  • Improvement: Scan times for very large sites with huge numbers of files are greatly improved.
  • Improvement: Added a configurable time limit for scans to help reduce overall server load and identify configuration problems.
  • Improvement: Email-based logins are now covered by "Don't let WordPress reveal valid users in login errors".
  • Improvement: Extended rate limiting support to the login page.
  • Fix: Fixed a case where files in the site root with issues could have them added multiple times.
  • Fix: Improved IP detection in the WAF when using an IP detection method that can have multiple values.
  • Fix: Added a safety check for when the database fails to return its max_allowed_packet value.
  • Fix: Added safety checks for when the configuration table migration has failed.
  • Fix: Added a couple rare failed login error codes to brute force detection.
  • Fix: Fixed a sequencing problem when adding detection for bot/human that led to it being called on every request.
  • Fix: Suppressed errors if a file is removed between the start of a scan and later scan stages.
  • Fix: Addressed a problem where the scan exclusions list was not checked correctly in some situations.
Download this release

Release Info

Developer wfryan
Plugin Icon 128x128 Wordfence Security – Firewall & Malware Scan
Version 6.2.4
Comparing to
See all releases

Code changes from version 6.2.3 to 6.2.4

js/admin.js CHANGED
@@ -179,8 +179,8 @@
179
  }
180
  startTicker = true;
181
  if (this.needTour()) {
182
- this.tour('wfWelcomeContent3', 'wfHeading', 'top', 'left', "Learn about Site Performance", function() {
183
- self.tourRedir('WordfenceSitePerf');
184
  });
185
  }
186
  } else if (jQuery('#wordfenceMode_options').length > 0) {
@@ -552,7 +552,7 @@
552
  }
553
  html += '">[' + item.date + ']&nbsp;' + item.msg + '</div>';
554
  jQuery('#consoleActivity').append(html);
555
- if (/Scan complete\./i.test(item.msg)) {
556
  this.loadIssues();
557
  }
558
  }
179
  }
180
  startTicker = true;
181
  if (this.needTour()) {
182
+ this.tour('wfWelcomeContent3', 'wfHeading', 'top', 'left', "Learn about IP Blocking", function() {
183
+ self.tourRedir('WordfenceBlockedIPs');
184
  });
185
  }
186
  } else if (jQuery('#wordfenceMode_options').length > 0) {
552
  }
553
  html += '">[' + item.date + ']&nbsp;' + item.msg + '</div>';
554
  jQuery('#consoleActivity').append(html);
555
+ if (/Scan complete\./i.test(item.msg) || /Scan interrupted\./i.test(item.msg)) {
556
  this.loadIssues();
557
  }
558
  }
lib/email_newIssues.php CHANGED
@@ -11,6 +11,12 @@
11
  </div>
12
  <?php endif ?>
13
 
 
 
 
 
 
 
14
  <?php if($totalCriticalIssues > 0){ ?>
15
  <p>Critical Problems:</p>
16
 
11
  </div>
12
  <?php endif ?>
13
 
14
+ <?php if ($timeLimitReached): ?>
15
+ <div style="margin: 12px 0;padding: 8px; background-color: #ffffe0; border: 1px solid #ffd975; border-width: 1px 1px 1px 10px;">
16
+ <em>The scan was terminated early because it reached the time limit for scans. If you would like to allow your scans to run longer, you can customize the limit on the options page: <a href="<?php echo $adminURL; ?>admin.php?page=WordfenceSecOpt"><?php echo $adminURL; ?>admin.php?page=WordfenceSecOpt</a> or read more about scan options to improve scan speed here: <a href="https://docs.wordfence.com/en/Scan_time_limit">https://docs.wordfence.com/en/Scan_time_limit</a></em>
17
+ </div>
18
+ <?php endif ?>
19
+
20
  <?php if($totalCriticalIssues > 0){ ?>
21
  <p>Critical Problems:</p>
22
 
lib/menu_options.php CHANGED
@@ -632,6 +632,15 @@ $w = new wfConfig();
632
  issues will be sent.
633
  </td>
634
  </tr>
 
 
 
 
 
 
 
 
 
635
  <tr>
636
  <td colspan="2">
637
  <div class="wfMarker" id="wfMarkerFirewallRules"></div>
632
  issues will be sent.
633
  </td>
634
  </tr>
635
+ <tr>
636
+ <th>Time limit that a scan can run in seconds.<a
637
+ href="http://docs.wordfence.com/en/Wordfence_options#Time_limit_that_a_scan_can_run_in_seconds"
638
+ target="_blank" class="wfhelp"></a></th></th>
639
+ <td>
640
+ <input type="text" name="scan_maxDuration" id="scan_maxDuration"
641
+ value="<?php $w->f( 'scan_maxDuration' ); ?>"/> 0 or empty means the default of <?php echo wfUtils::makeDuration(WORDFENCE_DEFAULT_MAX_SCAN_TIME); ?> will be used.
642
+ </td>
643
+ </tr>
644
  <tr>
645
  <td colspan="2">
646
  <div class="wfMarker" id="wfMarkerFirewallRules"></div>
lib/menu_scan.php CHANGED
@@ -1134,6 +1134,37 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
1134
  </div>
1135
  </script>
1136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
 
1138
 
1139
 
@@ -1161,7 +1192,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
1161
  <div>
1162
  <h3>Welcome to Wordfence</h3>
1163
  <p>
1164
- Wordfence is a robust and complete security system and performance enhancer for WordPress. It protects your WordPress site
1165
  from security threats and keeps you off Google's SEO black-list by providing a firewall, brute force protection, continuous scanning and many other security enhancements.
1166
  </p>
1167
  <p>
1134
  </div>
1135
  </script>
1136
 
1137
+ <script type="text/x-jquery-template" id="issueTmpl_timelimit">
1138
+ <div>
1139
+ <div class="wfIssue">
1140
+ <h2>${shortMsg}</h2>
1141
+ <p>
1142
+ <table border="0" class="wfIssue" cellspacing="0" cellpadding="0">
1143
+ <tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
1144
+ <tr><th>Status</th><td>
1145
+ {{if status == 'new' }}New{{/if}}
1146
+ {{if status == 'ignoreC' }}This issue will be ignored until it changes.{{/if}}
1147
+ {{if status == 'ignoreP' }}This issue is permanently ignored.{{/if}}
1148
+ </td></tr>
1149
+ </table>
1150
+ </p>
1151
+ <p>
1152
+ {{html longMsg}}
1153
+ </p>
1154
+ <div class="wfIssueOptions">
1155
+ {{if status == 'new'}}
1156
+ <strong>Resolve:</strong>
1157
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">I have fixed this issue</a>
1158
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreP'); return false;">Ignore this problem</a>
1159
+ {{/if}}
1160
+ {{if status == 'ignoreP' || status == 'ignoreC'}}
1161
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
1162
+ {{/if}}
1163
+ </div>
1164
+ </div>
1165
+ </div>
1166
+ </script>
1167
+
1168
 
1169
 
1170
 
1192
  <div>
1193
  <h3>Welcome to Wordfence</h3>
1194
  <p>
1195
+ Wordfence is a robust and complete security system for WordPress. It protects your WordPress site
1196
  from security threats and keeps you off Google's SEO black-list by providing a firewall, brute force protection, continuous scanning and many other security enhancements.
1197
  </p>
1198
  <p>
lib/wfConfig.php CHANGED
@@ -92,7 +92,7 @@ class wfConfig {
92
  "otherParams" => array(
93
  "scan_include_extra" => "",
94
  // 'securityLevel' => '2',
95
- "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'scan_maxIssues' => 1000, 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
96
  'liveTraf_maxRows' => 2000,
97
  "neverBlockBG" => "neverBlockVerified",
98
  "loginSec_countFailMins" => "240",
@@ -429,6 +429,7 @@ class wfConfig {
429
 
430
  global $wpdb;
431
  $dbh = $wpdb->dbh;
 
432
 
433
  if (!self::$tableExists) {
434
  return;
@@ -443,21 +444,26 @@ class wfConfig {
443
  $data = serialize($val);
444
  }
445
 
446
- if (!$wpdb->use_mysqli) {
447
  $data = bin2hex($data);
448
  }
449
 
450
  $dataLength = strlen($data);
451
- $chunkSize = intval((self::getDB()->getMaxAllowedPacketBytes() - 50) / 1.2); //Based on max_allowed_packet + 20% for escaping and SQL
 
452
  $chunkSize = $chunkSize - ($chunkSize % 2); //Ensure it's even
453
  $chunkedValueKey = self::ser_chunked_key($key);
454
  if ($dataLength > $chunkSize) {
455
  $chunks = 0;
456
  while (($chunks * $chunkSize) < $dataLength) {
457
  $dataChunk = substr($data, $chunks * $chunkSize, $chunkSize);
458
- if ($wpdb->use_mysqli) {
459
  $chunkKey = $chunkedValueKey . $chunks;
460
  $stmt = $dbh->prepare("INSERT IGNORE INTO " . self::table() . " (name, val, autoload) VALUES (?, ?, 'no')");
 
 
 
 
461
  $null = NULL;
462
  $stmt->bind_param("sb", $chunkKey, $null);
463
 
@@ -489,14 +495,22 @@ class wfConfig {
489
  else {
490
  $exists = self::getDB()->querySingle("select name from " . self::table() . " where name='%s'", $key);
491
 
492
- if ($wpdb->use_mysqli) {
493
  if ($exists) {
494
  $stmt = $dbh->prepare("UPDATE " . self::table() . " SET val=? WHERE name=?");
 
 
 
 
495
  $null = NULL;
496
  $stmt->bind_param("bs", $null, $key);
497
  }
498
  else {
499
  $stmt = $dbh->prepare("INSERT IGNORE INTO " . self::table() . " (val, name, autoload) VALUES (?, ?, ?)");
 
 
 
 
500
  $null = NULL;
501
  $stmt->bind_param("bss", $null, $key, $autoload);
502
  }
92
  "otherParams" => array(
93
  "scan_include_extra" => "",
94
  // 'securityLevel' => '2',
95
+ "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'scan_maxIssues' => 1000, 'scan_maxDuration' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
96
  'liveTraf_maxRows' => 2000,
97
  "neverBlockBG" => "neverBlockVerified",
98
  "loginSec_countFailMins" => "240",
429
 
430
  global $wpdb;
431
  $dbh = $wpdb->dbh;
432
+ $useMySQLi = (is_object($dbh) && $wpdb->use_mysqli);
433
 
434
  if (!self::$tableExists) {
435
  return;
444
  $data = serialize($val);
445
  }
446
 
447
+ if (!$useMySQLi) {
448
  $data = bin2hex($data);
449
  }
450
 
451
  $dataLength = strlen($data);
452
+ $maxAllowedPacketBytes = self::getDB()->getMaxAllowedPacketBytes();
453
+ $chunkSize = intval((($maxAllowedPacketBytes < 1024 /* MySQL minimum, probably failure to fetch it */ ? 1024 * 1024 /* MySQL default */ : $maxAllowedPacketBytes) - 50) / 1.2); //Based on max_allowed_packet + 20% for escaping and SQL
454
  $chunkSize = $chunkSize - ($chunkSize % 2); //Ensure it's even
455
  $chunkedValueKey = self::ser_chunked_key($key);
456
  if ($dataLength > $chunkSize) {
457
  $chunks = 0;
458
  while (($chunks * $chunkSize) < $dataLength) {
459
  $dataChunk = substr($data, $chunks * $chunkSize, $chunkSize);
460
+ if ($useMySQLi) {
461
  $chunkKey = $chunkedValueKey . $chunks;
462
  $stmt = $dbh->prepare("INSERT IGNORE INTO " . self::table() . " (name, val, autoload) VALUES (?, ?, 'no')");
463
+ if ($stmt === false) {
464
+ wordfence::status(2, 'error', "Error writing value chunk for {$key} (MySQLi error: [{$dbh->errno}] {$dbh->error})");
465
+ return false;
466
+ }
467
  $null = NULL;
468
  $stmt->bind_param("sb", $chunkKey, $null);
469
 
495
  else {
496
  $exists = self::getDB()->querySingle("select name from " . self::table() . " where name='%s'", $key);
497
 
498
+ if ($useMySQLi) {
499
  if ($exists) {
500
  $stmt = $dbh->prepare("UPDATE " . self::table() . " SET val=? WHERE name=?");
501
+ if ($stmt === false) {
502
+ wordfence::status(2, 'error', "Error writing value for {$key} (MySQLi error: [{$dbh->errno}] {$dbh->error})");
503
+ return false;
504
+ }
505
  $null = NULL;
506
  $stmt->bind_param("bs", $null, $key);
507
  }
508
  else {
509
  $stmt = $dbh->prepare("INSERT IGNORE INTO " . self::table() . " (val, name, autoload) VALUES (?, ?, ?)");
510
+ if ($stmt === false) {
511
+ wordfence::status(2, 'error', "Error writing value for {$key} (MySQLi error: [{$dbh->errno}] {$dbh->error})");
512
+ return false;
513
+ }
514
  $null = NULL;
515
  $stmt->bind_param("bss", $null, $key, $autoload);
516
  }
lib/wfDB.php CHANGED
@@ -84,7 +84,7 @@ class wfDB {
84
  }
85
  public function getMaxAllowedPacketBytes(){
86
  $rec = $this->querySingleRec("show variables like 'max_allowed_packet'");
87
- return $rec['Value'];
88
  }
89
  public function getMaxLongDataSizeBytes() {
90
  $rec = $this->querySingleRec("show variables like 'max_long_data_size'");
84
  }
85
  public function getMaxAllowedPacketBytes(){
86
  $rec = $this->querySingleRec("show variables like 'max_allowed_packet'");
87
+ return intval($rec['Value']);
88
  }
89
  public function getMaxLongDataSizeBytes() {
90
  $rec = $this->querySingleRec("show variables like 'max_long_data_size'");
lib/wfIssues.php CHANGED
@@ -84,7 +84,7 @@ class wfIssues {
84
  public function ignoreAllNew(){
85
  $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
86
  }
87
- public function emailNewIssues(){
88
  $level = wfConfig::getAlertLevel();
89
  $emails = wfConfig::getAlertEmails();
90
  $shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
@@ -141,6 +141,7 @@ class wfIssues {
141
  'level' => $level,
142
  'issuesNotShown' => $overflowCount,
143
  'adminURL' => get_admin_url(),
 
144
  ));
145
 
146
  wp_mail(implode(',', $emails), $subject, $content, 'Content-type: text/html');
84
  public function ignoreAllNew(){
85
  $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
86
  }
87
+ public function emailNewIssues($timeLimitReached = false){
88
  $level = wfConfig::getAlertLevel();
89
  $emails = wfConfig::getAlertEmails();
90
  $shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
141
  'level' => $level,
142
  'issuesNotShown' => $overflowCount,
143
  'adminURL' => get_admin_url(),
144
+ 'timeLimitReached' => $timeLimitReached,
145
  ));
146
 
147
  wp_mail(implode(',', $emails), $subject, $content, 'Content-type: text/html');
lib/wfScan.php CHANGED
@@ -89,7 +89,15 @@ class wfScan {
89
  }
90
  try {
91
  $scan->go();
92
- } catch (Exception $e){
 
 
 
 
 
 
 
 
93
  wfUtils::clearScanLock();
94
  self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
95
  self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
89
  }
90
  try {
91
  $scan->go();
92
+ }
93
+ catch (wfScanEngineDurationLimitException $e) {
94
+ wfUtils::clearScanLock();
95
+ self::logPeakMemory();
96
+ self::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
97
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
98
+ exit();
99
+ }
100
+ catch (Exception $e){
101
  wfUtils::clearScanLock();
102
  self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
103
  self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
lib/wfScanEngine.php CHANGED
@@ -130,7 +130,18 @@ class wfScanEngine {
130
  $this->recordMetric('scan', 'duration', (time() - $this->startTime));
131
  $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0));
132
  $this->submitMetrics();
133
- } catch(Exception $e){
 
 
 
 
 
 
 
 
 
 
 
134
  wfConfig::set('lastScanCompleted', $e->getMessage());
135
  $this->recordMetric('scan', 'duration', (time() - $this->startTime));
136
  $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0));
@@ -139,8 +150,29 @@ class wfScanEngine {
139
  throw $e;
140
  }
141
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  public function forkIfNeeded(){
143
  self::checkForKill();
 
144
  if(time() - $this->cycleStartTime > $this->maxExecTime){
145
  wordfence::status(4, 'info', "Forking during hash scan to ensure continuity.");
146
  $this->fork();
@@ -154,8 +186,8 @@ class wfScanEngine {
154
  } //Otherwise there was an error so don't start another scan.
155
  exit(0);
156
  }
157
- public function emailNewIssues(){
158
- $this->i->emailNewIssues();
159
  }
160
  public function submitMetrics() {
161
  if (wfConfig::get('other_WFNet', true)) {
@@ -461,18 +493,20 @@ class wfScanEngine {
461
  if(! is_array($baseContents)){
462
  throw new Exception("Wordfence could not read the contents of your base WordPress directory. This usually indicates your permissions are so strict that your web server can't read your WordPress directory.");
463
  }
 
 
464
  $scanOutside = wfConfig::get('other_scanOutside');
465
- if($scanOutside){
466
  wordfence::status(2, 'info', "Including files that are outside the WordPress installation in the scan.");
 
467
  }
468
- $includeInKnownFilesScan = array();
469
- foreach($baseContents as $file){ //Only include base files less than a meg that are files.
470
- if($file == '.' || $file == '..'){ continue; }
471
- $fullFile = rtrim(ABSPATH, '/') . '/' . $file;
472
- if($scanOutside){
473
- $includeInKnownFilesScan[] = $file;
474
- } else if(in_array($file, $baseWPStuff) || (@is_file($fullFile) && @is_readable($fullFile) && (! wfUtils::fileTooBig($fullFile)) ) ){
475
- $includeInKnownFilesScan[] = $file;
476
  }
477
  }
478
 
@@ -1163,7 +1197,7 @@ class wfScanEngine {
1163
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
1164
  }
1165
  public static function requestKill(){
1166
- wfConfig::set('wfKillRequested', time());
1167
  }
1168
  public static function checkForKill(){
1169
  $kill = wfConfig::get('wfKillRequested', 0);
@@ -1175,7 +1209,7 @@ class wfScanEngine {
1175
  public static function startScan($isFork = false){
1176
  if(! $isFork){ //beginning of scan
1177
  wfConfig::inc('totalScansRun');
1178
- wfConfig::set('wfKillRequested', 0);
1179
  wordfence::status(4, 'info', "Entering start scan routine");
1180
  if(wfUtils::isScanRunning()){
1181
  wfUtils::getScanFileError();
@@ -1621,4 +1655,7 @@ class wfCommonBackupFileTest {
1621
 
1622
  class wfPubliclyAccessibleFileTest extends wfCommonBackupFileTest {
1623
 
 
 
 
1624
  }
130
  $this->recordMetric('scan', 'duration', (time() - $this->startTime));
131
  $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0));
132
  $this->submitMetrics();
133
+ }
134
+ catch (wfScanEngineDurationLimitException $e) {
135
+ wfConfig::set('lastScanCompleted', $e->getMessage());
136
+ $this->i->setScanTimeNow();
137
+
138
+ $this->emailNewIssues(true);
139
+ $this->recordMetric('scan', 'duration', (time() - $this->startTime));
140
+ $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0));
141
+ $this->submitMetrics();
142
+ throw $e;
143
+ }
144
+ catch(Exception $e) {
145
  wfConfig::set('lastScanCompleted', $e->getMessage());
146
  $this->recordMetric('scan', 'duration', (time() - $this->startTime));
147
  $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0));
150
  throw $e;
151
  }
152
  }
153
+ public function checkForDurationLimit() {
154
+ $timeLimit = intval(wfConfig::get('scan_maxDuration'));
155
+ if ($timeLimit < 1) {
156
+ $timeLimit = WORDFENCE_DEFAULT_MAX_SCAN_TIME;
157
+ }
158
+
159
+ if ((time() - $this->startTime) > $timeLimit){
160
+ $error = 'The scan time limit of ' . wfUtils::makeDuration($timeLimit) . ' has been exceeded and the scan will be terminated. This limit can be customized on the options page. <a href="http://docs.wordfence.com/en/Scan_time_limit" target="_blank">Get More Information</a>';
161
+ $this->addIssue('timelimit', 1, md5($this->startTime), md5($this->startTime), 'Scan Time Limit Exceeded', $error, array());
162
+ $summary = $this->i->getSummaryItems();
163
+ $this->status(1, 'info', '-------------------');
164
+ $this->status(1, 'info', "Scan interrupted. Scanned " . $summary['totalFiles'] . " files, " . $summary['totalPlugins'] . " plugins, " . $summary['totalThemes'] . " themes, " . ($summary['totalPages'] + $summary['totalPosts']) . " pages, " . $summary['totalComments'] . " comments and " . $summary['totalRows'] . " records in " . wfUtils::makeDuration(time() - $this->startTime, true) . ".");
165
+ if($this->i->totalIssues > 0){
166
+ $this->status(10, 'info', "SUM_FINAL:Scan interrupted. You have " . $this->i->totalIssues . " new issues to fix. See below.");
167
+ } else {
168
+ $this->status(10, 'info', "SUM_FINAL:Scan interrupted. No problems found prior to stopping.");
169
+ }
170
+ throw new wfScanEngineDurationLimitException($error);
171
+ }
172
+ }
173
  public function forkIfNeeded(){
174
  self::checkForKill();
175
+ $this->checkForDurationLimit();
176
  if(time() - $this->cycleStartTime > $this->maxExecTime){
177
  wordfence::status(4, 'info', "Forking during hash scan to ensure continuity.");
178
  $this->fork();
186
  } //Otherwise there was an error so don't start another scan.
187
  exit(0);
188
  }
189
+ public function emailNewIssues($timeLimitReached = false){
190
+ $this->i->emailNewIssues($timeLimitReached);
191
  }
192
  public function submitMetrics() {
193
  if (wfConfig::get('other_WFNet', true)) {
493
  if(! is_array($baseContents)){
494
  throw new Exception("Wordfence could not read the contents of your base WordPress directory. This usually indicates your permissions are so strict that your web server can't read your WordPress directory.");
495
  }
496
+
497
+ $includeInKnownFilesScan = array();
498
  $scanOutside = wfConfig::get('other_scanOutside');
499
+ if ($scanOutside) {
500
  wordfence::status(2, 'info', "Including files that are outside the WordPress installation in the scan.");
501
+ $includeInKnownFilesScan[] = ''; //Ends up as a literal ABSPATH
502
  }
503
+ else {
504
+ foreach ($baseContents as $file) { //Only include base files less than a meg that are files.
505
+ if($file == '.' || $file == '..'){ continue; }
506
+ $fullFile = rtrim(ABSPATH, '/') . '/' . $file;
507
+ if (in_array($file, $baseWPStuff) || (@is_file($fullFile) && @is_readable($fullFile) && (!wfUtils::fileTooBig($fullFile)))) {
508
+ $includeInKnownFilesScan[] = $file;
509
+ }
 
510
  }
511
  }
512
 
1197
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
1198
  }
1199
  public static function requestKill(){
1200
+ wfConfig::set('wfKillRequested', time(), wfConfig::DONT_AUTOLOAD);
1201
  }
1202
  public static function checkForKill(){
1203
  $kill = wfConfig::get('wfKillRequested', 0);
1209
  public static function startScan($isFork = false){
1210
  if(! $isFork){ //beginning of scan
1211
  wfConfig::inc('totalScansRun');
1212
+ wfConfig::set('wfKillRequested', 0, wfConfig::DONT_AUTOLOAD);
1213
  wordfence::status(4, 'info', "Entering start scan routine");
1214
  if(wfUtils::isScanRunning()){
1215
  wfUtils::getScanFileError();
1655
 
1656
  class wfPubliclyAccessibleFileTest extends wfCommonBackupFileTest {
1657
 
1658
+ }
1659
+
1660
+ class wfScanEngineDurationLimitException extends Exception {
1661
  }
lib/wfSchema.php CHANGED
@@ -24,8 +24,10 @@ class wfSchema {
24
  KEY k1(wfsn)
25
  ) default charset=utf8",
26
  "wfConfig" => "(
27
- name varchar(100) PRIMARY KEY NOT NULL,
28
- val longblob
 
 
29
  ) default charset=utf8",
30
  "wfCrawlers" => "(
31
  IP INT UNSIGNED NOT NULL,
@@ -177,6 +179,11 @@ class wfSchema {
177
  KEY `expiration` (`expiration`),
178
  KEY `IP` (`IP`)
179
  ) DEFAULT CHARSET=utf8;",
 
 
 
 
 
180
  /*
181
  'wfPerfLog' => "(
182
  id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
24
  KEY k1(wfsn)
25
  ) default charset=utf8",
26
  "wfConfig" => "(
27
+ `name` varchar(100) NOT NULL,
28
+ `val` longblob,
29
+ `autoload` enum('no','yes') NOT NULL DEFAULT 'yes',
30
+ PRIMARY KEY (`name`)
31
  ) default charset=utf8",
32
  "wfCrawlers" => "(
33
  IP INT UNSIGNED NOT NULL,
179
  KEY `expiration` (`expiration`),
180
  KEY `IP` (`IP`)
181
  ) DEFAULT CHARSET=utf8;",
182
+ 'wfKnownFileList' => "(
183
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
184
+ `path` text NOT NULL,
185
+ PRIMARY KEY (`id`)
186
+ ) DEFAULT CHARSET=utf8;",
187
  /*
188
  'wfPerfLog' => "(
189
  id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
lib/wfUtils.php CHANGED
@@ -570,6 +570,7 @@ class wfUtils {
570
  if(strpos($item, $char) !== false){
571
  $sp = explode($char, $item);
572
  foreach($sp as $j){
 
573
  if (!self::isValidIP($j)) {
574
  $j = preg_replace('/:\d+$/', '', $j); //Strip off port
575
  }
@@ -845,7 +846,7 @@ class wfUtils {
845
  public static function getScanLock(){
846
  //Windows does not support non-blocking flock, so we use time.
847
  $scanRunning = wfConfig::get('wf_scanRunning');
848
- if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
849
  return false;
850
  }
851
  wfConfig::set('wf_scanRunning', time());
@@ -860,7 +861,7 @@ class wfUtils {
860
  }
861
  public static function isScanRunning(){
862
  $scanRunning = wfConfig::get('wf_scanRunning');
863
- if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
864
  return true;
865
  } else {
866
  return false;
570
  if(strpos($item, $char) !== false){
571
  $sp = explode($char, $item);
572
  foreach($sp as $j){
573
+ $j = trim($j);
574
  if (!self::isValidIP($j)) {
575
  $j = preg_replace('/:\d+$/', '', $j); //Strip off port
576
  }
846
  public static function getScanLock(){
847
  //Windows does not support non-blocking flock, so we use time.
848
  $scanRunning = wfConfig::get('wf_scanRunning');
849
+ if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_LOCK_TIME){
850
  return false;
851
  }
852
  wfConfig::set('wf_scanRunning', time());
861
  }
862
  public static function isScanRunning(){
863
  $scanRunning = wfConfig::get('wf_scanRunning');
864
+ if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_LOCK_TIME){
865
  return true;
866
  } else {
867
  return false;
lib/wordfenceClass.php CHANGED
@@ -1404,7 +1404,7 @@ SQL
1404
  }
1405
  else { //Code path for old method, invalid password the second time
1406
  self::$authError = $authUser;
1407
- if (is_wp_error(self::$authError) && (self::$authError->get_error_code() == 'invalid_username' || self::$authError->get_error_code() == 'incorrect_password') && wfConfig::get('loginSec_maskLoginErrors')) {
1408
  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()));
1409
  }
1410
 
@@ -1694,7 +1694,7 @@ SQL
1694
  if(self::getLog()->isWhitelisted($IP)){
1695
  return $authUser;
1696
  }
1697
- if(wfConfig::get('other_WFNet') && is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'incorrect_password' || $authUser->get_error_code() == 'twofactor_invalid') ){
1698
  if($maxBlockTime = self::wfsnIsBlocked($IP, 'brute')){
1699
  self::getLog()->blockIP($IP, "Blocked by Wordfence Security Network", true, false, $maxBlockTime);
1700
  $secsToGo = wfConfig::get('blockedTime');
@@ -1704,7 +1704,7 @@ SQL
1704
 
1705
  }
1706
  if($secEnabled){
1707
- if(is_wp_error($authUser) && $authUser->get_error_code() == 'invalid_username'){
1708
  if($blacklist = wfConfig::get('loginSec_userBlacklist')){
1709
  $users = explode("\n", wfUtils::cleanupOneEntryPerLine($blacklist));
1710
  foreach($users as $user){
@@ -1725,7 +1725,7 @@ SQL
1725
  }
1726
  }
1727
  $tKey = 'wflginfl_' . bin2hex(wfUtils::inet_pton($IP));
1728
- if(is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'incorrect_password' || $authUser->get_error_code() == 'twofactor_invalid') ){
1729
  $tries = get_transient($tKey);
1730
  if($tries){
1731
  $tries++;
@@ -1742,14 +1742,14 @@ SQL
1742
  }
1743
  }
1744
  if(is_wp_error($authUser)){
1745
- if($authUser->get_error_code() == 'invalid_username'){
1746
  self::getLog()->logLogin('loginFailInvalidUsername', 1, $username);
1747
  } else {
1748
  self::getLog()->logLogin('loginFailValidUsername', 1, $username);
1749
  }
1750
  }
1751
 
1752
- if(is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'incorrect_password') && wfConfig::get('loginSec_maskLoginErrors')){
1753
  return 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() ) );
1754
  }
1755
  return $authUser;
@@ -1920,6 +1920,8 @@ SQL
1920
  if(self::isLockedOut(wfUtils::getIP())){
1921
  require('wfLockedOut.php');
1922
  }
 
 
1923
  }
1924
  public static function authActionNew(&$username, &$passwd){ //As of php 5.4 we must denote passing by ref in the function definition, not the function call (as WordPress core does, which is a bug in WordPress).
1925
  if(self::isLockedOut(wfUtils::getIP())){
@@ -4007,6 +4009,11 @@ wfscr.src = url;
4007
  EOL;
4008
  }
4009
  public static function wfLogHumanHeader(){
 
 
 
 
 
4010
  self::$hitID = self::getLog()->logHit();
4011
  if (self::$hitID) {
4012
  $URL = home_url('/?wordfence_logHuman=1&hid=' . wfUtils::encrypt(self::$hitID));
1404
  }
1405
  else { //Code path for old method, invalid password the second time
1406
  self::$authError = $authUser;
1407
+ 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')) {
1408
  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()));
1409
  }
1410
 
1694
  if(self::getLog()->isWhitelisted($IP)){
1695
  return $authUser;
1696
  }
1697
+ if(wfConfig::get('other_WFNet') && is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'invalid_email' || $authUser->get_error_code() == 'incorrect_password' || $authUser->get_error_code() == 'twofactor_invalid' || $authUser->get_error_code() == 'authentication_failed') ){
1698
  if($maxBlockTime = self::wfsnIsBlocked($IP, 'brute')){
1699
  self::getLog()->blockIP($IP, "Blocked by Wordfence Security Network", true, false, $maxBlockTime);
1700
  $secsToGo = wfConfig::get('blockedTime');
1704
 
1705
  }
1706
  if($secEnabled){
1707
+ if(is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username') || ($authUser->get_error_code() == 'invalid_email')){
1708
  if($blacklist = wfConfig::get('loginSec_userBlacklist')){
1709
  $users = explode("\n", wfUtils::cleanupOneEntryPerLine($blacklist));
1710
  foreach($users as $user){
1725
  }
1726
  }
1727
  $tKey = 'wflginfl_' . bin2hex(wfUtils::inet_pton($IP));
1728
+ if(is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'invalid_email' || $authUser->get_error_code() == 'incorrect_password' || $authUser->get_error_code() == 'twofactor_invalid' || $authUser->get_error_code() == 'authentication_failed') ){
1729
  $tries = get_transient($tKey);
1730
  if($tries){
1731
  $tries++;
1742
  }
1743
  }
1744
  if(is_wp_error($authUser)){
1745
+ if($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'invalid_email'){
1746
  self::getLog()->logLogin('loginFailInvalidUsername', 1, $username);
1747
  } else {
1748
  self::getLog()->logLogin('loginFailValidUsername', 1, $username);
1749
  }
1750
  }
1751
 
1752
+ if(is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'invalid_email' || $authUser->get_error_code() == 'incorrect_password') && wfConfig::get('loginSec_maskLoginErrors')){
1753
  return 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() ) );
1754
  }
1755
  return $authUser;
1920
  if(self::isLockedOut(wfUtils::getIP())){
1921
  require('wfLockedOut.php');
1922
  }
1923
+
1924
+ self::doEarlyAccessLogging(); //Rate limiting
1925
  }
1926
  public static function authActionNew(&$username, &$passwd){ //As of php 5.4 we must denote passing by ref in the function definition, not the function call (as WordPress core does, which is a bug in WordPress).
1927
  if(self::isLockedOut(wfUtils::getIP())){
4009
  EOL;
4010
  }
4011
  public static function wfLogHumanHeader(){
4012
+ //Final check in case this was added as an action before the request was fully initialized
4013
+ if (self::getLog()->getCurrentRequest()->jsRun || !wfConfig::liveTrafficEnabled()) {
4014
+ return;
4015
+ }
4016
+
4017
  self::$hitID = self::getLog()->logHit();
4018
  if (self::$hitID) {
4019
  $URL = home_url('/?wordfence_logHuman=1&hid=' . wfUtils::encrypt(self::$hitID));
lib/wordfenceConstants.php CHANGED
@@ -5,7 +5,8 @@ define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
  define('WORDFENCE_API_URL_BASE_SEC', WORDFENCE_API_URL_SEC . 'v' . WORDFENCE_API_VERSION . '/');
6
  define('WORDFENCE_API_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . 'v' . WORDFENCE_API_VERSION . '/');
7
  define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com/');
8
- define('WORDFENCE_MAX_SCAN_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
 
9
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
10
  define('WORDFENCE_MAX_IPLOC_AGE', 86400); //1 day
11
  define('WORDFENCE_CRAWLER_VERIFY_CACHE_TIME', 604800);
5
  define('WORDFENCE_API_URL_BASE_SEC', WORDFENCE_API_URL_SEC . 'v' . WORDFENCE_API_VERSION . '/');
6
  define('WORDFENCE_API_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . 'v' . WORDFENCE_API_VERSION . '/');
7
  define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com/');
8
+ define('WORDFENCE_MAX_SCAN_LOCK_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
9
+ define('WORDFENCE_DEFAULT_MAX_SCAN_TIME', 10800);
10
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
11
  define('WORDFENCE_MAX_IPLOC_AGE', 86400); //1 day
12
  define('WORDFENCE_CRAWLER_VERIFY_CACHE_TIME', 604800);
lib/wordfenceHash.php CHANGED
@@ -25,9 +25,12 @@ class wordfenceHash {
25
  private $only = false;
26
  private $totalForks = 0;
27
  private $alertedOnUnknownWordPressVersion = false;
 
28
  private $foldersProcessed = array();
29
- private $filesProcessedBloomFilter = false;
30
  private $suspectedFiles = array();
 
 
 
31
 
32
  /**
33
  * @param string $striplen
@@ -61,14 +64,13 @@ class wordfenceHash {
61
  if(wfConfig::get('scansEnabled_coreUnknown')){
62
  $this->coreUnknownEnabled = true;
63
  }
64
-
65
- $this->filesProcessedBloomFilter = new wfMD5BloomFilter(65536, 8); //65536,8 produces the lowest miss probability. Uses approximately 8 KB of memory + PHP object overhead
66
 
67
  $this->db = new wfDB();
68
 
69
  //Doing a delete for now. Later we can optimize this to only scan modified files.
70
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
71
  $this->db->truncate($this->db->prefix() . "wfFileMods");
 
72
  $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
73
  try {
74
  $this->knownFiles = $this->engine->getKnownFilesLoader()
@@ -116,7 +118,7 @@ class wordfenceHash {
116
  if($this->coreUnknownEnabled){ $this->status['coreUnknown'] = wordfence::statusStart("Scanning for unknown files in wp-admin and wp-includes"); } else { wordfence::statusDisabled("Skipping unknown core file scan"); }
117
  }
118
  public function __sleep(){
119
- return array('striplen', 'totalFiles', 'totalDirs', 'totalData', 'stoppedOnFile', 'coreEnabled', 'pluginsEnabled', 'themesEnabled', 'malwareEnabled', 'coreUnknownEnabled', 'knownFiles', 'malwareData', 'haveIssues', 'status', 'possibleMalware', 'path', 'only', 'totalForks', 'alertedOnUnknownWordPressVersion', 'foldersProcessed', 'filesProcessedBloomFilter', 'suspectedFiles');
120
  }
121
  public function __wakeup(){
122
  $this->db = new wfDB();
@@ -131,16 +133,37 @@ class wordfenceHash {
131
  throw new Exception("Wordfence file scanner detected a possible infinite loop. Exiting on file: " . $this->stoppedOnFile);
132
  }
133
  $this->engine = $engine;
134
- $files = scandir($this->path);
135
- foreach($files as $file){
136
- if($file == '.' || $file == '..'){ continue; }
137
- if(sizeof($this->only) > 0 && (! in_array($file, $this->only))){
138
- continue;
 
 
139
  }
140
- $file = $this->path . $file;
141
- wordfence::status(4, 'info', "Hashing item in base dir: $file");
142
- $this->_dirHash($file);
 
 
 
 
 
 
 
 
 
 
143
  }
 
 
 
 
 
 
 
 
 
144
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data.");
145
  if($this->coreEnabled){ wordfence::statusEnd($this->status['core'], $this->haveIssues['core']); }
146
  if($this->themesEnabled){ wordfence::statusEnd($this->status['themes'], $this->haveIssues['themes']); }
@@ -179,53 +202,139 @@ class wordfenceHash {
179
  }
180
  if($this->malwareEnabled){ wordfence::statusEnd($this->status['malware'], $this->haveIssues['malware']); }
181
  }
182
- private function _dirHash($path){
183
- if(substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.'){
184
  return;
185
  }
186
- if(! is_readable($path)){ return; } //Applies to files and dirs
187
  if (!$this->_shouldProcessPath($path)) { return; }
188
- $this->_checkForTimeout($path);
189
- if(is_dir($path)){
 
 
 
 
 
 
190
  $this->totalDirs++;
191
- if($path[strlen($path) - 1] != '/'){
192
  $path .= '/';
193
  }
194
  $cont = scandir($path);
195
- for($i = 0; $i < sizeof($cont); $i++){
196
- if($cont[$i] == '.' || $cont[$i] == '..'){ continue; }
197
  $file = $path . $cont[$i];
198
- if(is_file($file)){
199
- $this->_checkForTimeout($file);
200
- $this->processFile($file);
201
- } else if(is_dir($file)) {
202
- $this->_dirHash($file);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  }
204
  }
205
 
206
- $realPath = realpath($path);
207
- $this->foldersProcessed[$realPath] = 1;
208
- } else {
209
- if(is_file($path)){
210
- $this->processFile($path);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  }
212
  }
213
  }
214
- private function _checkForTimeout($path) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  $file = substr($path, $this->striplen);
216
  if ((!$this->stoppedOnFile) && microtime(true) - $this->startTime > $this->engine->maxExecTime) { //max X seconds but don't allow fork if we're looking for the file we stopped on. Search mode is VERY fast.
217
- $this->stoppedOnFile = $file;
218
- wordfence::status(4, 'info', "Calling fork() from wordfenceHash::processFile with maxExecTime: " . $this->engine->maxExecTime);
 
 
 
 
 
 
219
  $this->engine->fork();
220
  //exits
221
  }
222
 
223
- //Put this after the fork, that way we will at least scan one more file after we fork if it takes us more than 10 seconds to search for the stoppedOnFile
224
- if ($this->stoppedOnFile && $file != $this->stoppedOnFile) {
225
  return;
226
  }
227
  else if ($this->stoppedOnFile && $file == $this->stoppedOnFile) {
228
- $this->stoppedOnFile = false; //Continue scanning
229
  }
230
  }
231
  private function _shouldProcessPath($path) {
@@ -245,20 +354,11 @@ class wordfenceHash {
245
  private function processFile($realFile){
246
  $file = substr($realFile, $this->striplen);
247
 
248
- if (preg_match('/\.suspected$/i', $file)) { //Already iterating over all files in the search areas so generate this list here
249
- wordfence::status(4, 'info', "Found .suspected file: $file");
250
- $this->suspectedFiles[$file] = 1;
251
- }
252
-
253
- if (!$this->_shouldHashFile($realFile)) {
254
- wordfence::status(4, 'info', "Skipping unneeded hash: {$realFile}");
255
- return;
256
- }
257
-
258
  if(wfUtils::fileTooBig($realFile)){
259
  wordfence::status(4, 'info', "Skipping file larger than max size: $realFile");
260
  return;
261
  }
 
262
  if (function_exists('memory_get_usage')) {
263
  wordfence::status(4, 'info', "Scanning: $realFile (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
264
  } else {
@@ -437,11 +537,9 @@ class wordfenceHash {
437
  // we could split this into files who's path we recognize and file's who's path we recognize AND who have a valid sig.
438
  // But because we want to scan files who's sig we don't recognize, regardless of known path or not, we only need one "knownFile" field.
439
  $this->db->queryWrite("insert into " . $this->db->prefix() . "wfFileMods (filename, filenameMD5, knownFile, oldMD5, newMD5) values ('%s', unhex(md5('%s')), %d, '', unhex('%s')) ON DUPLICATE KEY UPDATE newMD5=unhex('%s'), knownFile=%d", $file, $file, $knownFile, $md5, $md5, $knownFile);
440
-
441
- $this->filesProcessedBloomFilter->add($file);
442
 
443
  $this->totalFiles++;
444
- $this->totalData += filesize($realFile); //We already checked if file overflows int in the fileTooBig routine above
445
  if($this->totalFiles % 100 === 0){
446
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data so far");
447
  }
@@ -471,16 +569,7 @@ class wordfenceHash {
471
  }
472
  private function _shouldHashFile($fullPath) {
473
  $file = substr($fullPath, $this->striplen);
474
-
475
- //Already hashed and processed, return false
476
- if ($this->filesProcessedBloomFilter->contains($file)) { //Might have hashed it, verify
477
- $rec = $this->db->querySingleRec("SELECT * FROM " . $this->db->prefix() . "wfFileMods WHERE filename = '%s'", $file);
478
- if ($rec !== null) {
479
- wordfence::status(4, 'info', "Already hashed: {$file}");
480
- return false;
481
- }
482
- }
483
-
484
  //Core File, return true
485
  if ((isset($this->knownFiles['core']) && isset($this->knownFiles['core'][$file])) ||
486
  (isset($this->knownFiles['plugins']) && isset($this->knownFiles['plugins'][$file])) ||
@@ -489,7 +578,7 @@ class wordfenceHash {
489
  }
490
 
491
  //Excluded file, return false
492
- $excludePattern = wordfenceScanner::getExcludeFilePattern(wordfenceScanner::EXCLUSION_PATTERNS_USER & wordfenceScanner::EXCLUSION_PATTERNS_MALWARE);
493
  if ($excludePattern && preg_match($excludePattern, $file)) {
494
  return false;
495
  }
25
  private $only = false;
26
  private $totalForks = 0;
27
  private $alertedOnUnknownWordPressVersion = false;
28
+ private $foldersEntered = array();
29
  private $foldersProcessed = array();
 
30
  private $suspectedFiles = array();
31
+ private $indexed = false;
32
+ private $indexSize = 0;
33
+ private $currentIndex = 0;
34
 
35
  /**
36
  * @param string $striplen
64
  if(wfConfig::get('scansEnabled_coreUnknown')){
65
  $this->coreUnknownEnabled = true;
66
  }
 
 
67
 
68
  $this->db = new wfDB();
69
 
70
  //Doing a delete for now. Later we can optimize this to only scan modified files.
71
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
72
  $this->db->truncate($this->db->prefix() . "wfFileMods");
73
+ $this->db->truncate($this->db->prefix() . "wfKnownFileList");
74
  $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
75
  try {
76
  $this->knownFiles = $this->engine->getKnownFilesLoader()
118
  if($this->coreUnknownEnabled){ $this->status['coreUnknown'] = wordfence::statusStart("Scanning for unknown files in wp-admin and wp-includes"); } else { wordfence::statusDisabled("Skipping unknown core file scan"); }
119
  }
120
  public function __sleep(){
121
+ return array('striplen', 'totalFiles', 'totalDirs', 'totalData', 'stoppedOnFile', 'coreEnabled', 'pluginsEnabled', 'themesEnabled', 'malwareEnabled', 'coreUnknownEnabled', 'knownFiles', 'malwareData', 'haveIssues', 'status', 'possibleMalware', 'path', 'only', 'totalForks', 'alertedOnUnknownWordPressVersion', 'foldersProcessed', 'suspectedFiles', 'indexed', 'indexSize', 'currentIndex', 'foldersEntered');
122
  }
123
  public function __wakeup(){
124
  $this->db = new wfDB();
133
  throw new Exception("Wordfence file scanner detected a possible infinite loop. Exiting on file: " . $this->stoppedOnFile);
134
  }
135
  $this->engine = $engine;
136
+ wordfence::status(4, 'info', "Indexing files for scanning");
137
+ if (!$this->indexed) {
138
+ $start = microtime(true);
139
+ $indexedFiles = array();
140
+
141
+ if (count($this->only) > 0) {
142
+ $files = $this->only;
143
  }
144
+ else {
145
+ $files = scandir($this->path);
146
+ }
147
+
148
+ foreach ($files as $file) {
149
+ if ($file == '.' || $file == '..') { continue; }
150
+ $file = $this->path . $file;
151
+ $this->_dirIndex($file, $indexedFiles);
152
+ }
153
+ $this->_serviceIndexQueue($indexedFiles, true);
154
+ $this->indexed = true;
155
+ $end = microtime(true);
156
+ wordfence::status(4, 'info', "Index time: " . ($end - $start));
157
  }
158
+
159
+ $this->_checkForTimeout('');
160
+
161
+ wordfence::status(4, 'info', "Beginning file hashing");
162
+ while ($file = $this->_nextFile()) {
163
+ $this->processFile($file);
164
+ $this->_checkForTimeout($file);
165
+ }
166
+
167
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data.");
168
  if($this->coreEnabled){ wordfence::statusEnd($this->status['core'], $this->haveIssues['core']); }
169
  if($this->themesEnabled){ wordfence::statusEnd($this->status['themes'], $this->haveIssues['themes']); }
202
  }
203
  if($this->malwareEnabled){ wordfence::statusEnd($this->status['malware'], $this->haveIssues['malware']); }
204
  }
205
+ private function _dirIndex($path, &$indexedFiles) {
206
+ if (substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.') {
207
  return;
208
  }
209
+ if (!is_readable($path)) { return; } //Applies to files and dirs
210
  if (!$this->_shouldProcessPath($path)) { return; }
211
+ if (is_dir($path)) {
212
+ $realPath = realpath($path);
213
+ if (!$this->stoppedOnFile && isset($this->foldersEntered[$realPath])) { //Not resuming and already entered this path
214
+ return;
215
+ }
216
+
217
+ $this->foldersEntered[$realPath] = 1;
218
+
219
  $this->totalDirs++;
220
+ if ($path[strlen($path) - 1] != '/') {
221
  $path .= '/';
222
  }
223
  $cont = scandir($path);
224
+ for ($i = 0; $i < sizeof($cont); $i++) {
225
+ if ($cont[$i] == '.' || $cont[$i] == '..') { continue; }
226
  $file = $path . $cont[$i];
227
+ if (is_file($file)) {
228
+ $relativeFile = substr($file, $this->striplen);
229
+ if ($this->stoppedOnFile && $relativeFile != $this->stoppedOnFile) {
230
+ continue;
231
+ }
232
+
233
+ if (preg_match('/\.suspected$/i', $relativeFile)) { //Already iterating over all files in the search areas so generate this list here
234
+ wordfence::status(4, 'info', "Found .suspected file: {$relativeFile}");
235
+ $this->suspectedFiles[$relativeFile] = 1;
236
+ }
237
+
238
+ $this->_checkForTimeout($file, $indexedFiles);
239
+ if ($this->_shouldHashFile($file)) {
240
+ $indexedFiles[] = $relativeFile;
241
+ }
242
+ else {
243
+ wordfence::status(4, 'info', "Skipping unneeded hash: {$file}");
244
+ }
245
+ $this->_serviceIndexQueue($indexedFiles);
246
+ } else if (is_dir($file)) {
247
+ $this->_dirIndex($file, $indexedFiles);
248
  }
249
  }
250
 
251
+ $this->foldersProcessed[$realPath] = 1;
252
+ unset($this->foldersEntered[$realPath]);
253
+ }
254
+ else {
255
+ if (is_file($path)) {
256
+ $relativeFile = substr($path, $this->striplen);
257
+ if ($this->stoppedOnFile && $relativeFile != $this->stoppedOnFile) {
258
+ return;
259
+ }
260
+
261
+ if (preg_match('/\.suspected$/i', $relativeFile)) { //Already iterating over all files in the search areas so generate this list here
262
+ wordfence::status(4, 'info', "Found .suspected file: {$relativeFile}");
263
+ $this->suspectedFiles[$relativeFile] = 1;
264
+ }
265
+
266
+ $this->_checkForTimeout($path, $indexedFiles);
267
+ if ($this->_shouldHashFile($path)) {
268
+ $indexedFiles[] = substr($path, $this->striplen);
269
+ }
270
+ else {
271
+ wordfence::status(4, 'info', "Skipping unneeded hash: {$path}");
272
+ }
273
+ $this->_serviceIndexQueue($indexedFiles);
274
  }
275
  }
276
  }
277
+ private function _serviceIndexQueue(&$indexedFiles, $final = false) {
278
+ $payload = array();
279
+ if (count($indexedFiles) > 500) {
280
+ $payload = array_splice($indexedFiles, 0, 500);
281
+ }
282
+ else if ($final) {
283
+ $payload = $indexedFiles;
284
+ $indexedFiles = array();
285
+ }
286
+
287
+ if (count($payload) > 0) {
288
+ global $wpdb;
289
+ $query = substr("INSERT INTO {$wpdb->base_prefix}wfKnownFileList (path) VALUES " . str_repeat("('%s'), ", count($payload)), 0, -2);
290
+ $wpdb->query($wpdb->prepare($query, $payload));
291
+ $this->indexSize += count($payload);
292
+ wordfence::status(2, 'info', "{$this->indexSize} files indexed");
293
+ }
294
+ }
295
+ private function _nextFile($advanceCursor = true) {
296
+ static $files = array();
297
+ if (count($files) == 0) {
298
+ global $wpdb;
299
+ $files = $wpdb->get_col($wpdb->prepare("SELECT path FROM {$wpdb->base_prefix}wfKnownFileList WHERE id > %d ORDER BY id ASC LIMIT 500", $this->currentIndex));
300
+ }
301
+
302
+ $file = null;
303
+ if ($advanceCursor) {
304
+ $file = array_shift($files);
305
+ $this->currentIndex++;
306
+ }
307
+ else if (count($files) > 0) {
308
+ $file = $files[0];
309
+ }
310
+
311
+ if ($file === null) {
312
+ return null;
313
+ }
314
+ return ABSPATH . $file;
315
+ }
316
+ private function _checkForTimeout($path, $indexQueue = false) {
317
+ $this->engine->checkForKill();
318
+ $this->engine->checkForDurationLimit();
319
  $file = substr($path, $this->striplen);
320
  if ((!$this->stoppedOnFile) && microtime(true) - $this->startTime > $this->engine->maxExecTime) { //max X seconds but don't allow fork if we're looking for the file we stopped on. Search mode is VERY fast.
321
+ if ($indexQueue !== false) {
322
+ $this->_serviceIndexQueue($indexQueue, true);
323
+ $this->stoppedOnFile = $file;
324
+ wordfence::status(4, 'info', "Forking during indexing: " . $path);
325
+ }
326
+ else {
327
+ wordfence::status(4, 'info', "Calling fork() from wordfenceHash with maxExecTime: " . $this->engine->maxExecTime);
328
+ }
329
  $this->engine->fork();
330
  //exits
331
  }
332
 
333
+ if ($this->stoppedOnFile && $file != $this->stoppedOnFile && $indexQueue !== false) {
 
334
  return;
335
  }
336
  else if ($this->stoppedOnFile && $file == $this->stoppedOnFile) {
337
+ $this->stoppedOnFile = false; //Continue indexing
338
  }
339
  }
340
  private function _shouldProcessPath($path) {
354
  private function processFile($realFile){
355
  $file = substr($realFile, $this->striplen);
356
 
 
 
 
 
 
 
 
 
 
 
357
  if(wfUtils::fileTooBig($realFile)){
358
  wordfence::status(4, 'info', "Skipping file larger than max size: $realFile");
359
  return;
360
  }
361
+
362
  if (function_exists('memory_get_usage')) {
363
  wordfence::status(4, 'info', "Scanning: $realFile (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
364
  } else {
537
  // we could split this into files who's path we recognize and file's who's path we recognize AND who have a valid sig.
538
  // But because we want to scan files who's sig we don't recognize, regardless of known path or not, we only need one "knownFile" field.
539
  $this->db->queryWrite("insert into " . $this->db->prefix() . "wfFileMods (filename, filenameMD5, knownFile, oldMD5, newMD5) values ('%s', unhex(md5('%s')), %d, '', unhex('%s')) ON DUPLICATE KEY UPDATE newMD5=unhex('%s'), knownFile=%d", $file, $file, $knownFile, $md5, $md5, $knownFile);
 
 
540
 
541
  $this->totalFiles++;
542
+ $this->totalData += @filesize($realFile); //We already checked if file overflows int in the fileTooBig routine above
543
  if($this->totalFiles % 100 === 0){
544
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data so far");
545
  }
569
  }
570
  private function _shouldHashFile($fullPath) {
571
  $file = substr($fullPath, $this->striplen);
572
+
 
 
 
 
 
 
 
 
 
573
  //Core File, return true
574
  if ((isset($this->knownFiles['core']) && isset($this->knownFiles['core'][$file])) ||
575
  (isset($this->knownFiles['plugins']) && isset($this->knownFiles['plugins'][$file])) ||
578
  }
579
 
580
  //Excluded file, return false
581
+ $excludePattern = wordfenceScanner::getExcludeFilePattern(wordfenceScanner::EXCLUSION_PATTERNS_USER | wordfenceScanner::EXCLUSION_PATTERNS_MALWARE);
582
  if ($excludePattern && preg_match($excludePattern, $file)) {
583
  return false;
584
  }
lib/wordfenceScanner.php CHANGED
@@ -177,7 +177,7 @@ class wordfenceScanner {
177
  }
178
  $db = new wfDB();
179
  $lastCount = 'whatever';
180
- $excludePattern = self::getExcludeFilePattern(self::EXCLUSION_PATTERNS_USER & self::EXCLUSION_PATTERNS_MALWARE);
181
  while(true){
182
  $thisCount = $db->querySingle("select count(*) from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0");
183
  if($thisCount == $lastCount){
@@ -195,6 +195,9 @@ class wordfenceScanner {
195
  if($excludePattern && preg_match($excludePattern, $file)){
196
  continue;
197
  }
 
 
 
198
  $fileSum = $rec1['newMD5'];
199
 
200
  $fileExt = '';
@@ -244,7 +247,7 @@ class wordfenceScanner {
244
  }
245
  wfUtils::beginProcessingFile($file);
246
 
247
- $fsize = filesize($this->path . $file); //Checked if too big above
248
  if($fsize > 1000000){
249
  $fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
250
  } else {
177
  }
178
  $db = new wfDB();
179
  $lastCount = 'whatever';
180
+ $excludePattern = self::getExcludeFilePattern(self::EXCLUSION_PATTERNS_USER | self::EXCLUSION_PATTERNS_MALWARE);
181
  while(true){
182
  $thisCount = $db->querySingle("select count(*) from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0");
183
  if($thisCount == $lastCount){
195
  if($excludePattern && preg_match($excludePattern, $file)){
196
  continue;
197
  }
198
+ if (!file_exists($this->path . $file)) {
199
+ continue;
200
+ }
201
  $fileSum = $rec1['newMD5'];
202
 
203
  $fileExt = '';
247
  }
248
  wfUtils::beginProcessingFile($file);
249
 
250
+ $fsize = @filesize($this->path . $file); //Checked if too big above
251
  if($fsize > 1000000){
252
  $fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
253
  } else {
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.6.1
6
- Stable tag: 6.2.3
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
@@ -190,6 +190,20 @@ Secure your website with Wordfence.
190
 
191
  == Changelog ==
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  = 6.2.3 =
194
  * Improvement: Reworked blocking for IP ranges, country blocking, and direct IP blocking to minimize server impact when under attack.
195
  * Improvement: Live traffic better indicates the action taken by country blocking when it redirects a visitor.
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.6.1
6
+ Stable tag: 6.2.4
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
190
 
191
  == Changelog ==
192
 
193
+ = 6.2.4 =
194
+ * Improvement: Scan times for very large sites with huge numbers of files are greatly improved.
195
+ * Improvement: Added a configurable time limit for scans to help reduce overall server load and identify configuration problems.
196
+ * Improvement: Email-based logins are now covered by "Don't let WordPress reveal valid users in login errors".
197
+ * Improvement: Extended rate limiting support to the login page.
198
+ * Fix: Fixed a case where files in the site root with issues could have them added multiple times.
199
+ * Fix: Improved IP detection in the WAF when using an IP detection method that can have multiple values.
200
+ * Fix: Added a safety check for when the database fails to return its max_allowed_packet value.
201
+ * Fix: Added safety checks for when the configuration table migration has failed.
202
+ * Fix: Added a couple rare failed login error codes to brute force detection.
203
+ * Fix: Fixed a sequencing problem when adding detection for bot/human that led to it being called on every request.
204
+ * Fix: Suppressed errors if a file is removed between the start of a scan and later scan stages.
205
+ * Fix: Addressed a problem where the scan exclusions list was not checked correctly in some situations.
206
+
207
  = 6.2.3 =
208
  * Improvement: Reworked blocking for IP ranges, country blocking, and direct IP blocking to minimize server impact when under attack.
209
  * Improvement: Live traffic better indicates the action taken by country blocking when it redirects a visitor.
waf/bootstrap.php CHANGED
@@ -31,15 +31,135 @@ class wfWAFWordPressRequest extends wfWAFRequest {
31
  public function getIP() {
32
  $howGet = wfWAF::getInstance()->getStorageEngine()->getConfig('howGetIPs');
33
  if (is_string($howGet) && is_array($_SERVER) && array_key_exists($howGet, $_SERVER)) {
34
- $ips[] = $_SERVER[$howGet];
35
  }
36
- $ips[] = $ip = (is_array($_SERVER) && array_key_exists('REMOTE_ADDR', $_SERVER)) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
37
- foreach ($ips as $ip) {
38
- if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
39
- return $ip;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
- return $ip;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
  }
45
 
31
  public function getIP() {
32
  $howGet = wfWAF::getInstance()->getStorageEngine()->getConfig('howGetIPs');
33
  if (is_string($howGet) && is_array($_SERVER) && array_key_exists($howGet, $_SERVER)) {
34
+ $ips[] = array($_SERVER[$howGet], $howGet);
35
  }
36
+ $ips[] = array((is_array($_SERVER) && array_key_exists('REMOTE_ADDR', $_SERVER)) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1', 'REMOTE_ADDR');
37
+ $cleanedIP = $this->_getCleanIPAndServerVar($ips);
38
+ if (is_array($cleanedIP)) {
39
+ list($ip, $variable) = $cleanedIP;
40
+ return $ip;
41
+ }
42
+ return $cleanedIP;
43
+ }
44
+
45
+ /**
46
+ * Expects an array of items. The items are either IPs or IPs separated by comma, space or tab. Or an array of IP's.
47
+ * We then examine all IP's looking for a public IP and storing private IP's in an array. If we find no public IPs we return the first private addr we found.
48
+ *
49
+ * @param array $arr
50
+ * @return bool|mixed
51
+ */
52
+ private function _getCleanIPAndServerVar($arr) {
53
+ $privates = array(); //Store private addrs until end as last resort.
54
+ foreach ($arr as $entry) {
55
+ list($item, $var) = $entry;
56
+ if (is_array($item)) {
57
+ foreach ($item as $j) {
58
+ // try verifying the IP is valid before stripping the port off
59
+ if (!$this->_isValidIP($j)) {
60
+ $j = preg_replace('/:\d+$/', '', $j); //Strip off port
61
+ }
62
+ if ($this->_isValidIP($j)) {
63
+ if ($this->_isIPv6MappedIPv4($j)) {
64
+ $j = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($j));
65
+ }
66
+
67
+ if ($this->_isPrivateIP($j)) {
68
+ $privates[] = array($j, $var);
69
+ }
70
+ else {
71
+ return array($j, $var);
72
+ }
73
+ }
74
+ }
75
+ continue; //This was an array so we can skip to the next item
76
  }
77
+ $skipToNext = false;
78
+ foreach (array(',', ' ', "\t") as $char) {
79
+ if (strpos($item, $char) !== false) {
80
+ $sp = explode($char, $item);
81
+ foreach ($sp as $j) {
82
+ $j = trim($j);
83
+ if (!$this->_isValidIP($j)) {
84
+ $j = preg_replace('/:\d+$/', '', $j); //Strip off port
85
+ }
86
+ if ($this->_isValidIP($j)) {
87
+ if ($this->_isIPv6MappedIPv4($j)) {
88
+ $j = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($j));
89
+ }
90
+
91
+ if ($this->_isPrivateIP($j)) {
92
+ $privates[] = array($j, $var);
93
+ }
94
+ else {
95
+ return array($j, $var);
96
+ }
97
+ }
98
+ }
99
+ $skipToNext = true;
100
+ break;
101
+ }
102
+ }
103
+ if ($skipToNext){ continue; } //Skip to next item because this one had a comma, space or tab so was delimited and we didn't find anything.
104
+
105
+ if (!$this->_isValidIP($item)) {
106
+ $item = preg_replace('/:\d+$/', '', $item); //Strip off port
107
+ }
108
+ if ($this->_isValidIP($item)) {
109
+ if ($this->_isIPv6MappedIPv4($item)) {
110
+ $item = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($item));
111
+ }
112
+
113
+ if ($this->_isPrivateIP($item)) {
114
+ $privates[] = array($item, $var);
115
+ }
116
+ else {
117
+ return array($item, $var);
118
+ }
119
+ }
120
+ }
121
+ if (sizeof($privates) > 0) {
122
+ return $privates[0]; //Return the first private we found so that we respect the order the IP's were passed to this function.
123
  }
124
+ return false;
125
+ }
126
+
127
+ /**
128
+ * @param string $ip
129
+ * @return bool
130
+ */
131
+ private function _isValidIP($ip) {
132
+ return filter_var($ip, FILTER_VALIDATE_IP) !== false;
133
+ }
134
+
135
+ /**
136
+ * @param string $ip
137
+ * @return bool
138
+ */
139
+ private function _isIPv6MappedIPv4($ip) {
140
+ return preg_match('/^(?:\:(?:\:0{1,4}){0,4}\:|(?:0{1,4}\:){5})ffff\:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i', $ip) > 0;
141
+ }
142
+
143
+ /**
144
+ * @param string $addr Should be in dot or colon notation (127.0.0.1 or ::1)
145
+ * @return bool
146
+ */
147
+ private function _isPrivateIP($ip) {
148
+ // Run this through the preset list for IPv4 addresses.
149
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
150
+ $wordfenceLib = realpath(dirname(__FILE__) . '/../lib');
151
+ include($wordfenceLib . '/wfIPWhitelist.php'); // defines $wfIPWhitelist
152
+ $private = $wfIPWhitelist['private'];
153
+
154
+ foreach ($private as $a) {
155
+ if (wfWAFUtils::subnetContainsIP($a, $ip)) {
156
+ return true;
157
+ }
158
+ }
159
+ }
160
+
161
+ return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) !== false
162
+ && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false;
163
  }
164
  }
165
 
wordfence.php CHANGED
@@ -4,14 +4,14 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
- Version: 6.2.3
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
- define('WORDFENCE_VERSION', '6.2.3');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17
 
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
+ Version: 6.2.4
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
+ define('WORDFENCE_VERSION', '6.2.4');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17