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 | 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 +3 -3
- lib/email_newIssues.php +6 -0
- lib/menu_options.php +9 -0
- lib/menu_scan.php +32 -1
- lib/wfConfig.php +19 -5
- lib/wfDB.php +1 -1
- lib/wfIssues.php +2 -1
- lib/wfScan.php +9 -1
- lib/wfScanEngine.php +51 -14
- lib/wfSchema.php +9 -2
- lib/wfUtils.php +3 -2
- lib/wordfenceClass.php +13 -6
- lib/wordfenceConstants.php +2 -1
- lib/wordfenceHash.php +149 -60
- lib/wordfenceScanner.php +5 -2
- readme.txt +15 -1
- waf/bootstrap.php +126 -6
- wordfence.php +2 -2
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
|
183 |
-
self.tourRedir('
|
184 |
});
|
185 |
}
|
186 |
} else if (jQuery('#wordfenceMode_options').length > 0) {
|
@@ -552,7 +552,7 @@
|
|
552 |
}
|
553 |
html += '">[' + item.date + '] ' + 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 + '] ' + 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
|
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 (!$
|
447 |
$data = bin2hex($data);
|
448 |
}
|
449 |
|
450 |
$dataLength = strlen($data);
|
451 |
-
$
|
|
|
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 ($
|
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 ($
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
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 |
-
|
28 |
-
|
|
|
|
|
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 <
|
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 <
|
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('
|
|
|
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', '
|
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 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
|
|
139 |
}
|
140 |
-
|
141 |
-
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
183 |
-
if(substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.'){
|
184 |
return;
|
185 |
}
|
186 |
-
if(!
|
187 |
if (!$this->_shouldProcessPath($path)) { return; }
|
188 |
-
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
200 |
-
$this->
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
}
|
204 |
}
|
205 |
|
206 |
-
$realPath =
|
207 |
-
$this->
|
208 |
-
}
|
209 |
-
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
}
|
212 |
}
|
213 |
}
|
214 |
-
private function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
$this->engine->fork();
|
220 |
//exits
|
221 |
}
|
222 |
|
223 |
-
|
224 |
-
if ($this->stoppedOnFile && $file != $this->stoppedOnFile) {
|
225 |
return;
|
226 |
}
|
227 |
else if ($this->stoppedOnFile && $file == $this->stoppedOnFile) {
|
228 |
-
$this->stoppedOnFile = false; //Continue
|
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
|
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
|
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.
|
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[] =
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
}
|
42 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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.
|
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 |
|