Wordfence Security – Firewall & Malware Scan - Version 6.3.14

Version Description

  • Improvement: Introduced smart scan distribution. Scan times are now distributed intelligently across servers to provide consistent server performance.
  • Improvement: Introduced light-weight scan that runs frequently to perform checks that do not use any server resources.
  • Improvement: If unable to successfully look up the status of an IP claiming to be Googlebot, the hit is now allowed.
  • Improvement: Scan issue results for abandoned plugins and unpatched vulnerabilities include more info.
  • Fix: Suppressed PHP notice with time formatting when a microtimestamp is passed.
  • Fix: Improved binary data to HTML entity conversion to avoid wpdb stripping out-of-range UTF-8 sequences.
  • Fix: Added better detection to SSL status, particularly for IIS.
  • Fix: Fixed PHP notice in the diff renderer.
  • Fix: Fixed typo in lockout alert.
Download this release

Release Info

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

Code changes from version 6.3.12 to 6.3.14

lib/Diff/Renderer/Html/Array.php CHANGED
@@ -190,7 +190,8 @@ class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
190
*/
191
private function fixSpacesCallback($matches)
192
{
193
- return $this->fixSpaces($matches[1]);
194
}
195
196
/**
190
*/
191
private function fixSpacesCallback($matches)
192
{
193
+ $spaces = (isset($matches[1]) ? $matches[1] : '');
194
+ return $this->fixSpaces($spaces);
195
}
196
197
/**
lib/email_newIssues.php CHANGED
@@ -25,18 +25,29 @@
25
<?php
26
if ((isset($i['tmplData']['wpRemoved']) && $i['tmplData']['wpRemoved']) || (isset($i['tmplData']['abandoned']) && $i['tmplData']['abandoned'])) {
27
if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
28
- echo '<p><strong>Plugin contains an unpatched security vulnerability.</strong></p>';
29
}
30
}
31
else if (isset($i['tmplData']['wpURL'])) {
32
echo '<p>';
33
if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
34
echo '<strong>Update includes security-related fixes.</strong> ';
35
}
36
echo $i['tmplData']['wpURL'] . '/#developers</p>';
37
}
38
else if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
39
- echo '<p><strong>Update includes security-related fixes.</strong></p>';
40
}
41
?>
42
<?php if (!empty($i['tmplData']['badURL'])): ?>
25
<?php
26
if ((isset($i['tmplData']['wpRemoved']) && $i['tmplData']['wpRemoved']) || (isset($i['tmplData']['abandoned']) && $i['tmplData']['abandoned'])) {
27
if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
28
+ echo '<p><strong>Plugin contains an unpatched security vulnerability.</strong>';
29
+ if (isset($i['tmplData']['vulnerabilityLink'])) {
30
+ echo ' <a href="' . $i['tmplData']['vulnerabilityLink'] . '" target="_blank" rel="nofollow noreferer noopener">Vulnerability Information</a>';
31
+ }
32
+ echo '</p>';
33
}
34
}
35
else if (isset($i['tmplData']['wpURL'])) {
36
echo '<p>';
37
if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
38
echo '<strong>Update includes security-related fixes.</strong> ';
39
+ if (isset($i['tmplData']['vulnerabilityLink'])) {
40
+ echo '<a href="' . $i['tmplData']['vulnerabilityLink'] . '" target="_blank" rel="nofollow noreferer noopener">Vulnerability Information</a> ';
41
+ }
42
}
43
echo $i['tmplData']['wpURL'] . '/#developers</p>';
44
}
45
else if (isset($i['tmplData']['vulnerable']) && $i['tmplData']['vulnerable']) {
46
+ echo '<p><strong>Update includes security-related fixes.</strong>';
47
+ if (isset($i['tmplData']['vulnerabilityLink'])) {
48
+ echo ' <a href="' . $i['tmplData']['vulnerabilityLink'] . '" target="_blank" rel="nofollow noreferer noopener">Vulnerability Information</a>';
49
+ }
50
+ echo '</p>';
51
}
52
?>
53
<?php if (!empty($i['tmplData']['badURL'])): ?>
lib/menu_scan.php CHANGED
@@ -271,6 +271,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
271
<tr><th>New <span class="wf-hidden-xs">Theme </span>Version:</th><td>${data.newVersion}</td></tr>
272
<tr><th><span class="wf-hidden-xs">Theme </span>URL:</th><td><a href="${data.URL}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.URL}</span><span class="wf-visible-xs">View</span></a></td></tr>
273
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
274
<tr><th>Status</th><td>
275
{{if status == 'new' }}New{{/if}}
276
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
@@ -312,6 +313,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
312
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
313
<tr><th>New <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.newVersion}</td></tr>
314
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
315
<tr><th>Status</th><td>
316
{{if status == 'new' }}New{{/if}}
317
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
@@ -351,6 +353,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
351
{{if data.PluginURI}}<tr><th><span class="wf-hidden-xs">Plugin </span>Website:</th><td><a href="${data.PluginURI}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.PluginURI}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
352
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
353
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
354
<tr><th>Status</th><td>
355
{{if status == 'new' }}New{{/if}}
356
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
@@ -390,6 +393,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
390
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.version}</td></tr>
391
<tr><th>Last Updated:</th><td>${data.dateUpdated}</td></tr>
392
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
393
<tr><th>Status</th><td>
394
{{if status == 'new' }}New{{/if}}
395
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
@@ -429,6 +433,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
429
{{if data.wpURL}}<tr><th>Repository<span class="wf-hidden-xs"> Link</span>:</th><td><a href="${data.wpURL}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.wpURL}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
430
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
431
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
432
<tr><th>Status</th><td>
433
{{if status == 'new' }}New{{/if}}
434
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
@@ -465,6 +470,7 @@ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
465
<tr><th>Current <span class="wf-hidden-xs">WordPress </span>Version:</th><td>${data.currentVersion}</td></tr>
466
<tr><th>New <span class="wf-hidden-xs">WordPress </span>Version:</th><td>${data.newVersion}</td></tr>
467
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
468
<tr><th>Status</th><td>
469
{{if status == 'new' }}New{{/if}}
470
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
271
<tr><th>New <span class="wf-hidden-xs">Theme </span>Version:</th><td>${data.newVersion}</td></tr>
272
<tr><th><span class="wf-hidden-xs">Theme </span>URL:</th><td><a href="${data.URL}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.URL}</span><span class="wf-visible-xs">View</span></a></td></tr>
273
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
274
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
275
<tr><th>Status</th><td>
276
{{if status == 'new' }}New{{/if}}
277
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
313
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
314
<tr><th>New <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.newVersion}</td></tr>
315
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
316
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
317
<tr><th>Status</th><td>
318
{{if status == 'new' }}New{{/if}}
319
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
353
{{if data.PluginURI}}<tr><th><span class="wf-hidden-xs">Plugin </span>Website:</th><td><a href="${data.PluginURI}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.PluginURI}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
354
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
355
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
356
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
357
<tr><th>Status</th><td>
358
{{if status == 'new' }}New{{/if}}
359
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
393
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.version}</td></tr>
394
<tr><th>Last Updated:</th><td>${data.dateUpdated}</td></tr>
395
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
396
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
397
<tr><th>Status</th><td>
398
{{if status == 'new' }}New{{/if}}
399
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
433
{{if data.wpURL}}<tr><th>Repository<span class="wf-hidden-xs"> Link</span>:</th><td><a href="${data.wpURL}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.wpURL}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
434
<tr><th>Current <span class="wf-hidden-xs">Plugin </span>Version:</th><td>${data.Version}</td></tr>
435
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
436
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
437
<tr><th>Status</th><td>
438
{{if status == 'new' }}New{{/if}}
439
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
470
<tr><th>Current <span class="wf-hidden-xs">WordPress </span>Version:</th><td>${data.currentVersion}</td></tr>
471
<tr><th>New <span class="wf-hidden-xs">WordPress </span>Version:</th><td>${data.newVersion}</td></tr>
472
<tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
473
+ {{if data.vulnerabilityLink}}<tr><th>Vulnerability <span class="wf-hidden-xs">Information</span>:</th><td><a href="${data.vulnerabilityLink}" target="_blank" rel="noopener noreferrer"><span class="wf-hidden-xs wf-split-word">${data.vulnerabilityLink}</span><span class="wf-visible-xs">View</span></a></td></tr>{{/if}}
474
<tr><th>Status</th><td>
475
{{if status == 'new' }}New{{/if}}
476
{{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
lib/menu_scan_schedule.php CHANGED
@@ -14,24 +14,23 @@
14
<p class="center"><a class="wf-btn wf-btn-primary wf-btn-callout" href="https://www.wordfence.com/gnl1scanSched1/wordfence-signup/" target="_blank" rel="noopener noreferrer">Get Premium</a></p>
15
</div>
16
<?php } ?>
17
- <?php $schedMode = wfConfig::get('isPaid') ? wfConfig::get('schedMode', 'auto') : 'auto'; ?>
18
<div class="wf-container-fluid">
19
<div class="wf-row">
20
<div class="wf-col-xs-12">
21
- <div class="wf-card wf-card-left<?php echo ($schedMode == 'auto' ? ' active' : ''); ?>" data-mode="auto">
22
<div class="wf-card-inner">
23
<div class="wf-card-content">
24
<div class="wf-card-title">
25
Let Wordfence automatically schedule scans (recommended)
26
</div>
27
<div class="wf-card-subtitle">
28
- <?php if ($schedMode == 'auto') : ?>
29
<?php echo wordfence::getNextScanStartTime(); ?>
30
<?php endif; ?>
31
</div>
32
</div>
33
<div class="wf-card-action">
34
- <div class="wf-card-action-checkbox<?php echo ($schedMode == 'auto' ? ' checked' : ''); ?>"></div>
35
</div>
36
</div>
37
</div>
@@ -39,20 +38,20 @@
39
</div>
40
<div class="wf-row">
41
<div class="wf-col-xs-12">
42
- <div class="wf-card wf-card-left<?php echo ($schedMode == 'manual' ? ' active' : ''); ?><?php if (!wfConfig::get('isPaid')) { echo ' disabled'; } ?>" data-mode="manual">
43
<div class="wf-card-inner">
44
<div class="wf-card-content">
45
<div class="wf-card-title">
46
Manually schedule scans<?php if (!wfConfig::get('isPaid')) { echo ' (Premium Members Only)'; } ?>
47
</div>
48
<div class="wf-card-subtitle">
49
- <?php if ($schedMode == 'manual') : ?>
50
<?php echo wordfence::getNextScanStartTime(); ?>
51
<?php endif; ?>
52
</div>
53
</div>
54
<div class="wf-card-action">
55
- <div class="wf-card-action-checkbox<?php echo ($schedMode == 'manual' ? ' checked' : ''); ?>"></div>
56
</div>
57
</div>
58
<div class="wf-card-extra">
14
<p class="center"><a class="wf-btn wf-btn-primary wf-btn-callout" href="https://www.wordfence.com/gnl1scanSched1/wordfence-signup/" target="_blank" rel="noopener noreferrer">Get Premium</a></p>
15
</div>
16
<?php } ?>
17
<div class="wf-container-fluid">
18
<div class="wf-row">
19
<div class="wf-col-xs-12">
20
+ <div class="wf-card wf-card-left<?php echo (wfScan::isAutoScanSchedule() ? ' active' : ''); ?>" data-mode="auto">
21
<div class="wf-card-inner">
22
<div class="wf-card-content">
23
<div class="wf-card-title">
24
Let Wordfence automatically schedule scans (recommended)
25
</div>
26
<div class="wf-card-subtitle">
27
+ <?php if (wfScan::isAutoScanSchedule()) : ?>
28
<?php echo wordfence::getNextScanStartTime(); ?>
29
<?php endif; ?>
30
</div>
31
</div>
32
<div class="wf-card-action">
33
+ <div class="wf-card-action-checkbox<?php echo (wfScan::isAutoScanSchedule() ? ' checked' : ''); ?>"></div>
34
</div>
35
</div>
36
</div>
38
</div>
39
<div class="wf-row">
40
<div class="wf-col-xs-12">
41
+ <div class="wf-card wf-card-left<?php echo (wfScan::isManualScanSchedule() ? ' active' : ''); ?><?php if (!wfConfig::get('isPaid')) { echo ' disabled'; } ?>" data-mode="manual">
42
<div class="wf-card-inner">
43
<div class="wf-card-content">
44
<div class="wf-card-title">
45
Manually schedule scans<?php if (!wfConfig::get('isPaid')) { echo ' (Premium Members Only)'; } ?>
46
</div>
47
<div class="wf-card-subtitle">
48
+ <?php if (wfScan::isManualScanSchedule()) : ?>
49
<?php echo wordfence::getNextScanStartTime(); ?>
50
<?php endif; ?>
51
</div>
52
</div>
53
<div class="wf-card-action">
54
+ <div class="wf-card-action-checkbox<?php echo (wfScan::isManualScanSchedule() ? ' checked' : ''); ?>"></div>
55
</div>
56
</div>
57
<div class="wf-card-extra">
lib/wfConfig.php CHANGED
@@ -130,7 +130,7 @@ class wfConfig {
130
'howGetIPs_trusted_proxies' => '',
131
)
132
);
133
- public static $serializedOptions = array('lastAdminLogin', 'scanSched', 'emailedIssuesList', 'wf_summaryItems', 'adminUserList', 'twoFactorUsers', 'alertFreqTrack', 'wfStatusStartMsgs', 'vulnerabilities_plugin', 'vulnerabilities_theme', 'dashboardData', 'malwarePrefixes');
134
public static function setDefaults() {
135
foreach (self::$defaultConfig['checkboxes'] as $key => $config) {
136
$val = $config['value'];
130
'howGetIPs_trusted_proxies' => '',
131
)
132
);
133
+ public static $serializedOptions = array('lastAdminLogin', 'scanSched', 'emailedIssuesList', 'wf_summaryItems', 'adminUserList', 'twoFactorUsers', 'alertFreqTrack', 'wfStatusStartMsgs', 'vulnerabilities_plugin', 'vulnerabilities_theme', 'dashboardData', 'malwarePrefixes', 'noc1ScanSchedule', 'allScansScheduled');
134
public static function setDefaults() {
135
foreach (self::$defaultConfig['checkboxes'] as $key => $config) {
136
$val = $config['value'];
lib/wfCrawl.php CHANGED
@@ -1,6 +1,10 @@
1
<?php
2
require_once('wfUtils.php');
3
class wfCrawl {
4
public static function isCrawler($UA){
5
$browscap = new wfBrowscap();
6
$b = $browscap->getBrowser($UA);
@@ -120,18 +124,27 @@ class wfCrawl {
120
$verified[$ip] = true;
121
return $verified[$ip];
122
}
123
- if (self::verifyGooglebotViaNOC1($ip)) {
124
$verified[$ip] = true;
125
return $verified[$ip];
126
}
127
}
128
$verified[$ip] = false;
129
return $verified[$ip];
130
}
131
132
/**
133
* @param string|null $ip
134
- * @return bool
135
*/
136
public static function verifyGooglebotViaNOC1($ip = null) {
137
global $wpdb;
@@ -150,9 +163,9 @@ class wfCrawl {
150
$patternSig,
151
WORDFENCE_CRAWLER_VERIFY_CACHE_TIME);
152
if ($status === 'verified') {
153
- return true;
154
} else if ($status === 'fakeBot') {
155
- return false;
156
}
157
158
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
@@ -166,17 +179,18 @@ class wfCrawl {
166
values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp())
167
ON DUPLICATE KEY UPDATE status='%3\$s', lastUpdate=unix_timestamp()",
168
$IPn, $patternSig, 'verified');
169
- return true;
170
} else {
171
$db->queryWrite("insert into $table (IP, patternSig, status, lastUpdate)
172
values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp())
173
ON DUPLICATE KEY UPDATE status='%3\$s', lastUpdate=unix_timestamp()",
174
$IPn, $patternSig, 'fakeBot');
175
}
176
} catch (Exception $e) {
177
// Do nothing, bail
178
}
179
- return false;
180
}
181
}
182
?>
1
<?php
2
require_once('wfUtils.php');
3
class wfCrawl {
4
+ const GOOGLE_BOT_VERIFIED = 'verified';
5
+ const GOOGLE_BOT_FAKE = 'fakeBot';
6
+ const GOOGLE_BOT_UNDETERMINED = 'undetermined';
7
+
8
public static function isCrawler($UA){
9
$browscap = new wfBrowscap();
10
$b = $browscap->getBrowser($UA);
124
$verified[$ip] = true;
125
return $verified[$ip];
126
}
127
+ $noc1Status = self::verifyGooglebotViaNOC1($ip);
128
+ if ($noc1Status == self::GOOGLE_BOT_VERIFIED) {
129
$verified[$ip] = true;
130
return $verified[$ip];
131
}
132
+ else if ($noc1Status == self::GOOGLE_BOT_FAKE) {
133
+ $verified[$ip] = false;
134
+ return $verified[$ip];
135
+ }
136
+
137
+ return true; //We were unable to successfully validate Googlebot status so default to being permissive
138
}
139
$verified[$ip] = false;
140
return $verified[$ip];
141
}
142
143
/**
144
+ * Attempts to verify whether an IP claiming to be Googlebot is actually Googlebot.
145
+ *
146
* @param string|null $ip
147
+ * @return string
148
*/
149
public static function verifyGooglebotViaNOC1($ip = null) {
150
global $wpdb;
163
$patternSig,
164
WORDFENCE_CRAWLER_VERIFY_CACHE_TIME);
165
if ($status === 'verified') {
166
+ return self::GOOGLE_BOT_VERIFIED;
167
} else if ($status === 'fakeBot') {
168
+ return self::GOOGLE_BOT_FAKE;
169
}
170
171
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
179
values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp())
180
ON DUPLICATE KEY UPDATE status='%3\$s', lastUpdate=unix_timestamp()",
181
$IPn, $patternSig, 'verified');
182
+ return self::GOOGLE_BOT_VERIFIED;
183
} else {
184
$db->queryWrite("insert into $table (IP, patternSig, status, lastUpdate)
185
values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp())
186
ON DUPLICATE KEY UPDATE status='%3\$s', lastUpdate=unix_timestamp()",
187
$IPn, $patternSig, 'fakeBot');
188
+ self::GOOGLE_BOT_FAKE;
189
}
190
} catch (Exception $e) {
191
// Do nothing, bail
192
}
193
+ return self::GOOGLE_BOT_UNDETERMINED;
194
}
195
}
196
?>
lib/wfIssues.php CHANGED
@@ -233,8 +233,15 @@ class wfIssues {
233
public function deleteIgnored(){
234
$this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
235
}
236
- public function deleteNew(){
237
- $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='new'");
238
}
239
public function ignoreAllNew(){
240
$this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
233
public function deleteIgnored(){
234
$this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
235
}
236
+ public function deleteNew($types = null) {
237
+ if (!is_array($types)) {
238
+ $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new'");
239
+ }
240
+ else {
241
+ $query = "DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type IN (" . implode(',', array_fill(0, count($types), "'%s'")) . ")";
242
+ array_unshift($types, $query);
243
+ call_user_func_array(array($this->getDB(), 'queryWrite'), $types);
244
+ }
245
}
246
public function ignoreAllNew(){
247
$this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
lib/wfScan.php CHANGED
@@ -1,8 +1,27 @@
1
<?php
2
class wfScan {
3
public static $debugMode = false;
4
public static $errorHandlingOn = true;
5
private static $peakMemAtStart = 0;
6
public static function wfScanMain(){
7
self::$peakMemAtStart = memory_get_peak_usage(true);
8
$db = new wfDB();
@@ -85,9 +104,59 @@ class wfScan {
85
exit();
86
}
87
} else {
88
wfIssues::statusPrep(); //Re-initializes all status counters
89
- $scan = new wfScanEngine();
90
- $scan->deleteNewIssues();
91
}
92
try {
93
$scan->go();
@@ -124,6 +193,9 @@ class wfScan {
124
$peakMemory = self::logPeakMemory();
125
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
126
}
127
private static function logPeakMemory(){
128
$oldPeak = wfConfig::get('wfPeakMemory', 0, false);
129
$peak = memory_get_peak_usage(true);
1
<?php
2
class wfScan {
3
+ const SCAN_SCHEDULING_MODE_AUTOMATIC = 'auto';
4
+ const SCAN_SCHEDULING_MODE_MANUAL = 'manual';
5
+
6
public static $debugMode = false;
7
public static $errorHandlingOn = true;
8
private static $peakMemAtStart = 0;
9
+
10
+ public static function scanSchedulingMode() {
11
+ $sched = wfConfig::get_ser('scanSched', array());
12
+ if (wfConfig::get('isPaid') && wfConfig::get('schedMode') == 'manual' && is_array($sched) && is_array($sched[0])) {
13
+ return self::SCAN_SCHEDULING_MODE_MANUAL;
14
+ }
15
+ return self::SCAN_SCHEDULING_MODE_AUTOMATIC;
16
+ }
17
+ public static function isAutoScanSchedule() { return self::scanSchedulingMode() == self::SCAN_SCHEDULING_MODE_AUTOMATIC; }
18
+ public static function isManualScanSchedule() { return self::scanSchedulingMode() == self::SCAN_SCHEDULING_MODE_MANUAL; }
19
+
20
+ public static function shouldRunScan($scanMode) {
21
+ $jobs = wfScanEngine::jobsForScanMode($scanMode);
22
+ return count($jobs) > 0;
23
+ }
24
+
25
public static function wfScanMain(){
26
self::$peakMemAtStart = memory_get_peak_usage(true);
27
$db = new wfDB();
104
exit();
105
}
106
} else {
107
+ $scanMode = wfScanEngine::SCAN_MODE_FULL;
108
+ if (isset($_GET['scanMode']) && self::_isValidScanMode($_GET['scanMode'])) {
109
+ $scanMode = $_GET['scanMode'];
110
+ }
111
+
112
+ $delay = -1;
113
+ $isScheduled = false;
114
+ $originalScanStart = wfConfig::get('originalScheduledScanStart', 0);
115
+ $lastScanStart = wfConfig::get('lastScheduledScanStart', 0);
116
+ $minimumFrequency = (wfScan::isManualScanSchedule() ? 1800 : 43200);
117
+ if ($lastScanStart && (time() - $lastScanStart) < $minimumFrequency) {
118
+ $isScheduled = true;
119
+
120
+ if ($originalScanStart > 0) {
121
+ $delay = max($lastScanStart - $originalScanStart, 0);
122
+ }
123
+ }
124
+
125
wfIssues::statusPrep(); //Re-initializes all status counters
126
+
127
+ if ($scanMode == wfScanEngine::SCAN_MODE_FULL) {
128
+ wordfence::status(1, 'info', "Contacting Wordfence to initiate scan");
129
+ $wp_version = wfUtils::getWPVersion();
130
+ $apiKey = wfConfig::get('apiKey');
131
+ $api = new wfAPI($apiKey, $wp_version);
132
+ $response = $api->call('log_scan', array(), array('delay' => $delay, 'scheduled' => (int) $isScheduled, 'mode' => wfConfig::get('schedMode')/*, 'forcedefer' => 1*/));
133
+
134
+ if (!wfScan::isManualScanSchedule() && $isScheduled) {
135
+ if (isset($response['defer'])) {
136
+ $defer = (int) $response['defer'];
137
+ wordfence::status(2, 'info', "Deferring scheduled scan by " . wfUtils::makeDuration($defer));
138
+ wfConfig::set('lastScheduledScanStart', 0);
139
+ wfConfig::set('lastScanCompleted', 'ok');
140
+ wfConfig::set('lastScanFailureType', false);
141
+ wfConfig::set_ser('wfStatusStartMsgs', array());
142
+ $i = new wfIssues();
143
+ $i->setScanTimeNow();
144
+ wfScanEngine::refreshScanNotification($i);
145
+ wordfence::scheduleSingleScan(time() + $defer, $originalScanStart);
146
+ wfUtils::clearScanLock();
147
+ exit();
148
+ }
149
+ }
150
+
151
+ $malwarePrefixesHash = (isset($response['malwarePrefixes']) ? $response['malwarePrefixes'] : '');
152
+
153
+ $scan = new wfScanEngine($malwarePrefixesHash, $scanMode);
154
+ $scan->deleteNewIssues();
155
+ }
156
+ else {
157
+ wordfence::status(1, 'info', "Initiating quick scan");
158
+ $scan = new wfScanEngine('', $scanMode);
159
+ }
160
}
161
try {
162
$scan->go();
193
$peakMemory = self::logPeakMemory();
194
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
195
}
196
+ private static function _isValidScanMode($scanMode) {
197
+ return ($scanMode == wfScanEngine::SCAN_MODE_QUICK || $scanMode == wfScanEngine::SCAN_MODE_FULL);
198
+ }
199
private static function logPeakMemory(){
200
$oldPeak = wfConfig::get('wfPeakMemory', 0, false);
201
$peak = memory_get_peak_usage(true);
lib/wfScanEngine.php CHANGED
@@ -7,6 +7,9 @@ require_once('wfIssues.php');
7
require_once('wfDB.php');
8
require_once('wfUtils.php');
9
class wfScanEngine {
10
public $api = false;
11
private $dictWords = array();
12
private $forkRequested = false;
@@ -43,6 +46,8 @@ class wfScanEngine {
43
private $gsbMultisiteBlogOffset = 0;
44
private $updateCheck = false;
45
private $pluginRepoStatus = array();
46
47
/**
48
* @var wordfenceDBScanner
@@ -118,11 +123,43 @@ class wfScanEngine {
118
}
119
}
120
}
121
122
public function __sleep(){ //Same order here as above for properties that are included in serialization
123
- return array('hasher', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'maxExecTime', 'publicScanEnabled', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues', 'suspectedFiles', 'dbScanner', 'knownFilesLoader', 'metrics', 'checkHowGetIPsRequestTime', 'gsbMultisiteBlogOffset', 'updateCheck', 'pluginRepoStatus');
124
}
125
- public function __construct(){
126
$this->startTime = time();
127
$this->recordMetric('scan', 'start', $this->startTime);
128
$this->maxExecTime = self::getMaxExecutionTime();
@@ -131,34 +168,31 @@ class wfScanEngine {
131
$this->wp_version = wfUtils::getWPVersion();
132
$this->apiKey = wfConfig::get('apiKey');
133
$this->api = new wfAPI($this->apiKey, $this->wp_version);
134
include('wfDict.php'); //$dictWords
135
$this->dictWords = $dictWords;
136
- $this->jobList[] = 'checkSpamvertized';
137
- $this->jobList[] = 'checkSpamIP';
138
- $this->jobList[] = 'checkGSB_init';
139
- $this->jobList[] = 'checkGSB_main';
140
- $this->jobList[] = 'checkGSB_finish';
141
- $this->jobList[] = 'checkHowGetIPs_init';
142
- $this->jobList[] = 'checkHowGetIPs_main';
143
- $this->jobList[] = 'knownFiles_init';
144
- $this->jobList[] = 'knownFiles_main';
145
- $this->jobList[] = 'knownFiles_finish';
146
- foreach (array('knownFiles', 'checkReadableConfig', 'fileContents', 'suspectedFiles',
147
- // 'wpscan_fullPathDisclosure', 'wpscan_directoryListingEnabled',
148
- 'posts', 'comments', 'passwds', 'dns', 'diskSpace', 'oldVersions', 'suspiciousAdminUsers') as $scanType) {
149
- if (wfConfig::get('scansEnabled_' . $scanType)) {
150
- if (method_exists($this, 'scan_' . $scanType . '_init')) {
151
- foreach (array('init', 'main', 'finish') as $op) {
152
- $this->jobList[] = $scanType . '_' . $op;
153
- };
154
- } else if (method_exists($this, 'scan_' . $scanType)) {
155
- $this->jobList[] = $scanType;
156
}
157
}
158
}
159
}
160
- public function deleteNewIssues(){
161
- $this->i->deleteNew();
162
}
163
public function __wakeup(){
164
$this->cycleStartTime = time();
@@ -166,6 +200,9 @@ class wfScanEngine {
166
include('wfDict.php'); //$dictWords
167
$this->dictWords = $dictWords;
168
}
169
public function go(){
170
try {
171
self::checkForKill();
@@ -177,9 +214,11 @@ class wfScanEngine {
177
$this->i->setScanTimeNow();
178
//scan ID only incremented at end of scan to make UI load new results
179
$this->emailNewIssues();
180
- $this->recordMetric('scan', 'duration', (time() - $this->startTime));
181
- $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
182
- $this->submitMetrics();
183
184
wfScanEngine::refreshScanNotification($this->i);
185
}
@@ -283,7 +322,7 @@ class wfScanEngine {
283
wordfence::status(4, 'info', "Entered fork()");
284
if(wfConfig::set_ser('wfsd_engine', $this, true, wfConfig::DONT_AUTOLOAD)){
285
wordfence::status(4, 'info', "Calling startScan(true)");
286
- self::startScan(true);
287
} //Otherwise there was an error so don't start another scan.
288
exit(0);
289
}
@@ -322,7 +361,12 @@ class wfScanEngine {
322
}
323
$summary = $this->i->getSummaryItems();
324
$this->status(1, 'info', '-------------------');
325
- $this->status(1, 'info', "Scan Complete. 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) . ".");
326
327
$ignoredText = '';
328
if ($this->i->totalIgnoredIssues > 0) {
@@ -552,6 +596,10 @@ class wfScanEngine {
552
wfIssues::statusEnd($this->statusIDX['checkHowGetIPs'], $haveIssues);
553
}
554
}
555
556
private function scan_checkReadableConfig() {
557
$haveIssues = wfIssues::STATUS_SECURE;
@@ -699,8 +747,6 @@ class wfScanEngine {
699
}
700
}
701
private function scan_knownFiles_init(){
702
- $this->status(1, 'info', "Contacting Wordfence to initiate scan");
703
- $response = $this->api->call('log_scan', array(), array());
704
$baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
705
$baseContents = scandir(ABSPATH);
706
if(! is_array($baseContents)){
@@ -733,8 +779,7 @@ class wfScanEngine {
733
$this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
734
$this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
735
736
- $malwarePrefixesHash = (isset($response['malwarePrefixes']) ? wfUtils::hex2bin($response['malwarePrefixes']) : '');
737
- $this->hasher = new wordfenceHash(strlen(ABSPATH), ABSPATH, $includeInKnownFilesScan, $knownFilesThemes, $knownFilesPlugins, $this, $malwarePrefixesHash);
738
}
739
private function scan_knownFiles_main(){
740
$this->hasher->run($this); //Include this so we can call addIssue and ->api->
@@ -1418,8 +1463,13 @@ class wfScanEngine {
1418
$this->statusIDX['oldVersions'] = wfIssues::statusStart("Scanning for old themes, plugins and core files");
1419
1420
$this->updateCheck = new wfUpdateCheck();
1421
- $this->updateCheck->checkAllUpdates(false);
1422
- $this->updateCheck->checkAllVulnerabilities();
1423
1424
foreach ($this->updateCheck->getPluginSlugs() as $slug) {
1425
$this->pluginRepoStatus[$slug] = false;
@@ -1436,6 +1486,10 @@ class wfScanEngine {
1436
}
1437
1438
private function scan_oldVersions_main() {
1439
if (!function_exists('plugins_api')) {
1440
require_once(ABSPATH . 'wp-admin/includes/plugin-install.php');
1441
}
@@ -1480,6 +1534,10 @@ class wfScanEngine {
1480
1481
private function scan_oldVersions_finish() {
1482
$haveIssues = wfIssues::STATUS_SECURE;
1483
1484
// WordPress core updates needed
1485
if ($this->updateCheck->needsCoreUpdate()) {
@@ -1531,95 +1589,112 @@ class wfScanEngine {
1531
}
1532
}
1533
1534
- //Abandoned plugins
1535
- foreach ($this->pluginRepoStatus as $slug => $status) {
1536
- if ($status !== false && !is_wp_error($status) && property_exists($status, 'last_updated')) {
1537
- $lastUpdateTimestamp = strtotime($status->last_updated);
1538
- if ($lastUpdateTimestamp > 0 && (time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
1539
- $statusArray = (array) $status;
1540
- $statusArray['dateUpdated'] = wfUtils::formatLocalTime(get_option('date_format'), $lastUpdateTimestamp);
1541
- $severity = 2; //Warning
1542
- $statusArray['abandoned'] = true;
1543
- $statusArray['vulnerable'] = false;
1544
- if ($this->updateCheck->isPluginVulnerable($slug, $statusArray['version'])) {
1545
- $severity = 1; //Critical
1546
- $statusArray['vulnerable'] = true;
1547
- }
1548
-
1549
- if (isset($allPlugins[$slug])) {
1550
- $statusArray['wpURL'] = $allPlugins[$slug]['wpURL'];
1551
- }
1552
-
1553
- $key = "wfPluginAbandoned {$slug} {$statusArray['version']}";
1554
- $shortMsg = 'The Plugin "' . $statusArray['name'] . '" appears to be abandoned.';
1555
- $longMsg = 'It was last updated ' . wfUtils::makeTimeAgo(time() - $lastUpdateTimestamp) . ' ago.';
1556
- if ($statusArray['vulnerable']) {
1557
- $longMsg .= ' It has unpatched security issues and may have compatibility problems with the current version of WordPress.';
1558
- }
1559
- else {
1560
- $longMsg .= ' It may have compatibility problems with the current version of WordPress or unknown security issues.';
1561
- }
1562
- $longMsg .= ' <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_appears_to_be_abandoned" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1563
- $added = $this->addIssue('wfPluginAbandoned', $severity, $key, $key, $shortMsg, $longMsg, $statusArray);
1564
- if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1565
- else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1566
-
1567
- unset($allPlugins[$slug]);
1568
- }
1569
- }
1570
- else if ($status !== false && is_wp_error($status) && isset($status->error_data['plugins_api_failed']) && $status->error_data['plugins_api_failed'] == 'N;') { //The plugin does not exist in the wp.org repo
1571
- $knownFiles = $this->getKnownFilesLoader()->getKnownFiles();
1572
- $requestedPlugins = $this->getPlugins();
1573
- foreach ($requestedPlugins as $key => $data) {
1574
- $testKey = trailingslashit($data['FullDir']) . '.';
1575
- if ($data['ShortDir'] == $slug && isset($knownFiles['plugins'][$testKey])) { //It existed in the repo at some point and was removed
1576
- $pluginFile = wfUtils::getPluginBaseDir() . $key;
1577
- $pluginData = get_plugin_data($pluginFile);
1578
- $pluginData['wpRemoved'] = true;
1579
- $pluginData['vulnerable'] = false;
1580
- if ($this->updateCheck->isPluginVulnerable($slug, $pluginData['Version'])) {
1581
- $pluginData['vulnerable'] = true;
1582
}
1583
1584
- $key = "wfPluginRemoved {$slug} {$pluginData['Version']}";
1585
- $shortMsg = 'The Plugin "' . $pluginData['Name'] . '" has been removed from wordpress.org.';
1586
- if ($pluginData['vulnerable']) {
1587
- $longMsg = 'It has unpatched security issues and may have compatibility problems with the current version of WordPress.';
1588
}
1589
else {
1590
- $longMsg = 'It may have compatibility problems with the current version of WordPress or unknown security issues.';
1591
}
1592
- $longMsg .= ' <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_has_been_removed_from_wordpress.org" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1593
- $added = $this->addIssue('wfPluginRemoved', 1, $key, $key, $shortMsg, $longMsg, $pluginData);
1594
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1595
else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1596
1597
unset($allPlugins[$slug]);
1598
}
1599
}
1600
- }
1601
- }
1602
-
1603
- //Other vulnerable plugins
1604
- //Disabled until we improve the data source to weed out false positives
1605
- /*if (count($allPlugins) > 0) {
1606
- foreach ($allPlugins as $plugin) {
1607
- if (!isset($plugin['vulnerable']) || !$plugin['vulnerable']) {
1608
- continue;
1609
- }
1610
-
1611
- $key = 'wfPluginVulnerable' . ' ' . $plugin['pluginFile'] . ' ' . $plugin['Version'];
1612
- $shortMsg = "The Plugin \"" . $plugin['Name'] . "\" has an unpatched security vulnerability.";
1613
- $longMsg = 'To protect your site from this vulnerability, the safest option is to deactivate and completely remove ' . esc_html($plugin['Name']) . ' until the developer releases a security fix. <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_has_an_unpatched_security_vulnerability" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1614
- $added = $this->addIssue('wfPluginVulnerable', 1, $key, $key, $shortMsg, $longMsg, $plugin);
1615
- if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1616
- else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1617
-
1618
- if (isset($plugin['slug'])) {
1619
- unset($allPlugins[$plugin['slug']]);
1620
}
1621
}
1622
- }*/
1623
1624
$this->updateCheck = false;
1625
$this->pluginRepoStatus = array();
@@ -1679,7 +1754,7 @@ class wfScanEngine {
1679
throw new Exception("Scan was killed on administrator request.");
1680
}
1681
}
1682
- public static function startScan($isFork = false){
1683
if(! $isFork){ //beginning of scan
1684
wfConfig::inc('totalScansRun');
1685
wfConfig::set('wfKillRequested', 0, wfConfig::DONT_AUTOLOAD);
@@ -1705,7 +1780,7 @@ class wfScanEngine {
1705
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
1706
if( (! wfConfig::get('startScansRemotely', false)) && (! is_wp_error($testResult)) && (is_array($testResult) || $testResult instanceof ArrayAccess) && strstr($testResult['body'], 'WFSCANTESTOK') !== false){
1707
//ajax requests can be sent by the server to itself
1708
- $cronURL = 'admin-ajax.php?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&cronKey=' . $cronKey;
1709
$cronURL = admin_url($cronURL);
1710
$headers = array('Referer' => false/*, 'Cookie' => 'XDEBUG_SESSION=1'*/);
1711
wordfence::status(4, 'info', "Starting cron with normal ajax at URL $cronURL");
@@ -1719,7 +1794,7 @@ class wfScanEngine {
1719
} else {
1720
$cronURL = admin_url('admin-ajax.php');
1721
$cronURL = preg_replace('/^(https?:\/\/)/i', '$1noc1.wordfence.com/scanp/', $cronURL);
1722
- $cronURL .= '?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&cronKey=' . $cronKey;
1723
$headers = array();
1724
wordfence::status(4, 'info', "Starting cron via proxy at URL $cronURL");
1725
7
require_once('wfDB.php');
8
require_once('wfUtils.php');
9
class wfScanEngine {
10
+ const SCAN_MODE_FULL = 'full';
11
+ const SCAN_MODE_QUICK = 'quick';
12
+
13
public $api = false;
14
private $dictWords = array();
15
private $forkRequested = false;
46
private $gsbMultisiteBlogOffset = 0;
47
private $updateCheck = false;
48
private $pluginRepoStatus = array();
49
+ private $malwarePrefixesHash;
50
+ private $scanMode = self::SCAN_MODE_FULL;
51
52
/**
53
* @var wordfenceDBScanner
123
}
124
}
125
}
126
+
127
+ /**
128
+ * Returns an array of the jobs that are enabled for the given scan mode.
129
+ *
130
+ * @param $scanMode
131
+ * @return array
132
+ */
133
+ public static function jobsForScanMode($scanMode) {
134
+ $jobs = array();
135
+ if ($scanMode == self::SCAN_MODE_FULL) {
136
+ $jobs[] = 'checkSpamvertized';
137
+ $jobs[] = 'checkSpamIP';
138
+ $jobs[] = 'checkGSB';
139
+ $jobs[] = 'checkHowGetIPs';
140
+ $jobs[] = 'knownFiles';
141
+ foreach (array('knownFiles', 'checkReadableConfig', 'fileContents', 'suspectedFiles',
142
+ // 'wpscan_fullPathDisclosure', 'wpscan_directoryListingEnabled',
143
+ 'posts', 'comments', 'passwds', 'dns', 'diskSpace', 'oldVersions', 'suspiciousAdminUsers') as $job) {
144
+ if (wfConfig::get('scansEnabled_' . $job)) {
145
+ $jobs[] = $job;
146
+ }
147
+ }
148
+ }
149
+ else if ($scanMode == self::SCAN_MODE_QUICK) {
150
+ foreach (array('oldVersions') as $job) {
151
+ if (wfConfig::get('scansEnabled_' . $job)) {
152
+ $jobs[] = $job;
153
+ }
154
+ }
155
+ }
156
+ return $jobs;
157
+ }
158
159
public function __sleep(){ //Same order here as above for properties that are included in serialization
160
+ return array('hasher', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'maxExecTime', 'publicScanEnabled', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues', 'suspectedFiles', 'dbScanner', 'knownFilesLoader', 'metrics', 'checkHowGetIPsRequestTime', 'gsbMultisiteBlogOffset', 'updateCheck', 'pluginRepoStatus', 'malwarePrefixesHash', 'scanMode');
161
}
162
+ public function __construct($malwarePrefixesHash = '', $scanMode = self::SCAN_MODE_FULL) {
163
$this->startTime = time();
164
$this->recordMetric('scan', 'start', $this->startTime);
165
$this->maxExecTime = self::getMaxExecutionTime();
168
$this->wp_version = wfUtils::getWPVersion();
169
$this->apiKey = wfConfig::get('apiKey');
170
$this->api = new wfAPI($this->apiKey, $this->wp_version);
171
+ $this->malwarePrefixesHash = $malwarePrefixesHash;
172
include('wfDict.php'); //$dictWords
173
$this->dictWords = $dictWords;
174
+ $this->scanMode = $scanMode;
175
+
176
+ $jobs = self::jobsForScanMode($this->scanMode);
177
+ foreach ($jobs as $job) {
178
+ if (method_exists($this, 'scan_' . $job . '_init')) {
179
+ foreach (array('init', 'main', 'finish') as $op) {
180
+ $this->jobList[] = $job . '_' . $op;
181
}
182
}
183
+ else if (method_exists($this, 'scan_' . $job)) {
184
+ $this->jobList[] = $job;
185
+ }
186
}
187
}
188
+
189
+ /**
190
+ * Deletes all new issues. To only delete specific types, provide an array of issue types.
191
+ *
192
+ * @param null|array $types
193
+ */
194
+ public function deleteNewIssues($types = null) {
195
+ $this->i->deleteNew($types);
196
}
197
public function __wakeup(){
198
$this->cycleStartTime = time();
200
include('wfDict.php'); //$dictWords
201
$this->dictWords = $dictWords;
202
}
203
+ public function isFullScan() {
204
+ return $this->scanMode == self::SCAN_MODE_FULL;
205
+ }
206
public function go(){
207
try {
208
self::checkForKill();
214
$this->i->setScanTimeNow();
215
//scan ID only incremented at end of scan to make UI load new results
216
$this->emailNewIssues();
217
+ if ($this->isFullScan()) {
218
+ $this->recordMetric('scan', 'duration', (time() - $this->startTime));
219
+ $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
220
+ $this->submitMetrics();
221
+ }
222
223
wfScanEngine::refreshScanNotification($this->i);
224
}
322
wordfence::status(4, 'info', "Entered fork()");
323
if(wfConfig::set_ser('wfsd_engine', $this, true, wfConfig::DONT_AUTOLOAD)){
324
wordfence::status(4, 'info', "Calling startScan(true)");
325
+ self::startScan(true, $this->scanMode);
326
} //Otherwise there was an error so don't start another scan.
327
exit(0);
328
}
361
}
362
$summary = $this->i->getSummaryItems();
363
$this->status(1, 'info', '-------------------');
364
+ if ($this->isFullScan()) {
365
+ $this->status(1, 'info', "Scan Complete. 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) . ".");
366
+ }
367
+ else {
368
+ $this->status(1, 'info', "Quick Scan Complete. Scanned in " . wfUtils::makeDuration(time() - $this->startTime, true) . ".");
369
+ }
370
371
$ignoredText = '';
372
if ($this->i->totalIgnoredIssues > 0) {
596
wfIssues::statusEnd($this->statusIDX['checkHowGetIPs'], $haveIssues);
597
}
598
}
599
+
600
+ private function scan_checkHowGetIPs_finish() {
601
+ /* Do nothing */
602
+ }
603
604
private function scan_checkReadableConfig() {
605
$haveIssues = wfIssues::STATUS_SECURE;
747
}
748
}
749
private function scan_knownFiles_init(){
750
$baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
751
$baseContents = scandir(ABSPATH);
752
if(! is_array($baseContents)){
779
$this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
780
$this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
781
782
+ $this->hasher = new wordfenceHash(strlen(ABSPATH), ABSPATH, $includeInKnownFilesScan, $knownFilesThemes, $knownFilesPlugins, $this, wfUtils::hex2bin($this->malwarePrefixesHash));
783
}
784
private function scan_knownFiles_main(){
785
$this->hasher->run($this); //Include this so we can call addIssue and ->api->
1463
$this->statusIDX['oldVersions'] = wfIssues::statusStart("Scanning for old themes, plugins and core files");
1464
1465
$this->updateCheck = new wfUpdateCheck();
1466
+ if ($this->isFullScan()) {
1467
+ $this->updateCheck->checkAllUpdates(false);
1468
+ $this->updateCheck->checkAllVulnerabilities();
1469
+ }
1470
+ else {
1471
+ $this->updateCheck->checkAllUpdates();
1472
+ }
1473
1474
foreach ($this->updateCheck->getPluginSlugs() as $slug) {
1475
$this->pluginRepoStatus[$slug] = false;
1486
}
1487
1488
private function scan_oldVersions_main() {
1489
+ if (!$this->isFullScan()) {
1490
+ return;
1491
+ }
1492
+
1493
if (!function_exists('plugins_api')) {
1494
require_once(ABSPATH . 'wp-admin/includes/plugin-install.php');
1495
}
1534
1535
private function scan_oldVersions_finish() {
1536
$haveIssues = wfIssues::STATUS_SECURE;
1537
+
1538
+ if (!$this->isFullScan()) {
1539
+ $this->deleteNewIssues(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
1540
+ }
1541
1542
// WordPress core updates needed
1543
if ($this->updateCheck->needsCoreUpdate()) {
1589
}
1590
}
1591
1592
+ if ($this->isFullScan()) {
1593
+ //Abandoned plugins
1594
+ foreach ($this->pluginRepoStatus as $slug => $status) {
1595
+ if ($status !== false && !is_wp_error($status) && property_exists($status, 'last_updated')) {
1596
+ $lastUpdateTimestamp = strtotime($status->last_updated);
1597
+ if ($lastUpdateTimestamp > 0 && (time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
1598
+ $statusArray = (array) $status;
1599
+ $statusArray['dateUpdated'] = wfUtils::formatLocalTime(get_option('date_format'), $lastUpdateTimestamp);
1600
+ $severity = 2; //Warning
1601
+ $statusArray['abandoned'] = true;
1602
+ $statusArray['vulnerable'] = false;
1603
+ $vulnerable = $this->updateCheck->isPluginVulnerable($slug, $statusArray['version']);
1604
+ if ($vulnerable) {
1605
+ $severity = 1; //Critical
1606
+ $statusArray['vulnerable'] = true;
1607
+ if (is_string($vulnerable)) {
1608
+ $statusArray['vulnerabilityLink'] = $vulnerable;
1609
+ }
1610
+ }
1611
+
1612
+ if (isset($allPlugins[$slug])) {
1613
+ $statusArray['wpURL'] = $allPlugins[$slug]['wpURL'];
1614
+ }
1615
+
1616
+ $testedShort = '';
1617
+ $testedLong = '';
1618
+ if (isset($statusArray['tested'])) {
1619
+ $testedShort = ', tested to WP ' . $statusArray['tested'];
1620
+ $testedLong = ' and tested up to WordPress ' . esc_html($statusArray['tested']);
1621
}
1622
1623
+ $key = "wfPluginAbandoned {$slug} {$statusArray['version']}";
1624
+ $shortMsg = 'The Plugin "' . $statusArray['name'] . '" appears to be abandoned (updated ' . wfUtils::formatLocalTime(get_option('date_format'), $lastUpdateTimestamp) . "{$testedShort}).";
1625
+ $longMsg = 'It was last updated ' . wfUtils::makeTimeAgo(time() - $lastUpdateTimestamp) . " ago{$testedLong}.";
1626
+ if ($statusArray['vulnerable']) {
1627
+ $longMsg .= ' It has unpatched security issues and may have compatibility problems with the current version of WordPress.';
1628
}
1629
else {
1630
+ $longMsg .= ' It may have compatibility problems with the current version of WordPress or unknown security issues.';
1631
}
1632
+ $longMsg .= ' <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_appears_to_be_abandoned" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1633
+ $added = $this->addIssue('wfPluginAbandoned', $severity, $key, $key, $shortMsg, $longMsg, $statusArray);
1634
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1635
else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1636
1637
unset($allPlugins[$slug]);
1638
}
1639
}
1640
+ else if ($status !== false && is_wp_error($status) && isset($status->error_data['plugins_api_failed']) && $status->error_data['plugins_api_failed'] == 'N;') { //The plugin does not exist in the wp.org repo
1641
+ $knownFiles = $this->getKnownFilesLoader()->getKnownFiles();
1642
+ $requestedPlugins = $this->getPlugins();
1643
+ foreach ($requestedPlugins as $key => $data) {
1644
+ $testKey = trailingslashit($data['FullDir']) . '.';
1645
+ if ($data['ShortDir'] == $slug && isset($knownFiles['plugins'][$testKey])) { //It existed in the repo at some point and was removed
1646
+ $pluginFile = wfUtils::getPluginBaseDir() . $key;
1647
+ $pluginData = get_plugin_data($pluginFile);
1648
+ $pluginData['wpRemoved'] = true;
1649
+ $pluginData['vulnerable'] = false;
1650
+ $vulnerable = $this->updateCheck->isPluginVulnerable($slug, $pluginData['Version']);
1651
+ if ($vulnerable) {
1652
+ $pluginData['vulnerable'] = true;
1653
+ if (is_string($vulnerable)) {
1654
+ $pluginData['vulnerabilityLink'] = $vulnerable;
1655
+ }
1656
+ }
1657
+
1658
+ $key = "wfPluginRemoved {$slug} {$pluginData['Version']}";
1659
+ $shortMsg = 'The Plugin "' . $pluginData['Name'] . '" has been removed from wordpress.org.';
1660
+ if ($pluginData['vulnerable']) {
1661
+ $longMsg = 'It has unpatched security issues and may have compatibility problems with the current version of WordPress.';
1662
+ }
1663
+ else {
1664
+ $longMsg = 'It may have compatibility problems with the current version of WordPress or unknown security issues.';
1665
+ }
1666
+ $longMsg .= ' <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_has_been_removed_from_wordpress.org" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1667
+ $added = $this->addIssue('wfPluginRemoved', 1, $key, $key, $shortMsg, $longMsg, $pluginData);
1668
+ if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1669
+ else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1670
+
1671
+ unset($allPlugins[$slug]);
1672
+ }
1673
+ }
1674
}
1675
}
1676
+
1677
+ //Other vulnerable plugins
1678
+ //Disabled until we improve the data source to weed out false positives
1679
+ /*if (count($allPlugins) > 0) {
1680
+ foreach ($allPlugins as $plugin) {
1681
+ if (!isset($plugin['vulnerable']) || !$plugin['vulnerable']) {
1682
+ continue;
1683
+ }
1684
+
1685
+ $key = 'wfPluginVulnerable' . ' ' . $plugin['pluginFile'] . ' ' . $plugin['Version'];
1686
+ $shortMsg = "The Plugin \"" . $plugin['Name'] . "\" has an unpatched security vulnerability.";
1687
+ $longMsg = 'To protect your site from this vulnerability, the safest option is to deactivate and completely remove ' . esc_html($plugin['Name']) . ' until the developer releases a security fix. <a href="https://docs.wordfence.com/en/Understanding_scan_results#Plugin_has_an_unpatched_security_vulnerability" target="_blank" rel="noopener noreferrer">Get more information.</a>';
1688
+ $added = $this->addIssue('wfPluginVulnerable', 1, $key, $key, $shortMsg, $longMsg, $plugin);
1689
+ if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) { $haveIssues = wfIssues::STATUS_PROBLEM; }
1690
+ else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) { $haveIssues = wfIssues::STATUS_IGNORED; }
1691
+
1692
+ if (isset($plugin['slug'])) {
1693
+ unset($allPlugins[$plugin['slug']]);
1694
+ }
1695
+ }
1696
+ }*/
1697
+ }
1698
1699
$this->updateCheck = false;
1700
$this->pluginRepoStatus = array();
1754
throw new Exception("Scan was killed on administrator request.");
1755
}
1756
}
1757
+ public static function startScan($isFork = false, $scanMode = self::SCAN_MODE_FULL){
1758
if(! $isFork){ //beginning of scan
1759
wfConfig::inc('totalScansRun');
1760
wfConfig::set('wfKillRequested', 0, wfConfig::DONT_AUTOLOAD);
1780
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
1781
if( (! wfConfig::get('startScansRemotely', false)) && (! is_wp_error($testResult)) && (is_array($testResult) || $testResult instanceof ArrayAccess) && strstr($testResult['body'], 'WFSCANTESTOK') !== false){
1782
//ajax requests can be sent by the server to itself
1783
+ $cronURL = 'admin-ajax.php?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
1784
$cronURL = admin_url($cronURL);
1785
$headers = array('Referer' => false/*, 'Cookie' => 'XDEBUG_SESSION=1'*/);
1786
wordfence::status(4, 'info', "Starting cron with normal ajax at URL $cronURL");
1794
} else {
1795
$cronURL = admin_url('admin-ajax.php');
1796
$cronURL = preg_replace('/^(https?:\/\/)/i', '$1noc1.wordfence.com/scanp/', $cronURL);
1797
+ $cronURL .= '?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
1798
$headers = array();
1799
wordfence::status(4, 'info', "Starting cron via proxy at URL $cronURL");
1800
lib/wfUpdateCheck.php CHANGED
@@ -137,7 +137,11 @@ class wfUpdateCheck {
137
138
//Check the vulnerability database
139
if ($slug !== null && isset($data['Version'])) {
140
- $data['vulnerable'] = $this->isPluginVulnerable($slug, $data['Version']);
141
}
142
else {
143
$data['vulnerable'] = false;
@@ -175,7 +179,11 @@ class wfUpdateCheck {
175
176
//Check the vulnerability database
177
if (isset($valsArray['slug']) && isset($data['Version'])) {
178
- $data['vulnerable'] = $this->isPluginVulnerable($valsArray['slug'], $data['Version']);
179
}
180
else {
181
$data['vulnerable'] = false;
@@ -396,6 +404,9 @@ class wfUpdateCheck {
396
foreach ($vulnerableList as $r) {
397
if ($r['slug'] == $v['slug']) {
398
$v['vulnerable'] = !!$r['vulnerable'];
399
break;
400
}
401
}
@@ -477,15 +488,19 @@ class wfUpdateCheck {
477
foreach ($vulnerabilities as $v) {
478
if ($v['slug'] == $slug) {
479
if ($v['fromVersion'] == 'Unknown' && $v['toVersion'] == 'Unknown') {
480
return $v['vulnerable'];
481
}
482
else if ((!isset($v['toVersion']) || $v['toVersion'] == 'Unknown') && version_compare($version, $v['fromVersion']) >= 0) {
483
return $v['vulnerable'];
484
}
485
else if ($v['fromVersion'] == 'Unknown' && isset($v['toVersion']) && version_compare($version, $v['toVersion']) < 0) {
486
return $v['vulnerable'];
487
}
488
else if (version_compare($version, $v['fromVersion']) >= 0 && isset($v['toVersion']) && version_compare($version, $v['toVersion']) < 0) {
489
return $v['vulnerable'];
490
}
491
}
137
138
//Check the vulnerability database
139
if ($slug !== null && isset($data['Version'])) {
140
+ $status = $this->isPluginVulnerable($slug, $data['Version']);
141
+ $data['vulnerable'] = !!$status;
142
+ if (is_string($status)) {
143
+ $data['vulnerabilityLink'] = $status;
144
+ }
145
}
146
else {
147
$data['vulnerable'] = false;
179
180
//Check the vulnerability database
181
if (isset($valsArray['slug']) && isset($data['Version'])) {
182
+ $status = $this->isPluginVulnerable($valsArray['slug'], $data['Version']);
183
+ $data['vulnerable'] = !!$status;
184
+ if (is_string($status)) {
185
+ $data['vulnerabilityLink'] = $status;
186
+ }
187
}
188
else {
189
$data['vulnerable'] = false;
404
foreach ($vulnerableList as $r) {
405
if ($r['slug'] == $v['slug']) {
406
$v['vulnerable'] = !!$r['vulnerable'];
407
+ if (isset($r['link'])) {
408
+ $v['link'] = $r['link'];
409
+ }
410
break;
411
}
412
}
488
foreach ($vulnerabilities as $v) {
489
if ($v['slug'] == $slug) {
490
if ($v['fromVersion'] == 'Unknown' && $v['toVersion'] == 'Unknown') {
491
+ if ($v['vulnerable'] && isset($v['link']) && is_string($v['link'])) { return $v['link']; }
492
return $v['vulnerable'];
493
}
494
else if ((!isset($v['toVersion']) || $v['toVersion'] == 'Unknown') && version_compare($version, $v['fromVersion']) >= 0) {
495
+ if ($v['vulnerable'] && isset($v['link']) && is_string($v['link'])) { return $v['link']; }
496
return $v['vulnerable'];
497
}
498
else if ($v['fromVersion'] == 'Unknown' && isset($v['toVersion']) && version_compare($version, $v['toVersion']) < 0) {
499
+ if ($v['vulnerable'] && isset($v['link']) && is_string($v['link'])) { return $v['link']; }
500
return $v['vulnerable'];
501
}
502
else if (version_compare($version, $v['fromVersion']) >= 0 && isset($v['toVersion']) && version_compare($version, $v['toVersion']) < 0) {
503
+ if ($v['vulnerable'] && isset($v['link']) && is_string($v['link'])) { return $v['link']; }
504
return $v['vulnerable'];
505
}
506
}
lib/wfUtils.php CHANGED
@@ -19,7 +19,7 @@ class wfUtils {
19
if (function_exists('date_diff')) {
20
$now = new DateTime();
21
$utc = new DateTimeZone('UTC');
22
- $dtStr = gmdate("c", $now->getTimestamp() + $secs); //Have to do it this way because of PHP 5.2
23
$then = new DateTime($dtStr, $utc);
24
25
$diff = $then->diff($now);
@@ -103,6 +103,10 @@ class wfUtils {
103
$components[] = self::pluralize($secs, 'second');
104
}
105
106
return implode(' ', $components);
107
}
108
public static function pluralize($m1, $t1, $m2 = false, $t2 = false) {
@@ -793,7 +797,7 @@ class wfUtils {
793
return null;
794
}
795
$prefix = 'http';
796
- if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']) {
797
$prefix = 'https';
798
}
799
return $prefix . '://' . $host . $_SERVER['REQUEST_URI'];
@@ -1502,7 +1506,7 @@ class wfUtils {
1502
* @param bool $inline
1503
* @return string
1504
*/
1505
- public static function potentialBinaryStringToHTML($string, $inline = false) {
1506
$output = '';
1507
1508
if (!defined('ENT_SUBSTITUTE')) {
@@ -1521,7 +1525,7 @@ class wfUtils {
1521
$output .= $span . '\x' . str_pad(dechex($b), 2, '0', STR_PAD_LEFT) . '</span>';
1522
}
1523
else if ($b < 0x80) {
1524
- $output .= htmlspecialchars($c, ENT_QUOTES, 'UTF-8');
1525
}
1526
else { //Assume multi-byte UTF-8
1527
$bytes = 0;
@@ -1545,6 +1549,37 @@ class wfUtils {
1545
}
1546
}
1547
1548
if ($brokenUTF8) {
1549
$bytes = min($bytes, strlen($string) - $i);
1550
for ($n = 0; $n < $bytes; $n++) {
@@ -1555,7 +1590,7 @@ class wfUtils {
1555
$i += ($bytes - 1);
1556
}
1557
else {
1558
- $output .= htmlspecialchars(substr($string, $i, $bytes), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1559
$i += ($bytes - 1);
1560
}
1561
}
@@ -2011,13 +2046,32 @@ class wfUtils {
2011
*
2012
* @return int
2013
*/
2014
- public static function normalizedTime() {
2015
$offset = wfConfig::get('timeoffset_ntp', false);
2016
if ($offset === false) {
2017
$offset = wfConfig::get('timeoffset_wf', false);
2018
if ($offset === false) { $offset = 0; }
2019
}
2020
- return time() + $offset;
2021
}
2022
2023
/**
@@ -2033,7 +2087,7 @@ class wfUtils {
2033
}
2034
2035
$utc = new DateTimeZone('UTC');
2036
- $dtStr = gmdate("c", $timestamp); //Have to do it this way because of PHP 5.2
2037
$dt = new DateTime($dtStr, $utc);
2038
$tz = get_option('timezone_string');
2039
if (!empty($tz)) {
@@ -2043,7 +2097,7 @@ class wfUtils {
2043
$gmt = get_option('gmt_offset');
2044
if (!empty($gmt)) {
2045
if (PHP_VERSION_ID < 50510) {
2046
- $dtStr = gmdate("c", $timestamp + $gmt * 3600); //Have to do it this way because of < PHP 5.5.10
2047
$dt = new DateTime($dtStr, $utc);
2048
}
2049
else {
19
if (function_exists('date_diff')) {
20
$now = new DateTime();
21
$utc = new DateTimeZone('UTC');
22
+ $dtStr = gmdate("c", (int) ($now->getTimestamp() + $secs)); //Have to do it this way because of PHP 5.2
23
$then = new DateTime($dtStr, $utc);
24
25
$diff = $then->diff($now);
103
$components[] = self::pluralize($secs, 'second');
104
}
105
106
+ if (empty($components)) {
107
+ $components[] = 'less than 1 second';
108
+ }
109
+
110
return implode(' ', $components);
111
}
112
public static function pluralize($m1, $t1, $m2 = false, $t2 = false) {
797
return null;
798
}
799
$prefix = 'http';
800
+ if (is_ssl()) {
801
$prefix = 'https';
802
}
803
return $prefix . '://' . $host . $_SERVER['REQUEST_URI'];
1506
* @param bool $inline
1507
* @return string
1508
*/
1509
+ public static function potentialBinaryStringToHTML($string, $inline = false, $allowmb4 = false) {
1510
$output = '';
1511
1512
if (!defined('ENT_SUBSTITUTE')) {
1525
$output .= $span . '\x' . str_pad(dechex($b), 2, '0', STR_PAD_LEFT) . '</span>';
1526
}
1527
else if ($b < 0x80) {
1528
+ $output .= htmlspecialchars($c, ENT_QUOTES, 'ISO-8859-1');
1529
}
1530
else { //Assume multi-byte UTF-8
1531
$bytes = 0;
1549
}
1550
}
1551
1552
+ if (!$brokenUTF8) { //Ensure the byte sequences are within the accepted ranges: https://tools.ietf.org/html/rfc3629
1553
+ /*
1554
+ * UTF8-octets = *( UTF8-char )
1555
+ * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1556
+ * UTF8-1 = %x00-7F
1557
+ * UTF8-2 = %xC2-DF UTF8-tail
1558
+ * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1559
+ * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1560
+ * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1561
+ * %xF4 %x80-8F 2( UTF8-tail )
1562
+ * UTF8-tail = %x80-BF
1563
+ */
1564
+
1565
+ $testString = wfUtils::substr($string, $i, $bytes);
1566
+ $regex = '/^(?:' .
1567
+ '[\xc2-\xdf][\x80-\xbf]' . //UTF8-2
1568
+ '|' . '\xe0[\xa0-\xbf][\x80-\xbf]' . //UTF8-3
1569
+ '|' . '[\xe1-\xec][\x80-\xbf]{2}' .
1570
+ '|' . '\xed[\x80-\x9f][\x80-\xbf]' .
1571
+ '|' . '[\xee-\xef][\x80-\xbf]{2}';
1572
+ if ($allowmb4) {
1573
+ $regex .= '|' . '\xf0[\x90-\xbf][\x80-\xbf]{2}' . //UTF8-4
1574
+ '|' . '[\xf1-\xf3][\x80-\xbf]{3}' .
1575
+ '|' . '\xf4[\x80-\x8f][\x80-\xbf]{2}';
1576
+ }
1577
+ $regex .= ')#x2F;';
1578
+ if (!preg_match($regex, $testString)) {
1579
+ $brokenUTF8 = true;
1580
+ }
1581
+ }
1582
+
1583
if ($brokenUTF8) {
1584
$bytes = min($bytes, strlen($string) - $i);
1585
for ($n = 0; $n < $bytes; $n++) {
1590
$i += ($bytes - 1);
1591
}
1592
else {
1593
+ $output .= htmlspecialchars(wfUtils::substr($string, $i, $bytes), ENT_QUOTES | ENT_SUBSTITUTE, 'ISO-8859-1');
1594
$i += ($bytes - 1);
1595
}
1596
}
2046
*
2047
* @return int
2048
*/
2049
+ public static function normalizedTime($base = false) {
2050
+ if ($base === false) {
2051
+ $base = time();
2052
+ }
2053
+
2054
+ $offset = wfConfig::get('timeoffset_ntp', false);
2055
+ if ($offset === false) {
2056
+ $offset = wfConfig::get('timeoffset_wf', false);
2057
+ if ($offset === false) { $offset = 0; }
2058
+ }
2059
+ return $base + $offset;
2060
+ }
2061
+
2062
+ /**
2063
+ * Returns what we consider a true timestamp, adjusted as needed to match the local server's drift. We use this
2064
+ * because a significant number of servers are using a drastically incorrect time.
2065
+ *
2066
+ * @return int
2067
+ */
2068
+ public static function denormalizedTime($base) {
2069
$offset = wfConfig::get('timeoffset_ntp', false);
2070
if ($offset === false) {
2071
$offset = wfConfig::get('timeoffset_wf', false);
2072
if ($offset === false) { $offset = 0; }
2073
}
2074
+ return $base - $offset;
2075
}
2076
2077
/**
2087
}
2088
2089
$utc = new DateTimeZone('UTC');
2090
+ $dtStr = gmdate("c", (int) $timestamp); //Have to do it this way because of PHP 5.2
2091
$dt = new DateTime($dtStr, $utc);
2092
$tz = get_option('timezone_string');
2093
if (!empty($tz)) {
2097
$gmt = get_option('gmt_offset');
2098
if (!empty($gmt)) {
2099
if (PHP_VERSION_ID < 50510) {
2100
+ $dtStr = gmdate("c", (int) ($timestamp + $gmt * 3600)); //Have to do it this way because of < PHP 5.5.10
2101
$dt = new DateTime($dtStr, $utc);
2102
}
2103
else {
lib/wordfenceClass.php CHANGED
@@ -1,6 +1,7 @@
1
<?php
2
require_once('wordfenceConstants.php');
3
require_once('wfScanEngine.php');
4
require_once('wfCrawl.php');
5
require_once 'Diff.php';
6
require_once 'Diff/Renderer/Html/SideBySide.php';
@@ -210,6 +211,12 @@ class wordfence {
210
wfConfig::set('lastDashboardCheck', time());
211
wfDashboard::processDashboardResponse($keyData['dashboard']);
212
}
213
}
214
catch(Exception $e){
215
wordfence::status(4, 'error', "Could not verify Wordfence API Key: " . $e->getMessage());
@@ -281,6 +288,11 @@ class wordfence {
281
$report = new wfActivityReport();
282
$report->rotateIPLog();
283
self::_refreshUpdateNotification($report, true);
284
}
285
public static function _scheduleRefreshUpdateNotification($upgrader, $options) {
286
$defer = false;
@@ -1016,7 +1028,7 @@ SQL
1016
//This is messy, but not sure of a better way to do this without guaranteeing we get $wp_version
1017
require(ABSPATH . 'wp-includes/version.php');
1018
self::$wordfence_wp_version = $wp_version;
1019
- require('wfScan.php');
1020
wfScan::wfScanMain();
1021
1022
} //END doScan
@@ -1188,7 +1200,7 @@ SQL
1188
self::getLog()->lockOutIP(wfUtils::getIP(), $reason);
1189
//Then we send the email because email sending takes time and we want to block the baddie asap. If we don't users can get a lot of emails about a single attacker getting locked out.
1190
if(wfConfig::get('alertOn_loginLockout')){
1191
- wordfence::alert("User locked out from signing in", "A user with IP address $IP has been locked out from the signing in or using the password recovery form for the following reason: $reason", $IP);
1192
}
1193
}
1194
public static function isLockedOut($IP){
@@ -2592,20 +2604,20 @@ SQL
2592
2593
return 'Next scan in ' . wfUtils::makeDuration($difference) . ' (' . date('M j, Y g:i:s A', $nextTime + (3600 * get_option('gmt_offset'))) . ')';
2594
}
2595
- public static function wordfenceStartScheduledScan(){
2596
2597
//If scheduled scans are not enabled in the global config option, then don't run a scheduled scan.
2598
if(wfConfig::get('scheduledScansEnabled') != '1'){
2599
return;
2600
}
2601
2602
- //This prevents scheduled scans from piling up on low traffic blogs and all being run at once.
2603
- //Only one scheduled scan runs within a given 60 min window. Won't run if another scan has run within 30 mins.
2604
$lastScanStart = wfConfig::get('lastScheduledScanStart', 0);
2605
- if($lastScanStart && (time() - $lastScanStart) < 1800){
2606
- //A scheduled scan was started in the last 30 mins, so skip this one.
2607
return;
2608
}
2609
wfConfig::set('lastScheduledScanStart', time());
2610
wordfence::status(1, 'info', "Scheduled Wordfence scan starting at " . date('l jS \of F Y h:i:s A', current_time('timestamp')) );
2611
@@ -2615,57 +2627,64 @@ SQL
2615
2616
wfScanEngine::startScan();
2617
}
2618
- public static function scheduleScans(){ //Idempotent. Deschedules everything and schedules the following week.
2619
self::unscheduleAllScans();
2620
- $sched = wfConfig::get_ser('scanSched', array());
2621
- $mode = wfConfig::get('schedMode');
2622
- if($mode == 'manual' && is_array($sched) && is_array($sched[0]) ){
2623
- //Use sched as it is
2624
- } else { //Default to setting scans to run once a day at a randomly selected time.
2625
- $sched = array();
2626
- $runAt = rand(0,23);
2627
- for($day = 0; $day <= 6; $day++){
2628
- $sched[$day] = array();
2629
- for($hour = 0; $hour <= 23; $hour++){
2630
- if($hour == $runAt){
2631
- $sched[$day][$hour] = 1;
2632
- } else {
2633
- $sched[$day][$hour] = 0;
2634
}
2635
}
2636
}
2637
}
2638
- for($scheduledDay = 0; $scheduledDay <= 6; $scheduledDay++){
2639
- //0 is sunday
2640
- //6 is Saturday
2641
- for($scheduledHour = 0; $scheduledHour <= 23; $scheduledHour++){
2642
- if($sched[$scheduledDay][$scheduledHour]){
2643
- $wpTime = current_time('timestamp');
2644
- $currentDayOfWeek = date('w', $wpTime);
2645
- $daysInFuture = $scheduledDay - $currentDayOfWeek; //It's monday and scheduledDay is Wed (3) then result is 2 days in future. It's Wed and sched day is monday, then result is 3 - 1 = -2
2646
- if($daysInFuture < 0){ $daysInFuture += 7; } //Turns -2 into 5 days in future
2647
- $currentHour = date('G', $wpTime);
2648
- $secsOffset = ($scheduledHour - $currentHour) * 3600; //Offset from current hour, can be negative
2649
- $secondsInFuture = ($daysInFuture * 86400) + $secsOffset; //Can be negative, so we schedule those 1 week ahead
2650
- if($secondsInFuture < 1){
2651
- $secondsInFuture += (86400 * 7); //Add a week
2652
- }
2653
- $futureTime = time() - (time() % 3600) + $secondsInFuture; //Modulo rounds down to top of the hour
2654
- $futureTime += rand(0,3600); //Prevent a stampede of scans on our scanning server
2655
- wordfence::status(4, 'info', "Scheduled time for day $scheduledDay hour $scheduledHour is: " . date('l jS \of F Y h:i:s A', $futureTime));
2656
- self::scheduleSingleScan($futureTime);
2657
}
2658
}
2659
}
2660
}
2661
- public static function scheduleSingleScan($futureTime){
2662
// Removed ability to activate on network site in v5.3.12
2663
if (is_main_site()) {
2664
- wp_schedule_single_event($futureTime, 'wordfence_start_scheduled_scan');
2665
}
2666
}
2667
- private static function unscheduleAllScans(){
2668
wp_clear_scheduled_hook('wordfence_start_scheduled_scan');
2669
}
2670
public static function ajax_saveCountryBlocking_callback(){
2671
if(! wfConfig::get('isPaid')){
@@ -3071,6 +3090,12 @@ SQL
3071
wfConfig::set('lastDashboardCheck', time());
3072
wfDashboard::processDashboardResponse($keyData['dashboard']);
3073
}
3074
}
3075
catch (Exception $e){
3076
return array('errorMsg' => "Your options have been saved. However we tried to verify your API key with the Wordfence servers and received an error: " . wp_kses($e->getMessage(), array()) );
1
<?php
2
require_once('wordfenceConstants.php');
3
require_once('wfScanEngine.php');
4
+ require_once('wfScan.php');
5
require_once('wfCrawl.php');
6
require_once 'Diff.php';
7
require_once 'Diff/Renderer/Html/SideBySide.php';
211
wfConfig::set('lastDashboardCheck', time());
212
wfDashboard::processDashboardResponse($keyData['dashboard']);
213
}
214
+ if (isset($keyData['scanSchedule']) && is_array($keyData['scanSchedule'])) {
215
+ wfConfig::set_ser('noc1ScanSchedule', $keyData['scanSchedule']);
216
+ if (!wfScan::isManualScanSchedule()) {
217
+ wordfence::scheduleScans();
218
+ }
219
+ }
220
}
221
catch(Exception $e){
222
wordfence::status(4, 'error', "Could not verify Wordfence API Key: " . $e->getMessage());
288
$report = new wfActivityReport();
289
$report->rotateIPLog();
290
self::_refreshUpdateNotification($report, true);
291
+
292
+ $next = self::getNextScanStartTimestamp();
293
+ if ($next - time() > 3600 && wfScan::shouldRunScan(wfScanEngine::SCAN_MODE_QUICK)) {
294
+ wfScanEngine::startScan(false, wfScanEngine::SCAN_MODE_QUICK);
295
+ }
296
}
297
public static function _scheduleRefreshUpdateNotification($upgrader, $options) {
298
$defer = false;
1028
//This is messy, but not sure of a better way to do this without guaranteeing we get $wp_version
1029
require(ABSPATH . 'wp-includes/version.php');
1030
self::$wordfence_wp_version = $wp_version;
1031
+ require_once('wfScan.php');
1032
wfScan::wfScanMain();
1033
1034
} //END doScan
1200
self::getLog()->lockOutIP(wfUtils::getIP(), $reason);
1201
//Then we send the email because email sending takes time and we want to block the baddie asap. If we don't users can get a lot of emails about a single attacker getting locked out.
1202
if(wfConfig::get('alertOn_loginLockout')){
1203
+ wordfence::alert("User locked out from signing in", "A user with IP address {$IP} has been locked out from signing in or using the password recovery form for the following reason: {$reason}", $IP);
1204
}
1205
}
1206
public static function isLockedOut($IP){
2604
2605
return 'Next scan in ' . wfUtils::makeDuration($difference) . ' (' . date('M j, Y g:i:s A', $nextTime + (3600 * get_option('gmt_offset'))) . ')';
2606
}
2607
+ public static function wordfenceStartScheduledScan($scheduledStartTime) {
2608
2609
//If scheduled scans are not enabled in the global config option, then don't run a scheduled scan.
2610
if(wfConfig::get('scheduledScansEnabled') != '1'){
2611
return;
2612
}
2613
2614
+ $minimumFrequency = (wfScan::isManualScanSchedule() ? 1800 : 43200);
2615
$lastScanStart = wfConfig::get('lastScheduledScanStart', 0);
2616
+ if($lastScanStart && (time() - $lastScanStart) < $minimumFrequency){
2617
+ //A scheduled scan was started in the last 30 mins (manual schedule) or 12 hours (automatic schedule), so skip this one.
2618
return;
2619
}
2620
+ wfConfig::set('originalScheduledScanStart', $scheduledStartTime);
2621
wfConfig::set('lastScheduledScanStart', time());
2622
wordfence::status(1, 'info', "Scheduled Wordfence scan starting at " . date('l jS \of F Y h:i:s A', current_time('timestamp')) );
2623
2627
2628
wfScanEngine::startScan();
2629
}
2630
+ public static function scheduleScans() { //Idempotent. Deschedules everything and schedules the following week.
2631
self::unscheduleAllScans();
2632
+ if (wfScan::isManualScanSchedule()) {
2633
+ $sched = wfConfig::get_ser('scanSched', array());
2634
+ for ($scheduledDay = 0; $scheduledDay <= 6; $scheduledDay++) {
2635
+ //0 is sunday
2636
+ //6 is Saturday
2637
+ for ($scheduledHour = 0; $scheduledHour <= 23; $scheduledHour++) {
2638
+ if ($sched[$scheduledDay][$scheduledHour]) {
2639
+ $wpTime = current_time('timestamp');
2640
+ $currentDayOfWeek = date('w', $wpTime);
2641
+ $daysInFuture = $scheduledDay - $currentDayOfWeek; //It's monday and scheduledDay is Wed (3) then result is 2 days in future. It's Wed and sched day is monday, then result is 3 - 1 = -2
2642
+ if($daysInFuture < 0){ $daysInFuture += 7; } //Turns -2 into 5 days in future
2643
+ $currentHour = date('G', $wpTime);
2644
+ $secsOffset = ($scheduledHour - $currentHour) * 3600; //Offset from current hour, can be negative
2645
+ $secondsInFuture = ($daysInFuture * 86400) + $secsOffset; //Can be negative, so we schedule those 1 week ahead
2646
+ if($secondsInFuture < 1){
2647
+ $secondsInFuture += (86400 * 7); //Add a week
2648
+ }
2649
+ $futureTime = time() - (time() % 3600) + $secondsInFuture; //Modulo rounds down to top of the hour
2650
+ $futureTime += rand(0,3600); //Prevent a stampede of scans on our scanning server
2651
+ wordfence::status(4, 'info', "Scheduled time for day $scheduledDay hour $scheduledHour is: " . date('l jS \of F Y h:i:s A', $futureTime));
2652
+ self::scheduleSingleScan($futureTime);
2653
}
2654
}
2655
}
2656
}
2657
+ else {
2658
+ $noc1ScanSchedule = wfConfig::get_ser('noc1ScanSchedule', array());
2659
+ foreach ($noc1ScanSchedule as $timestamp) {
2660
+ $timestamp = wfUtils::denormalizedTime($timestamp);
2661
+ if ($timestamp > time()) {
2662
+ self::scheduleSingleScan($timestamp);
2663
}
2664
}
2665
}
2666
}
2667
+ public static function scheduleSingleScan($futureTime, $originalTime = false) {
2668
// Removed ability to activate on network site in v5.3.12
2669
if (is_main_site()) {
2670
+ if ($originalTime === false) {
2671
+ $originalTime = $futureTime;
2672
+ }
2673
+ wp_schedule_single_event($futureTime, 'wordfence_start_scheduled_scan', array((int) $originalTime));
2674
+
2675
+ //Saving our own copy of the schedule because the wp-cron functions all require the args list to act
2676
+ $allScansScheduled = wfConfig::get_ser('allScansScheduled', array());
2677
+ $allScansScheduled[] = array('timestamp' => $futureTime, 'args' => array((int) $originalTime));
2678
+ wfConfig::set_ser('allScansScheduled', $allScansScheduled);
2679
}
2680
}
2681
+ private static function unscheduleAllScans() {
2682
+ $allScansScheduled = wfConfig::get_ser('allScansScheduled', array());
2683
+ foreach ($allScansScheduled as $entry) {
2684
+ wp_unschedule_event($entry['timestamp'], 'wordfence_start_scheduled_scan', $entry['args']);
2685
+ }
2686
wp_clear_scheduled_hook('wordfence_start_scheduled_scan');
2687
+ wfConfig::set_ser('allScansScheduled', array());
2688
}
2689
public static function ajax_saveCountryBlocking_callback(){
2690
if(! wfConfig::get('isPaid')){
3090
wfConfig::set('lastDashboardCheck', time());
3091
wfDashboard::processDashboardResponse($keyData['dashboard']);
3092
}
3093
+ if (isset($keyData['scanSchedule']) && is_array($keyData['scanSchedule'])) {
3094
+ wfConfig::set_ser('noc1ScanSchedule', $keyData['scanSchedule']);
3095
+ if (!wfScan::isManualScanSchedule()) {
3096
+ wordfence::scheduleScans();
3097
+ }
3098
+ }
3099
}
3100
catch (Exception $e){
3101
return array('errorMsg' => "Your options have been saved. However we tried to verify your API key with the Wordfence servers and received an error: " . wp_kses($e->getMessage(), array()) );
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.8.0
6
- Stable tag: 6.3.12
7
8
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
@@ -160,6 +160,17 @@ Secure your website with Wordfence.
160
161
== Changelog ==
162
163
= 6.3.12 =
164
* Improvement: Adjusted the password audit to use a better cryptographic padding option.
165
* Improvement: Improved the option value entry process for the modified files exclusion list.
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.8.0
6
+ Stable tag: 6.3.14
7
8
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
160
161
== Changelog ==
162
163
+ = 6.3.14 =
164
+ * Improvement: Introduced smart scan distribution. Scan times are now distributed intelligently across servers to provide consistent server performance.
165
+ * Improvement: Introduced light-weight scan that runs frequently to perform checks that do not use any server resources.
166
+ * Improvement: If unable to successfully look up the status of an IP claiming to be Googlebot, the hit is now allowed.
167
+ * Improvement: Scan issue results for abandoned plugins and unpatched vulnerabilities include more info.
168
+ * Fix: Suppressed PHP notice with time formatting when a microtimestamp is passed.
169
+ * Fix: Improved binary data to HTML entity conversion to avoid wpdb stripping out-of-range UTF-8 sequences.
170
+ * Fix: Added better detection to SSL status, particularly for IIS.
171
+ * Fix: Fixed PHP notice in the diff renderer.
172
+ * Fix: Fixed typo in lockout alert.
173
+
174
= 6.3.12 =
175
* Improvement: Adjusted the password audit to use a better cryptographic padding option.
176
* Improvement: Improved the option value entry process for the modified files exclusion list.
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.3.12
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.3.12');
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.3.14
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.3.14');
15
define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17