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 | 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 +2 -1
- lib/email_newIssues.php +13 -2
- lib/menu_scan.php +6 -0
- lib/menu_scan_schedule.php +6 -7
- lib/wfConfig.php +1 -1
- lib/wfCrawl.php +20 -6
- lib/wfIssues.php +9 -2
- lib/wfScan.php +74 -2
- lib/wfScanEngine.php +189 -114
- lib/wfUpdateCheck.php +17 -2
- lib/wfUtils.php +63 -9
- lib/wordfenceClass.php +69 -44
- readme.txt +12 -1
- wordfence.php +2 -2
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 |
-
|
|
|
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
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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 (
|
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 (
|
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 (
|
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 (
|
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 (
|
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 (
|
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 |
-
|
|
|
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
|
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
|
154 |
} else if ($status === 'fakeBot') {
|
155 |
-
return
|
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
|
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
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
90 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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->
|
137 |
-
|
138 |
-
$
|
139 |
-
$
|
140 |
-
|
141 |
-
|
142 |
-
|
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 |
-
|
161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
181 |
-
|
182 |
-
|
|
|
|
|
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->
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
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->
|
1422 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1535 |
-
|
1536 |
-
|
1537 |
-
$
|
1538 |
-
|
1539 |
-
$
|
1540 |
-
|
1541 |
-
|
1542 |
-
|
1543 |
-
|
1544 |
-
|
1545 |
-
$
|
1546 |
-
$
|
1547 |
-
|
1548 |
-
|
1549 |
-
|
1550 |
-
|
1551 |
-
|
1552 |
-
|
1553 |
-
|
1554 |
-
|
1555 |
-
|
1556 |
-
|
1557 |
-
|
1558 |
-
|
1559 |
-
|
1560 |
-
$
|
1561 |
-
|
1562 |
-
|
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 = "
|
1585 |
-
$shortMsg = 'The Plugin "' . $
|
1586 |
-
|
1587 |
-
|
|
|
1588 |
}
|
1589 |
else {
|
1590 |
-
$longMsg
|
1591 |
}
|
1592 |
-
$longMsg .= ' <a href="https://docs.wordfence.com/en/Understanding_scan_results#
|
1593 |
-
$added = $this->addIssue('
|
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 |
-
|
1604 |
-
|
1605 |
-
|
1606 |
-
|
1607 |
-
|
1608 |
-
|
1609 |
-
|
1610 |
-
|
1611 |
-
|
1612 |
-
|
1613 |
-
|
1614 |
-
|
1615 |
-
|
1616 |
-
|
1617 |
-
|
1618 |
-
|
1619 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
|
|
|
|
|
|
|
|
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 |
-
$
|
|
|
|
|
|
|
|
|
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 (
|
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, '
|
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, '
|
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
|
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 .= ')$/';
|
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 |
-
|
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
|
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 |
-
|
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) <
|
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 |
-
|
2621 |
-
|
2622 |
-
|
2623 |
-
|
2624 |
-
|
2625 |
-
|
2626 |
-
|
2627 |
-
|
2628 |
-
|
2629 |
-
|
2630 |
-
|
2631 |
-
$
|
2632 |
-
|
2633 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2634 |
}
|
2635 |
}
|
2636 |
}
|
2637 |
}
|
2638 |
-
|
2639 |
-
|
2640 |
-
|
2641 |
-
|
2642 |
-
if($
|
2643 |
-
$
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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.
|
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.
|
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 |
|