Wordfence Security – Firewall & Malware Scan - Version 7.1.3

Version Description

  • Improvement: Improved the performance of our config table status check.
  • Improvement: The IP address of the user activating Wordfence is now used by the breached password check until an admin successfully logs in.
  • Improvement: Added several new error displays for scan failures to help diagnose and fix issues.
  • Improvement: Added the block duration to alerts generated when an IP is blocked.
  • Improvement: A text version of scan results is now included in the activity log email.
  • Improvement: The WAF install/uninstall process no longer asks to backup files that do not exist.
  • Change: Began a phased rollout of moving brute force queries to be https-only.
  • Change: Added the initial deprecation notice for PHP 5.2.
  • Change: Suppressed a script tag on the diagnostics page from being output in the email version.
  • Fix: Addressed an issue where plugins that return a null user during authentication would cause a PHP notice to be logged.
  • Fix: Fixed an issue where plugins that use non-standard version formatting could end up with a inaccurate vulnerability status.
  • Fix: Added a workaround for web email clients that erroneously encode some URL characters (e.g., #).
Download this release

Release Info

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

Code changes from version 7.1.2 to 7.1.3

Files changed (92) hide show
  1. css/{activity-report-widget.1522855379.css → activity-report-widget.1523983877.css} +0 -0
  2. css/{diff.1522855379.css → diff.1523983877.css} +0 -0
  3. css/{dt_table.1522855379.css → dt_table.1523983877.css} +0 -0
  4. css/{fullLog.1522855379.css → fullLog.1523983877.css} +0 -0
  5. css/{iptraf.1522855379.css → iptraf.1523983877.css} +0 -0
  6. css/{jquery-ui-timepicker-addon.1522855379.css → jquery-ui-timepicker-addon.1523983877.css} +0 -0
  7. css/{jquery-ui.min.1522855379.css → jquery-ui.min.1523983877.css} +0 -0
  8. css/{jquery-ui.structure.min.1522855379.css → jquery-ui.structure.min.1523983877.css} +0 -0
  9. css/{jquery-ui.theme.min.1522855379.css → jquery-ui.theme.min.1523983877.css} +0 -0
  10. css/{main.1522855379.css → main.1523983877.css} +0 -0
  11. css/{phpinfo.1522855379.css → phpinfo.1523983877.css} +0 -0
  12. css/{wf-adminbar.1522855379.css → wf-adminbar.1523983877.css} +0 -0
  13. css/{wf-colorbox.1522855379.css → wf-colorbox.1523983877.css} +0 -0
  14. css/{wf-font-awesome.1522855379.css → wf-font-awesome.1523983877.css} +0 -0
  15. css/{wf-ionicons.1522855379.css → wf-ionicons.1523983877.css} +0 -0
  16. css/{wf-onboarding.1522855379.css → wf-onboarding.1523983877.css} +0 -0
  17. css/{wfselect2.min.1522855379.css → wfselect2.min.1523983877.css} +0 -0
  18. css/{wordfenceBox.1522855379.css → wordfenceBox.1523983877.css} +0 -0
  19. js/{Chart.bundle.min.1522855379.js → Chart.bundle.min.1523983877.js} +0 -0
  20. js/{admin.1522855379.js → admin.1523983877.js} +13 -2
  21. js/{admin.ajaxWatcher.1522855379.js → admin.ajaxWatcher.1523983877.js} +0 -0
  22. js/{admin.liveTraffic.1522855379.js → admin.liveTraffic.1523983877.js} +0 -0
  23. js/{date.1522855379.js → date.1523983877.js} +0 -0
  24. js/{jquery-ui-timepicker-addon.1522855379.js → jquery-ui-timepicker-addon.1523983877.js} +0 -0
  25. js/{jquery.colorbox-min.1522855379.js → jquery.colorbox-min.1523983877.js} +0 -0
  26. js/{jquery.colorbox.1522855379.js → jquery.colorbox.1523983877.js} +0 -0
  27. js/{jquery.dataTables.min.1522855379.js → jquery.dataTables.min.1523983877.js} +0 -0
  28. js/{jquery.qrcode.min.1522855379.js → jquery.qrcode.min.1523983877.js} +0 -0
  29. js/{jquery.tmpl.min.1522855379.js → jquery.tmpl.min.1523983877.js} +0 -0
  30. js/{jquery.tools.min.1522855379.js → jquery.tools.min.1523983877.js} +0 -0
  31. js/{knockout-3.3.0.1522855379.js → knockout-3.3.0.1523983877.js} +0 -0
  32. js/{perf.1522855379.js → perf.1523983877.js} +0 -0
  33. js/{wfdashboard.1522855379.js → wfdashboard.1523983877.js} +0 -0
  34. js/{wfdropdown.1522855379.js → wfdropdown.1523983877.js} +0 -0
  35. js/{wfglobal.1522855379.js → wfglobal.1523983877.js} +0 -0
  36. js/{wfpopover.1522855379.js → wfpopover.1523983877.js} +0 -0
  37. js/{wfselect2.min.1522855379.js → wfselect2.min.1523983877.js} +0 -0
  38. lib/menu_dashboard_options.php +1 -1
  39. lib/menu_firewall_blocking_options.php +1 -1
  40. lib/menu_firewall_waf_options.php +1 -1
  41. lib/menu_options.php +1 -1
  42. lib/menu_scanner_options.php +1 -1
  43. lib/menu_tools_diagnostic.php +2 -0
  44. lib/wfAPI.php +18 -6
  45. lib/wfConfig.php +24 -8
  46. lib/wfCredentialsController.php +20 -2
  47. lib/wfDiagnostic.php +2 -2
  48. lib/wfIssues.php +24 -0
  49. lib/wfLog.php +8 -3
  50. lib/wfScan.php +109 -65
  51. lib/wfScanEngine.php +75 -21
  52. lib/wfSupportController.php +10 -0
  53. lib/wfUtils.php +0 -39
  54. lib/wfVersionCheckController.php +291 -0
  55. lib/wordfenceClass.php +135 -11
  56. lib/wordfenceConstants.php +2 -0
  57. readme.txt +16 -1
  58. views/blocking/block-list.php +1 -1
  59. views/scanner/issue-base.php +103 -0
  60. views/scanner/issue-checkGSB.php +6 -0
  61. views/scanner/issue-checkHowGetIPs.php +4 -0
  62. views/scanner/issue-checkSpamIP.php +4 -0
  63. views/scanner/issue-commentBadURL.php +13 -1
  64. views/scanner/issue-configReadable.php +7 -1
  65. views/scanner/issue-coreUnknown.php +5 -1
  66. views/scanner/issue-database.php +7 -0
  67. views/scanner/issue-diskSpace.php +7 -1
  68. views/scanner/issue-dnsChange.php +8 -1
  69. views/scanner/issue-easyPassword.php +9 -1
  70. views/scanner/issue-file.php +11 -1
  71. views/scanner/issue-geoipSupport.php +5 -1
  72. views/scanner/issue-knownfile.php +10 -1
  73. views/scanner/issue-optionBadURL.php +11 -0
  74. views/scanner/issue-postBadTitle.php +12 -1
  75. views/scanner/issue-postBadURL.php +13 -1
  76. views/scanner/issue-publiclyAccessible.php +7 -1
  77. views/scanner/issue-spamvertizeCheck.php +4 -0
  78. views/scanner/issue-suspiciousAdminUsers.php +5 -1
  79. views/scanner/issue-timelimit.php +5 -1
  80. views/scanner/issue-wfPluginAbandoned.php +14 -1
  81. views/scanner/issue-wfPluginRemoved.php +11 -1
  82. views/scanner/issue-wfPluginUpgrade.php +14 -1
  83. views/scanner/issue-wfPluginVulnerable.php +12 -1
  84. views/scanner/issue-wfThemeUpgrade.php +13 -1
  85. views/scanner/issue-wfUpgrade.php +11 -1
  86. views/scanner/issue-wpscan_directoryList.php +7 -1
  87. views/scanner/issue-wpscan_fullPathDiscl.php +7 -1
  88. views/scanner/scan-failed.php +1 -1
  89. views/waf/options-group-basic-firewall.php +10 -3
  90. views/waf/waf-install.php +12 -4
  91. views/waf/waf-uninstall.php +12 -4
  92. wordfence.php +3 -3
css/{activity-report-widget.1522855379.css → activity-report-widget.1523983877.css} RENAMED
File without changes
css/{diff.1522855379.css → diff.1523983877.css} RENAMED
File without changes
css/{dt_table.1522855379.css → dt_table.1523983877.css} RENAMED
File without changes
css/{fullLog.1522855379.css → fullLog.1523983877.css} RENAMED
File without changes
css/{iptraf.1522855379.css → iptraf.1523983877.css} RENAMED
File without changes
css/{jquery-ui-timepicker-addon.1522855379.css → jquery-ui-timepicker-addon.1523983877.css} RENAMED
File without changes
css/{jquery-ui.min.1522855379.css → jquery-ui.min.1523983877.css} RENAMED
File without changes
css/{jquery-ui.structure.min.1522855379.css → jquery-ui.structure.min.1523983877.css} RENAMED
File without changes
css/{jquery-ui.theme.min.1522855379.css → jquery-ui.theme.min.1523983877.css} RENAMED
File without changes
css/{main.1522855379.css → main.1523983877.css} RENAMED
File without changes
css/{phpinfo.1522855379.css → phpinfo.1523983877.css} RENAMED
File without changes
css/{wf-adminbar.1522855379.css → wf-adminbar.1523983877.css} RENAMED
File without changes
css/{wf-colorbox.1522855379.css → wf-colorbox.1523983877.css} RENAMED
File without changes
css/{wf-font-awesome.1522855379.css → wf-font-awesome.1523983877.css} RENAMED
File without changes
css/{wf-ionicons.1522855379.css → wf-ionicons.1523983877.css} RENAMED
File without changes
css/{wf-onboarding.1522855379.css → wf-onboarding.1523983877.css} RENAMED
File without changes
css/{wfselect2.min.1522855379.css → wfselect2.min.1523983877.css} RENAMED
File without changes
css/{wordfenceBox.1522855379.css → wordfenceBox.1523983877.css} RENAMED
File without changes
js/{Chart.bundle.min.1522855379.js → Chart.bundle.min.1523983877.js} RENAMED
File without changes
js/{admin.1522855379.js → admin.1523983877.js} RENAMED
@@ -128,7 +128,7 @@
128
$(window).trigger('wfTabChange', [tab.data('target')]);
129
});
130
if (window.location.hash) {
131
- var hashes = window.location.hash.split('#');
132
var hash = hashes[hashes.length - 1];
133
for (var i = 0; i < tabs.length; i++) {
134
if (hash == jQuery(tabs[i]).closest('.wf-tab').data('target')) {
@@ -140,7 +140,7 @@
140
jQuery(tabs[0]).trigger('click');
141
}
142
jQuery(window).on('hashchange', function () {
143
- var hashes = window.location.hash.split('#');
144
var hash = hashes[hashes.length - 1];
145
for (var i = 0; i < tabs.length; i++) {
146
if (hash == jQuery(tabs[i]).closest('.wf-tab').data('target')) {
@@ -2226,6 +2226,17 @@
2226
makeViewOptionLink: function(option, siteID) {
2227
return WordfenceAdminVars.siteBaseURL + '?_wfsf=viewOption&nonce=' + this.nonce + '&option=' + encodeURIComponent(option) + '&site_id=' + encodeURIComponent(siteID);
2228
},
2229
makeTimeAgo: function(t) {
2230
var months = Math.floor(t / (86400 * 30));
2231
var days = Math.floor(t / 86400);
128
$(window).trigger('wfTabChange', [tab.data('target')]);
129
});
130
if (window.location.hash) {
131
+ var hashes = WFAD.parseHashes();
132
var hash = hashes[hashes.length - 1];
133
for (var i = 0; i < tabs.length; i++) {
134
if (hash == jQuery(tabs[i]).closest('.wf-tab').data('target')) {
140
jQuery(tabs[0]).trigger('click');
141
}
142
jQuery(window).on('hashchange', function () {
143
+ var hashes = WFAD.parseHashes();
144
var hash = hashes[hashes.length - 1];
145
for (var i = 0; i < tabs.length; i++) {
146
if (hash == jQuery(tabs[i]).closest('.wf-tab').data('target')) {
2226
makeViewOptionLink: function(option, siteID) {
2227
return WordfenceAdminVars.siteBaseURL + '?_wfsf=viewOption&nonce=' + this.nonce + '&option=' + encodeURIComponent(option) + '&site_id=' + encodeURIComponent(siteID);
2228
},
2229
+ parseHashes: function() {
2230
+ var hashes = window.location.hash.replace('%23', '#');
2231
+ var splitHashes = hashes.split('#');
2232
+ var result = [];
2233
+ for (var i = 0; i < splitHashes.length; i++) {
2234
+ if (splitHashes[i].length > 0) {
2235
+ result.push(splitHashes[i]);
2236
+ }
2237
+ }
2238
+ return result;
2239
+ },
2240
makeTimeAgo: function(t) {
2241
var months = Math.floor(t / (86400 * 30));
2242
var days = Math.floor(t / 86400);
js/{admin.ajaxWatcher.1522855379.js → admin.ajaxWatcher.1523983877.js} RENAMED
File without changes
js/{admin.liveTraffic.1522855379.js → admin.liveTraffic.1523983877.js} RENAMED
File without changes
js/{date.1522855379.js → date.1523983877.js} RENAMED
File without changes
js/{jquery-ui-timepicker-addon.1522855379.js → jquery-ui-timepicker-addon.1523983877.js} RENAMED
File without changes
js/{jquery.colorbox-min.1522855379.js → jquery.colorbox-min.1523983877.js} RENAMED
File without changes
js/{jquery.colorbox.1522855379.js → jquery.colorbox.1523983877.js} RENAMED
File without changes
js/{jquery.dataTables.min.1522855379.js → jquery.dataTables.min.1523983877.js} RENAMED
File without changes
js/{jquery.qrcode.min.1522855379.js → jquery.qrcode.min.1523983877.js} RENAMED
File without changes
js/{jquery.tmpl.min.1522855379.js → jquery.tmpl.min.1523983877.js} RENAMED
File without changes
js/{jquery.tools.min.1522855379.js → jquery.tools.min.1523983877.js} RENAMED
File without changes
js/{knockout-3.3.0.1522855379.js → knockout-3.3.0.1523983877.js} RENAMED
File without changes
js/{perf.1522855379.js → perf.1523983877.js} RENAMED
File without changes
js/{wfdashboard.1522855379.js → wfdashboard.1523983877.js} RENAMED
File without changes
js/{wfdropdown.1522855379.js → wfdropdown.1523983877.js} RENAMED
File without changes
js/{wfglobal.1522855379.js → wfglobal.1523983877.js} RENAMED
File without changes
js/{wfpopover.1522855379.js → wfpopover.1523983877.js} RENAMED
File without changes
js/{wfselect2.min.1522855379.js → wfselect2.min.1523983877.js} RENAMED
File without changes
lib/menu_dashboard_options.php CHANGED
@@ -12,7 +12,7 @@ $d = new wfDashboard();
12
13
//Hash-based option block linking
14
if (window.location.hash) {
15
- var hashes = window.location.hash.split('#');
16
var hash = hashes[hashes.length - 1];
17
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
18
if (block) {
12
13
//Hash-based option block linking
14
if (window.location.hash) {
15
+ var hashes = WFAD.parseHashes();
16
var hash = hashes[hashes.length - 1];
17
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
18
if (block) {
lib/menu_firewall_blocking_options.php CHANGED
@@ -13,7 +13,7 @@ if (isset($_GET['source']) && wfPage::isValidPage($_GET['source'])) {
13
14
//Hash-based option block linking
15
if (window.location.hash) {
16
- var hashes = window.location.hash.split('#');
17
var hash = hashes[hashes.length - 1];
18
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
19
if (block) {
13
14
//Hash-based option block linking
15
if (window.location.hash) {
16
+ var hashes = WFAD.parseHashes();
17
var hash = hashes[hashes.length - 1];
18
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
19
if (block) {
lib/menu_firewall_waf_options.php CHANGED
@@ -26,7 +26,7 @@ if (isset($_GET['source']) && wfPage::isValidPage($_GET['source'])) {
26
27
//Hash-based option block linking
28
if (window.location.hash) {
29
- var hashes = window.location.hash.split('#');
30
var hash = hashes[hashes.length - 1];
31
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
32
if (block.length) {
26
27
//Hash-based option block linking
28
if (window.location.hash) {
29
+ var hashes = WFAD.parseHashes();
30
var hash = hashes[hashes.length - 1];
31
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
32
if (block.length) {
lib/menu_options.php CHANGED
@@ -27,7 +27,7 @@ if (isset($_GET['source']) && wfPage::isValidPage($_GET['source'])) {
27
28
//Hash-based option block linking
29
if (window.location.hash) {
30
- var hashes = window.location.hash.split('#');
31
var hash = hashes[hashes.length - 1];
32
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
33
if (block.length) {
27
28
//Hash-based option block linking
29
if (window.location.hash) {
30
+ var hashes = WFAD.parseHashes();
31
var hash = hashes[hashes.length - 1];
32
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
33
if (block.length) {
lib/menu_scanner_options.php CHANGED
@@ -15,7 +15,7 @@ if (isset($_GET['source']) && wfPage::isValidPage($_GET['source'])) {
15
16
//Hash-based option block linking
17
if (window.location.hash) {
18
- var hashes = window.location.hash.split('#');
19
var hash = hashes[hashes.length - 1];
20
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
21
if (block) {
15
16
//Hash-based option block linking
17
if (window.location.hash) {
18
+ var hashes = WFAD.parseHashes();
19
var hash = hashes[hashes.length - 1];
20
var block = $('.wf-block[data-persistence-key="' + hash + '"]');
21
if (block) {
lib/menu_tools_diagnostic.php CHANGED
@@ -17,6 +17,7 @@ if (!isset($sendingDiagnosticEmail)) {
17
$sendingDiagnosticEmail = false;
18
}
19
?>
20
<script type="application/javascript">
21
(function($) {
22
$(function() {
@@ -24,6 +25,7 @@ if (!isset($sendingDiagnosticEmail)) {
24
});
25
})(jQuery);
26
</script>
27
<div id="wf-diagnostics">
28
<?php if (!$sendingDiagnosticEmail): ?>
29
<div class="wf-diagnostics-wrapper">
17
$sendingDiagnosticEmail = false;
18
}
19
?>
20
+ <?php if (!$sendingDiagnosticEmail): ?>
21
<script type="application/javascript">
22
(function($) {
23
$(function() {
25
});
26
})(jQuery);
27
</script>
28
+ <?php endif; ?>
29
<div id="wf-diagnostics">
30
<?php if (!$sendingDiagnosticEmail): ?>
31
<div class="wf-diagnostics-wrapper">
lib/wfAPI.php CHANGED
@@ -27,7 +27,7 @@ class wfAPI {
27
//Sanity check. Developer should call wfAPI::SSLEnabled() to check if SSL is enabled before forcing SSL and return a user friendly msg if it's not.
28
if ($forceSSL && (!preg_match('/^https:/i', $apiURL))) {
29
//User's should never see this message unless we aren't calling SSLEnabled() to check if SSL is enabled before using call() with forceSSL
30
- throw new Exception("SSL is not supported by your web server and is required to use this function. Please ask your hosting provider or site admin to install cURL with openSSL to use this feature.");
31
}
32
$json = $this->getURL(rtrim($apiURL, '/') . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&' . self::buildQuery(
33
array_merge(
@@ -35,7 +35,7 @@ class wfAPI {
35
$getParams
36
)), $postParams, $timeout);
37
if (!$json) {
38
- throw new Exception("We received an empty data response from the Wordfence scanning servers when calling the '$action' function.");
39
}
40
41
$dat = json_decode($json, true);
@@ -84,10 +84,10 @@ class wfAPI {
84
}
85
86
if (!is_array($dat)) {
87
- throw new Exception("We received a data structure that is not the expected array when contacting the Wordfence scanning servers and calling the '$action' function.");
88
}
89
if (is_array($dat) && isset($dat['errorMsg'])) {
90
- throw new Exception($dat['errorMsg']);
91
}
92
return $dat;
93
}
@@ -118,7 +118,7 @@ class wfAPI {
118
119
if (is_wp_error($response)) {
120
$error_message = $response->get_error_message();
121
- throw new Exception("There was an " . ($error_message ? '' : 'unknown ') . "error connecting to the Wordfence scanning servers" . ($error_message ? ": $error_message" : '.'));
122
}
123
124
$dateHeader = @$response['headers']['date'];
@@ -140,7 +140,7 @@ class wfAPI {
140
}
141
142
if (200 != $this->lastHTTPStatus) {
143
- throw new Exception("The Wordfence scanning servers are currently unavailable. This may be for maintenance or a temporary outage. If this still occurs in an hour, please contact support. [$this->lastHTTPStatus]");
144
}
145
146
$content = wp_remote_retrieve_body($response);
@@ -193,3 +193,15 @@ class wfAPI {
193
return wp_http_supports(array('ssl'));
194
}
195
}
27
//Sanity check. Developer should call wfAPI::SSLEnabled() to check if SSL is enabled before forcing SSL and return a user friendly msg if it's not.
28
if ($forceSSL && (!preg_match('/^https:/i', $apiURL))) {
29
//User's should never see this message unless we aren't calling SSLEnabled() to check if SSL is enabled before using call() with forceSSL
30
+ throw new wfAPICallSSLUnavailableException("SSL is not supported by your web server and is required to use this function. Please ask your hosting provider or site admin to install cURL with openSSL to use this feature.");
31
}
32
$json = $this->getURL(rtrim($apiURL, '/') . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&' . self::buildQuery(
33
array_merge(
35
$getParams
36
)), $postParams, $timeout);
37
if (!$json) {
38
+ throw new wfAPICallInvalidResponseException("We received an empty data response from the Wordfence scanning servers when calling the '$action' function.");
39
}
40
41
$dat = json_decode($json, true);
84
}
85
86
if (!is_array($dat)) {
87
+ throw new wfAPICallInvalidResponseException("We received a data structure that is not the expected array when contacting the Wordfence scanning servers and calling the '$action' function.");
88
}
89
if (is_array($dat) && isset($dat['errorMsg'])) {
90
+ throw new wfAPICallErrorResponseException($dat['errorMsg']);
91
}
92
return $dat;
93
}
118
119
if (is_wp_error($response)) {
120
$error_message = $response->get_error_message();
121
+ throw new wfAPICallFailedException("There was an " . ($error_message ? '' : 'unknown ') . "error connecting to the Wordfence scanning servers" . ($error_message ? ": $error_message" : '.'));
122
}
123
124
$dateHeader = @$response['headers']['date'];
140
}
141
142
if (200 != $this->lastHTTPStatus) {
143
+ throw new wfAPICallFailedException("The Wordfence scanning servers are currently unavailable. This may be for maintenance or a temporary outage. If this still occurs in an hour, please contact support. [$this->lastHTTPStatus]");
144
}
145
146
$content = wp_remote_retrieve_body($response);
193
return wp_http_supports(array('ssl'));
194
}
195
}
196
+
197
+ class wfAPICallSSLUnavailableException extends Exception {
198
+ }
199
+
200
+ class wfAPICallFailedException extends Exception {
201
+ }
202
+
203
+ class wfAPICallInvalidResponseException extends Exception {
204
+ }
205
+
206
+ class wfAPICallErrorResponseException extends Exception {
207
+ }
lib/wfConfig.php CHANGED
@@ -1,5 +1,7 @@
1
<?php
2
class wfConfig {
3
const AUTOLOAD = 'yes';
4
const DONT_AUTOLOAD = 'no';
5
@@ -290,14 +292,28 @@ class wfConfig {
290
291
return $options;
292
}
293
- public static function updateTableExists() {
294
- global $wpdb;
295
- self::$tableExists = $wpdb->get_col($wpdb->prepare(<<<SQL
296
- SELECT TABLE_NAME FROM information_schema.TABLES
297
- WHERE TABLE_SCHEMA=DATABASE()
298
- AND TABLE_NAME=%s
299
- SQL
300
- , self::table()));
301
}
302
private static function updateCachedOption($name, $val) {
303
$options = self::loadAllOptions();
1
<?php
2
class wfConfig {
3
+ const TABLE_EXISTS_OPTION = 'wordfence_installed';
4
+
5
const AUTOLOAD = 'yes';
6
const DONT_AUTOLOAD = 'no';
7
292
293
return $options;
294
}
295
+
296
+ /**
297
+ * Bases the table's existence on the option specified by wfConfig::TABLE_EXISTS_OPTION for performance. We only
298
+ * set that option just prior to deletion in the uninstall handler and after table creation in the install handler.
299
+ */
300
+ public static function updateTableExists($change = null) {
301
+ if ($change !== null) {
302
+ self::$tableExists = !!$change;
303
+ update_option(wfConfig::TABLE_EXISTS_OPTION, self::$tableExists);
304
+ return;
305
+ }
306
+
307
+ self::$tableExists = true;
308
+ $optionValue = get_option(wfConfig::TABLE_EXISTS_OPTION, null);
309
+ if ($optionValue === null) { //No value, set an initial one
310
+ global $wpdb;
311
+ self::updateTableExists(!!$wpdb->get_col($wpdb->prepare('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s', self::table())));
312
+ return;
313
+ }
314
+ if (!$optionValue) {
315
+ self::$tableExists = false;
316
+ }
317
}
318
private static function updateCachedOption($name, $val) {
319
$options = self::loadAllOptions();
lib/wfCredentialsController.php CHANGED
@@ -95,6 +95,13 @@ class wfCredentialsController {
95
delete_transient($key);
96
}
97
98
public static function hasPreviousLoginFromIP($user, $ip) {
99
global $wpdb;
100
$table_wfLogins = wfDB::networkTable('wfLogins');
@@ -110,8 +117,19 @@ class wfCredentialsController {
110
}
111
112
$lastAdminLogin = wfConfig::get_ser('lastAdminLogin');
113
- if (is_array($lastAdminLogin) && isset($lastAdminLogin['userID']) && $lastAdminLogin['userID'] == $id && isset($lastAdminLogin['IP']) && wfUtils::inet_pton($lastAdminLogin['IP']) == wfUtils::inet_pton($ip)) {
114
- return true;
115
}
116
117
return false;
95
delete_transient($key);
96
}
97
98
+ /**
99
+ * Returns whether or not we've seen a successful login from $ip for the given user.
100
+ *
101
+ * @param WP_User $user
102
+ * @param string $ip
103
+ * @return bool
104
+ */
105
public static function hasPreviousLoginFromIP($user, $ip) {
106
global $wpdb;
107
$table_wfLogins = wfDB::networkTable('wfLogins');
117
}
118
119
$lastAdminLogin = wfConfig::get_ser('lastAdminLogin');
120
+ if (is_array($lastAdminLogin) && isset($lastAdminLogin['userID']) && isset($lastAdminLogin['IP'])) {
121
+ if ($lastAdminLogin['userID'] == $id && wfUtils::inet_pton($lastAdminLogin['IP']) == wfUtils::inet_pton($ip)) {
122
+ return true;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ //Final check -- if the IP recorded at plugin activation matches, let it through. This is __only__ checked when we don't have any other record of an admin login.
128
+ $activatingIP = wfConfig::get('activatingIP');
129
+ if (wfUtils::isValidIP($activatingIP)) {
130
+ if (wfUtils::inet_pton($activatingIP) == wfUtils::inet_pton($ip)) {
131
+ return true;
132
+ }
133
}
134
135
return false;
lib/wfDiagnostic.php CHANGED
@@ -377,9 +377,9 @@ class wfDiagnostic
377
if (!function_exists('openssl_verify') || !defined('OPENSSL_VERSION_NUMBER') || !defined('OPENSSL_VERSION_TEXT')) {
378
return false;
379
}
380
- $compare = wfUtils::openssl_version_compare('1.0.1');
381
return array(
382
- 'test' => $compare < 0,
383
'message' => OPENSSL_VERSION_TEXT,
384
);
385
}
377
if (!function_exists('openssl_verify') || !defined('OPENSSL_VERSION_NUMBER') || !defined('OPENSSL_VERSION_TEXT')) {
378
return false;
379
}
380
+ $compare = wfVersionCheckController::shared()->checkOpenSSLVersion();
381
return array(
382
+ 'test' => $compare == wfVersionCheckController::VERSION_COMPATIBLE,
383
'message' => OPENSSL_VERSION_TEXT,
384
);
385
}
lib/wfIssues.php CHANGED
@@ -28,6 +28,13 @@ class wfIssues {
28
const SCAN_FAILED_DURATION_REACHED = 'duration';
29
const SCAN_FAILED_VERSION_CHANGE = 'versionchange';
30
const SCAN_FAILED_FORK_FAILED = 'forkfailed';
31
32
private $db = false;
33
@@ -42,6 +49,10 @@ class wfIssues {
42
public $totalWarningIssues = 0;
43
public $totalIgnoredIssues = 0;
44
45
public static function statusPrep(){
46
wfConfig::set_ser('wfStatusStartMsgs', array());
47
wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
@@ -125,12 +136,22 @@ class wfIssues {
125
}
126
}
127
128
$recordedFailure = wfConfig::get('lastScanFailureType');
129
switch ($recordedFailure) {
130
case self::SCAN_FAILED_GENERAL:
131
case self::SCAN_FAILED_DURATION_REACHED:
132
case self::SCAN_FAILED_VERSION_CHANGE:
133
case self::SCAN_FAILED_FORK_FAILED:
134
return $recordedFailure;
135
}
136
@@ -437,6 +458,9 @@ class wfIssues {
437
}
438
}
439
$issueList[$i]['issueIDX'] = $i;
440
}
441
}
442
return $ret; //array of lists of issues by status
28
const SCAN_FAILED_DURATION_REACHED = 'duration';
29
const SCAN_FAILED_VERSION_CHANGE = 'versionchange';
30
const SCAN_FAILED_FORK_FAILED = 'forkfailed';
31
+ const SCAN_FAILED_CALLBACK_TEST_FAILED = 'callbackfailed';
32
+ const SCAN_FAILED_START_TIMEOUT = 'starttimeout';
33
+
34
+ const SCAN_FAILED_API_SSL_UNAVAILABLE = 'sslunavailable';
35
+ const SCAN_FAILED_API_CALL_FAILED = 'apifailed';
36
+ const SCAN_FAILED_API_INVALID_RESPONSE = 'apiinvalid';
37
+ const SCAN_FAILED_API_ERROR_RESPONSE = 'apierror';
38
39
private $db = false;
40
49
public $totalWarningIssues = 0;
50
public $totalIgnoredIssues = 0;
51
52
+ public static function validIssueTypes() {
53
+ return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'dnsChange', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl');
54
+ }
55
+
56
public static function statusPrep(){
57
wfConfig::set_ser('wfStatusStartMsgs', array());
58
wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
136
}
137
}
138
139
+ $scanStartAttempt = wfConfig::get('scanStartAttempt', 0);
140
+ if ($scanStartAttempt && time() - $scanStartAttempt > WORDFENCE_SCAN_START_FAILURE_THRESHOLD) {
141
+ return self::SCAN_FAILED_START_TIMEOUT;
142
+ }
143
+
144
$recordedFailure = wfConfig::get('lastScanFailureType');
145
switch ($recordedFailure) {
146
case self::SCAN_FAILED_GENERAL:
147
case self::SCAN_FAILED_DURATION_REACHED:
148
case self::SCAN_FAILED_VERSION_CHANGE:
149
case self::SCAN_FAILED_FORK_FAILED:
150
+ case self::SCAN_FAILED_CALLBACK_TEST_FAILED:
151
+ case self::SCAN_FAILED_API_SSL_UNAVAILABLE:
152
+ case self::SCAN_FAILED_API_CALL_FAILED:
153
+ case self::SCAN_FAILED_API_INVALID_RESPONSE:
154
+ case self::SCAN_FAILED_API_ERROR_RESPONSE:
155
return $recordedFailure;
156
}
157
458
}
459
}
460
$issueList[$i]['issueIDX'] = $i;
461
+ if (isset($issueList[$i]['data']['cType'])) {
462
+ $issueList[$i]['data']['ucType'] = ucwords($issueList[$i]['data']['cType']);
463
+ }
464
}
465
}
466
return $ret; //array of lists of issues by status
lib/wfLog.php CHANGED
@@ -686,16 +686,21 @@ class wfLog {
686
$this->tagRequestForBlock($reason);
687
688
if (wfConfig::get('alertOn_block')) {
689
- wordfence::alert("Blocking IP {$IP}", "Wordfence has blocked IP address {$IP}.\nThe reason is: \"{$reason}\".", $IP);
690
}
691
- wordfence::status(2, 'info', "Blocking IP {$IP}. {$reason}");
692
}
693
else if ($action == 'throttle') { //Rate limited - throttle
694
$secsToGo = wfBlock::rateLimitThrottleDuration();
695
wfBlock::createRateThrottle($reason, $IP, $secsToGo);
696
wfActivityReport::logBlockedIP($IP, null, 'throttle');
697
698
- wordfence::status(2, 'info', "Throttling IP {$IP}. {$reason}");
699
wfConfig::inc('totalIPsThrottled');
700
}
701
$this->do503($secsToGo, $reason);
686
$this->tagRequestForBlock($reason);
687
688
if (wfConfig::get('alertOn_block')) {
689
+ $message = sprintf(__('Wordfence has blocked IP address %s.', 'wordfence'), $IP) . "\n";
690
+ $message .= sprintf(__('The reason is: "%s".', 'wordfence'), $reason);
691
+ if ($secsToGo > 0) {
692
+ $message .= "\n" . sprintf(__('The duration of the block is %s.', 'wordfence'), wfUtils::makeDuration($secsToGo, true));
693
+ }
694
+ wordfence::alert(sprintf(__('Blocking IP %s', 'wordfence'), $IP), $message, $IP);
695
}
696
+ wordfence::status(2, 'info', sprintf(__('Blocking IP %s. %s', 'wordfence'), $IP, $reason));
697
}
698
else if ($action == 'throttle') { //Rate limited - throttle
699
$secsToGo = wfBlock::rateLimitThrottleDuration();
700
wfBlock::createRateThrottle($reason, $IP, $secsToGo);
701
wfActivityReport::logBlockedIP($IP, null, 'throttle');
702
703
+ wordfence::status(2, 'info', sprintf(__('Throttling IP %s. %s', 'wordfence'), $IP, $reason));
704
wfConfig::inc('totalIPsThrottled');
705
}
706
$this->do503($secsToGo, $reason);
lib/wfScan.php CHANGED
@@ -54,6 +54,7 @@ class wfScan {
54
}
55
$scanController = new wfScanner($scanMode);
56
57
$isFork = ($_GET['isFork'] == '1' ? true : false);
58
59
if(! $isFork){
@@ -82,85 +83,88 @@ class wfScan {
82
@error_reporting(E_ALL);
83
wfUtils::iniSet('display_errors','On');
84
self::status(4, 'info', "Setting up scanRunning and starting scan");
85
- if($isFork){
86
- $scan = wfConfig::get_ser('wfsd_engine', false, false);
87
- if($scan){
88
- self::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
89
- wfConfig::set('wfsd_engine', '', wfConfig::DONT_AUTOLOAD);
90
- } else {
91
- self::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
92
- wfConfig::set('wfsd_engine', '', wfConfig::DONT_AUTOLOAD);
93
- wfConfig::set('lastScanCompleted', __('Scan can\'t continue - stored data not found after a fork.', 'wordfence'));
94
- wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_FORK_FAILED);
95
- wfUtils::clearScanLock();
96
- self::status(2, 'error', "Scan terminated with error: " . __('Scan can\'t continue - stored data not found after a fork.', 'wordfence'));
97
- self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
98
- exit();
99
- }
100
- } else {
101
- $delay = -1;
102
- $isScheduled = false;
103
- $originalScanStart = wfConfig::get('originalScheduledScanStart', 0);
104
- $lastScanStart = wfConfig::get('lastScheduledScanStart', 0);
105
- $minimumFrequency = ($scanController->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_MANUAL ? 1800 : 43200);
106
- if ($lastScanStart && (time() - $lastScanStart) < $minimumFrequency) {
107
- $isScheduled = true;
108
-
109
- if ($originalScanStart > 0) {
110
- $delay = max($lastScanStart - $originalScanStart, 0);
111
}
112
}
113
-
114
- wfIssues::statusPrep(); //Re-initializes all status counters
115
- $scanController->resetStages();
116
- $scanController->resetSummaryItems();
117
-
118
- if ($scanMode != wfScanner::SCAN_TYPE_QUICK) {
119
- wordfence::status(1, 'info', "Contacting Wordfence to initiate scan");
120
- $wp_version = wfUtils::getWPVersion();
121
- $apiKey = wfConfig::get('apiKey');
122
- $api = new wfAPI($apiKey, $wp_version);
123
- $response = $api->call('log_scan', array(), array('delay' => $delay, 'scheduled' => (int) $isScheduled, 'mode' => wfConfig::get('schedMode')/*, 'forcedefer' => 1*/));
124
-
125
- if ($scanController->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_AUTOMATIC && $isScheduled) {
126
- if (isset($response['defer'])) {
127
- $defer = (int) $response['defer'];
128
- wordfence::status(2, 'info', "Deferring scheduled scan by " . wfUtils::makeDuration($defer));
129
- wfConfig::set('lastScheduledScanStart', 0);
130
- wfConfig::set('lastScanCompleted', 'ok');
131
- wfConfig::set('lastScanFailureType', false);
132
- wfConfig::set_ser('wfStatusStartMsgs', array());
133
- $scanController->recordLastScanTime();
134
- $i = new wfIssues();
135
- wfScanEngine::refreshScanNotification($i);
136
- wfScanner::shared()->scheduleSingleScan(time() + $defer, $originalScanStart);
137
- wfUtils::clearScanLock();
138
- exit();
139
}
140
}
141
142
- $malwarePrefixesHash = (isset($response['malwarePrefixes']) ? $response['malwarePrefixes'] : '');
143
- $coreHashesHash = (isset($response['coreHashes']) ? $response['coreHashes'] : '');
144
145
- $scan = new wfScanEngine($malwarePrefixesHash, $coreHashesHash, $scanMode);
146
- $scan->deleteNewIssues();
147
- }
148
- else {
149
- wordfence::status(1, 'info', "Initiating quick scan");
150
- $scan = new wfScanEngine('', '', $scanMode);
151
}
152
- }
153
- try {
154
$scan->go();
155
}
156
- catch (wfScanEngineDurationLimitException $e) {
157
wfUtils::clearScanLock();
158
$peakMemory = self::logPeakMemory();
159
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
160
self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
161
exit();
162
}
163
- catch (wfScanEngineCoreVersionChangeException $e) {
164
wfUtils::clearScanLock();
165
$peakMemory = self::logPeakMemory();
166
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
@@ -175,7 +179,47 @@ class wfScan {
175
176
exit();
177
}
178
- catch (Exception $e){
179
wfUtils::clearScanLock();
180
self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
181
self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
54
}
55
$scanController = new wfScanner($scanMode);
56
57
+ wfConfig::remove('scanStartAttempt');
58
$isFork = ($_GET['isFork'] == '1' ? true : false);
59
60
if(! $isFork){
83
@error_reporting(E_ALL);
84
wfUtils::iniSet('display_errors','On');
85
self::status(4, 'info', "Setting up scanRunning and starting scan");
86
+ try {
87
+ if ($isFork) {
88
+ $scan = wfConfig::get_ser('wfsd_engine', false, false);
89
+ if ($scan) {
90
+ self::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
91
+ wfConfig::set('wfsd_engine', '', wfConfig::DONT_AUTOLOAD);
92
+ }
93
+ else {
94
+ self::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
95
+ wfConfig::set('wfsd_engine', '', wfConfig::DONT_AUTOLOAD);
96
+ wfConfig::set('lastScanCompleted', __('Scan can\'t continue - stored data not found after a fork.', 'wordfence'));
97
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_FORK_FAILED);
98
+ wfUtils::clearScanLock();
99
+ self::status(2, 'error', "Scan terminated with error: " . __('Scan can\'t continue - stored data not found after a fork.', 'wordfence'));
100
+ self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
101
+ exit();
102
}
103
}
104
+ else {
105
+ $delay = -1;
106
+ $isScheduled = false;
107
+ $originalScanStart = wfConfig::get('originalScheduledScanStart', 0);
108
+ $lastScanStart = wfConfig::get('lastScheduledScanStart', 0);
109
+ $minimumFrequency = ($scanController->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_MANUAL ? 1800 : 43200);
110
+ if ($lastScanStart && (time() - $lastScanStart) < $minimumFrequency) {
111
+ $isScheduled = true;
112
+
113
+ if ($originalScanStart > 0) {
114
+ $delay = max($lastScanStart - $originalScanStart, 0);
115
}
116
}
117
118
+ wfIssues::statusPrep(); //Re-initializes all status counters
119
+ $scanController->resetStages();
120
+ $scanController->resetSummaryItems();
121
122
+ if ($scanMode != wfScanner::SCAN_TYPE_QUICK) {
123
+ wordfence::status(1, 'info', "Contacting Wordfence to initiate scan");
124
+ $wp_version = wfUtils::getWPVersion();
125
+ $apiKey = wfConfig::get('apiKey');
126
+ $api = new wfAPI($apiKey, $wp_version);
127
+ $response = $api->call('log_scan', array(), array('delay' => $delay, 'scheduled' => (int) $isScheduled, 'mode' => wfConfig::get('schedMode')/*, 'forcedefer' => 1*/));
128
+
129
+ if ($scanController->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_AUTOMATIC && $isScheduled) {
130
+ if (isset($response['defer'])) {
131
+ $defer = (int) $response['defer'];
132
+ wordfence::status(2, 'info', "Deferring scheduled scan by " . wfUtils::makeDuration($defer));
133
+ wfConfig::set('lastScheduledScanStart', 0);
134
+ wfConfig::set('lastScanCompleted', 'ok');
135
+ wfConfig::set('lastScanFailureType', false);
136
+ wfConfig::set_ser('wfStatusStartMsgs', array());
137
+ $scanController->recordLastScanTime();
138
+ $i = new wfIssues();
139
+ wfScanEngine::refreshScanNotification($i);
140
+ wfScanner::shared()->scheduleSingleScan(time() + $defer, $originalScanStart);
141
+ wfUtils::clearScanLock();
142
+ exit();
143
+ }
144
+ }
145
+
146
+ $malwarePrefixesHash = (isset($response['malwarePrefixes']) ? $response['malwarePrefixes'] : '');
147
+ $coreHashesHash = (isset($response['coreHashes']) ? $response['coreHashes'] : '');
148
+
149
+ $scan = new wfScanEngine($malwarePrefixesHash, $coreHashesHash, $scanMode);
150
+ $scan->deleteNewIssues();
151
+ }
152
+ else {
153
+ wordfence::status(1, 'info', "Initiating quick scan");
154
+ $scan = new wfScanEngine('', '', $scanMode);
155
+ }
156
}
157
+
158
$scan->go();
159
}
160
+ catch (wfScanEngineDurationLimitException $e) { //User error set in wfScanEngine
161
wfUtils::clearScanLock();
162
$peakMemory = self::logPeakMemory();
163
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
164
self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
165
exit();
166
}
167
+ catch (wfScanEngineCoreVersionChangeException $e) { //User error set in wfScanEngine
168
wfUtils::clearScanLock();
169
$peakMemory = self::logPeakMemory();
170
self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
179
180
exit();
181
}
182
+ catch (wfAPICallSSLUnavailableException $e) {
183
+ wfConfig::set('lastScanCompleted', $e->getMessage());
184
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_API_SSL_UNAVAILABLE);
185
+
186
+ wfUtils::clearScanLock();
187
+ $peakMemory = self::logPeakMemory();
188
+ self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
189
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
190
+ exit();
191
+ }
192
+ catch (wfAPICallFailedException $e) {
193
+ wfConfig::set('lastScanCompleted', $e->getMessage());
194
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_API_CALL_FAILED);
195
+
196
+ wfUtils::clearScanLock();
197
+ $peakMemory = self::logPeakMemory();
198
+ self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
199
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
200
+ exit();
201
+ }
202
+ catch (wfAPICallInvalidResponseException $e) {
203
+ wfConfig::set('lastScanCompleted', $e->getMessage());
204
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_API_INVALID_RESPONSE);
205
+
206
+ wfUtils::clearScanLock();
207
+ $peakMemory = self::logPeakMemory();
208
+ self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
209
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
210
+ exit();
211
+ }
212
+ catch (wfAPICallErrorResponseException $e) {
213
+ wfConfig::set('lastScanCompleted', $e->getMessage());
214
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_API_ERROR_RESPONSE);
215
+
216
+ wfUtils::clearScanLock();
217
+ $peakMemory = self::logPeakMemory();
218
+ self::status(2, 'info', "Wordfence used " . wfUtils::formatBytes($peakMemory - self::$peakMemAtStart) . " of memory for scan. Server peak memory usage was: " . wfUtils::formatBytes($peakMemory));
219
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
220
+ exit();
221
+ }
222
+ catch (Exception $e) {
223
wfUtils::clearScanLock();
224
self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
225
self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
lib/wfScanEngine.php CHANGED
@@ -230,6 +230,19 @@ class wfScanEngine {
230
wfScanEngine::refreshScanNotification($this->i);
231
throw $e;
232
}
233
catch (Exception $e) {
234
if ($e->getCode() != wfScanEngine::SCAN_MANUALLY_KILLED) {
235
wfConfig::set('lastScanCompleted', $e->getMessage());
@@ -1950,43 +1963,82 @@ class wfScanEngine {
1950
}
1951
$timeout = self::getMaxExecutionTime() - 2; //2 seconds shorter than max execution time which ensures that only 2 HTTP processes are ever occupied
1952
$testURL = admin_url('admin-ajax.php?action=wordfence_testAjax');
1953
- if(! wfConfig::get('startScansRemotely', false)){
1954
- $testResult = wp_remote_post($testURL, array(
1955
- 'timeout' => $timeout,
1956
- 'blocking' => true,
1957
- 'sslverify' => false,
1958
- 'headers' => array()
1959
));
1960
wordfence::status(4, 'info', "Test result of scan start URL fetch: " . var_export($testResult, true));
1961
}
1962
$cronKey = wfUtils::bigRandomHex();
1963
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
1964
- if( (! wfConfig::get('startScansRemotely', false)) && (! is_wp_error($testResult)) && (is_array($testResult) || $testResult instanceof ArrayAccess) && strstr($testResult['body'], 'WFSCANTESTOK') !== false){
1965
//ajax requests can be sent by the server to itself
1966
$cronURL = 'admin-ajax.php?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
1967
$cronURL = admin_url($cronURL);
1968
$headers = array('Referer' => false/*, 'Cookie' => 'XDEBUG_SESSION=1'*/);
1969
wordfence::status(4, 'info', "Starting cron with normal ajax at URL $cronURL");
1970
- wp_remote_get( $cronURL, array(
1971
- 'timeout' => 0.01,
1972
- 'blocking' => false,
1973
- 'sslverify' => false,
1974
- 'headers' => $headers
1975
- ) );
1976
wordfence::status(4, 'info', "Scan process ended after forking.");
1977
- } else {
1978
$cronURL = admin_url('admin-ajax.php');
1979
$cronURL = preg_replace('/^(https?:\/\/)/i', '$1noc1.wordfence.com/scanp/', $cronURL);
1980
$cronURL .= '?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
1981
$headers = array();
1982
wordfence::status(4, 'info', "Starting cron via proxy at URL $cronURL");
1983
-
1984
- wp_remote_get( $cronURL, array(
1985
- 'timeout' => 0.01,
1986
- 'blocking' => false,
1987
- 'sslverify' => false,
1988
- 'headers' => $headers
1989
- ) );
1990
wordfence::status(4, 'info', "Scan process ended after forking.");
1991
}
1992
return false; //No error
@@ -2442,3 +2494,5 @@ class wfScanEngineDurationLimitException extends Exception {
2442
2443
class wfScanEngineCoreVersionChangeException extends Exception {
2444
}
230
wfScanEngine::refreshScanNotification($this->i);
231
throw $e;
232
}
233
+ catch (wfScanEngineTestCallbackFailedException $e) {
234
+ wfConfig::set('lastScanCompleted', $e->getMessage());
235
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
236
+ $this->scanController->recordLastScanTime();
237
+
238
+ $this->recordMetric('scan', 'duration', (time() - $this->startTime));
239
+ $this->recordMetric('scan', 'memory', wfConfig::get('wfPeakMemory', 0, false));
240
+ $this->recordMetric('scan', 'failure', $e->getMessage());
241
+ $this->submitMetrics();
242
+
243
+ wfScanEngine::refreshScanNotification($this->i);
244
+ throw $e;
245
+ }
246
catch (Exception $e) {
247
if ($e->getCode() != wfScanEngine::SCAN_MANUALLY_KILLED) {
248
wfConfig::set('lastScanCompleted', $e->getMessage());
1963
}
1964
$timeout = self::getMaxExecutionTime() - 2; //2 seconds shorter than max execution time which ensures that only 2 HTTP processes are ever occupied
1965
$testURL = admin_url('admin-ajax.php?action=wordfence_testAjax');
1966
+ if (!wfConfig::get('startScansRemotely', false)) {
1967
+ try {
1968
+ $testResult = wp_remote_post($testURL, array(
1969
+ 'timeout' => $timeout,
1970
+ 'blocking' => true,
1971
+ 'sslverify' => false,
1972
+ 'headers' => array()
1973
));
1974
+ }
1975
+ catch (Exception $e) {
1976
+ //Fall through to the remote start test below
1977
+ }
1978
+
1979
wordfence::status(4, 'info', "Test result of scan start URL fetch: " . var_export($testResult, true));
1980
}
1981
+
1982
$cronKey = wfUtils::bigRandomHex();
1983
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
1984
+ if ((!wfConfig::get('startScansRemotely', false)) && (!is_wp_error($testResult)) && (is_array($testResult) || $testResult instanceof ArrayAccess) && strstr($testResult['body'], 'WFSCANTESTOK') !== false) {
1985
//ajax requests can be sent by the server to itself
1986
$cronURL = 'admin-ajax.php?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
1987
$cronURL = admin_url($cronURL);
1988
$headers = array('Referer' => false/*, 'Cookie' => 'XDEBUG_SESSION=1'*/);
1989
wordfence::status(4, 'info', "Starting cron with normal ajax at URL $cronURL");
1990
+
1991
+ try {
1992
+ wfConfig::set('scanStartAttempt', time());
1993
+ $response = wp_remote_get($cronURL, array(
1994
+ 'timeout' => 0.01,
1995
+ 'blocking' => false,
1996
+ 'sslverify' => false,
1997
+ 'headers' => $headers
1998
+ ));
1999
+ }
2000
+ catch (Exception $e) {
2001
+ wfConfig::set('lastScanCompleted', $e->getMessage());
2002
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
2003
+ return false;
2004
+ }
2005
+
2006
+ if (is_wp_error($response)) {
2007
+ $error_message = $response->get_error_message();
2008
+ wfConfig::set('lastScanCompleted', "There was an " . ($error_message ? '' : 'unknown ') . "error starting the scan" . ($error_message ? ": $error_message" : '.'));
2009
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
2010
+ }
2011
+
2012
wordfence::status(4, 'info', "Scan process ended after forking.");
2013
+ }
2014
+ else {
2015
$cronURL = admin_url('admin-ajax.php');
2016
$cronURL = preg_replace('/^(https?:\/\/)/i', '$1noc1.wordfence.com/scanp/', $cronURL);
2017
$cronURL .= '?action=wordfence_doScan&isFork=' . ($isFork ? '1' : '0') . '&scanMode=' . $scanMode . '&cronKey=' . $cronKey;
2018
$headers = array();
2019
wordfence::status(4, 'info', "Starting cron via proxy at URL $cronURL");
2020
+
2021
+ try {
2022
+ wfConfig::set('scanStartAttempt', time());
2023
+ $response = wp_remote_get($cronURL, array(
2024
+ 'timeout' => 0.01,
2025
+ 'blocking' => false,
2026
+ 'sslverify' => false,
2027
+ 'headers' => $headers
2028
+ ));
2029
+ }
2030
+ catch (Exception $e) {
2031
+ wfConfig::set('lastScanCompleted', $e->getMessage());
2032
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
2033
+ return false;
2034
+ }
2035
+
2036
+ if (is_wp_error($response)) {
2037
+ $error_message = $response->get_error_message();
2038
+ wfConfig::set('lastScanCompleted', "There was an " . ($error_message ? '' : 'unknown ') . "error starting the scan" . ($error_message ? ": $error_message" : '.'));
2039
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
2040
+ }
2041
+
2042
wordfence::status(4, 'info', "Scan process ended after forking.");
2043
}
2044
return false; //No error
2494
2495
class wfScanEngineCoreVersionChangeException extends Exception {
2496
}
2497
+ class wfScanEngineTestCallbackFailedException extends Exception {
2498
+ }
lib/wfSupportController.php CHANGED
@@ -17,6 +17,10 @@ class wfSupportController {
17
18
const ITEM_WIDGET_LOCAL_ATTACKS = 'widget-local-attacks';
19
20
const ITEM_DASHBOARD = 'dashboard';
21
const ITEM_DASHBOARD_STATUS_FIREWALL = 'dashboard-status-firewall';
22
const ITEM_DASHBOARD_STATUS_SCAN = 'dashboard-status-scan';
@@ -125,6 +129,7 @@ class wfSupportController {
125
const ITEM_SCAN_OPTION_CUSTOM_MALWARE_SIGNATURES = 'scan-option-custom-malware-signatures';
126
const ITEM_SCAN_TIME_LIMIT = 'scan-time-limit';
127
const ITEM_SCAN_FAILS = 'scan-fails';
128
const ITEM_SCAN_BULK_DELETE_WARNING = 'scan-bulk-delete-warning';
129
const ITEM_SCAN_SCHEDULING = 'scan-scheduling';
130
const ITEM_SCAN_RESULT_PUBLIC_CONFIG = 'scan-result-public-config';
@@ -177,6 +182,10 @@ class wfSupportController {
177
178
case self::ITEM_WIDGET_LOCAL_ATTACKS:
179
180
case self::ITEM_DASHBOARD:
181
case self::ITEM_DASHBOARD_STATUS_FIREWALL:
182
case self::ITEM_DASHBOARD_STATUS_SCAN:
@@ -254,6 +263,7 @@ class wfSupportController {
254
case self::ITEM_SCAN_STATUS_REPUTATION:
255
case self::ITEM_SCAN_TIME_LIMIT:
256
case self::ITEM_SCAN_FAILS:
257
case self::ITEM_SCAN_BULK_DELETE_WARNING:
258
case self::ITEM_SCAN_SCHEDULING:
259
case self::ITEM_SCAN_OPTION_CHECK_SITE_BLACKLISTED:
17
18
const ITEM_WIDGET_LOCAL_ATTACKS = 'widget-local-attacks';
19
20
+ const ITEM_VERSION_WORDPRESS = 'version-wordpress';
21
+ const ITEM_VERSION_PHP = 'version-php';
22
+ const ITEM_VERSION_OPENSSL = 'version-ssl';
23
+
24
const ITEM_DASHBOARD = 'dashboard';
25
const ITEM_DASHBOARD_STATUS_FIREWALL = 'dashboard-status-firewall';
26
const ITEM_DASHBOARD_STATUS_SCAN = 'dashboard-status-scan';
129
const ITEM_SCAN_OPTION_CUSTOM_MALWARE_SIGNATURES = 'scan-option-custom-malware-signatures';
130
const ITEM_SCAN_TIME_LIMIT = 'scan-time-limit';
131
const ITEM_SCAN_FAILS = 'scan-fails';
132
+ const ITEM_SCAN_FAILED_START = 'scan-failed-start';
133
const ITEM_SCAN_BULK_DELETE_WARNING = 'scan-bulk-delete-warning';
134
const ITEM_SCAN_SCHEDULING = 'scan-scheduling';
135
const ITEM_SCAN_RESULT_PUBLIC_CONFIG = 'scan-result-public-config';
182
183
case self::ITEM_WIDGET_LOCAL_ATTACKS:
184
185
+ case self::ITEM_VERSION_WORDPRESS:
186
+ case self::ITEM_VERSION_PHP:
187
+ case self::ITEM_VERSION_OPENSSL:
188
+
189
case self::ITEM_DASHBOARD:
190
case self::ITEM_DASHBOARD_STATUS_FIREWALL:
191
case self::ITEM_DASHBOARD_STATUS_SCAN:
263
case self::ITEM_SCAN_STATUS_REPUTATION:
264
case self::ITEM_SCAN_TIME_LIMIT:
265
case self::ITEM_SCAN_FAILS:
266
+ case self::ITEM_SCAN_FAILED_START:
267
case self::ITEM_SCAN_BULK_DELETE_WARNING:
268
case self::ITEM_SCAN_SCHEDULING:
269
case self::ITEM_SCAN_OPTION_CHECK_SITE_BLACKLISTED:
lib/wfUtils.php CHANGED
@@ -2411,45 +2411,6 @@ class wfUtils {
2411
}
2412
return new DateTime($timestring);
2413
}
2414
-
2415
- /**
2416
- * Returns whether or not the OpenSSL version is before, after, or equal to the equivalent text version string.
2417
- *
2418
- * @param string $compareVersion
2419
- * @param int $openSSLVersion A version number in the format OpenSSL uses.
2420
- * @return bool|int Returns -1 if $compareVersion is earlier, 0 if equal, 1 if later, and false if not a valid version string.
2421
- */
2422
- public static function openssl_version_compare($compareVersion, $openSSLVersion = OPENSSL_VERSION_NUMBER) {
2423
- if (preg_match('/^(\d+)\.(\d+)\.(\d+)([a-z]?)/i', $compareVersion, $matches)) {
2424
- $primary = 0; $major = 0; $minor = 0; $fixLetterIndex = 0;
2425
- if (isset($matches[1])) { $primary = (int) $matches[1]; }
2426
- if (isset($matches[2])) { $major = (int) $matches[2]; }
2427
- if (isset($matches[3])) { $minor = (int) $matches[3]; }
2428
- if (isset($matches[4])) { $fixLetterIndex = strpos('abcdefghijklmnopqrstuvwxyz', strtolower($matches[1])) + 1; }
2429
-
2430
- $compareOpenSSLVersion = self::openssl_make_version($primary, $major, $minor, $fixLetterIndex, 0);
2431
- if ($compareOpenSSLVersion < $openSSLVersion) { return -1; }
2432
- else if ($compareOpenSSLVersion == $openSSLVersion) { return 0; }
2433
- return 1;
2434
- }
2435
-
2436
- return false;
2437
- }
2438
-
2439
- /**
2440
- * Builds a number that can be compared to OPENSSL_VERSION_NUMBER from the parameters given. This is a modified
2441
- * version of the macro in the OpenSSL source.
2442
- *
2443
- * @param int $primary The '1' in 1.0.2g.
2444
- * @param int $major The '0' in 1.0.2g.
2445
- * @param int $minor The '2' in 1.0.2g.
2446
- * @param int $fixLetterIndex The 'g' in 1.0.2g.
2447
- * @param int $patch
2448
- * @return int
2449
- */
2450
- public static function openssl_make_version($primary, $major, $minor, $fixLetterIndex = 0, $patch = 0) {
2451
- return ((($primary & 0xff) << 28) + (($major & 0xff) << 20) + (($minor & 0xff) << 12) + (($fixLetterIndex & 0xff) << 4) + $patch);
2452
- }
2453
}
2454
2455
// GeoIP lib uses these as well
2411
}
2412
return new DateTime($timestring);
2413
}
2414
}
2415
2416
// GeoIP lib uses these as well
lib/wfVersionCheckController.php ADDED
@@ -0,0 +1,291 @@
1
+ <?php
2
+
3
+ class wfVersionCheckController {
4
+ const VERSION_COMPATIBLE = 'compatible';
5
+ const VERSION_DEPRECATED = 'deprecated';
6
+ const VERSION_UNSUPPORTED = 'unsupported';
7
+
8
+ const PHP_DEPRECATING = '5.3.0'; //When greater than PHP_MINIMUM, will issue a discontinuing warning the first time we check it and find a version less than this (also applies to the other similar constant pairs)
9
+ const PHP_MINIMUM = '5.2.0'; //The currently supported minimum
10
+
11
+ const OPENSSL_DEPRECATING = '1.0.1';
12
+ const OPENSSL_MINIMUM = '1.0.1';
13
+
14
+ const WORDPRESS_DEPRECATING = '3.9.0';
15
+ const WORDPRESS_MINIMUM = '3.9.0';
16
+
17
+ public static function shared() {
18
+ static $_shared = false;
19
+ if ($_shared === false) {
20
+ $_shared = new wfVersionCheckController();
21
+ }
22
+ return $_shared;
23
+ }
24
+
25
+ /**
26
+ * Returns whether or not all version checks are successful. If any check returns a value other than VERSION_COMPATIBLE, this returns false.
27
+ *
28
+ * @return bool
29
+ */
30
+ public function checkVersions() {
31
+ return ($this->checkPHPVersion() == self::VERSION_COMPATIBLE) && ($this->checkOpenSSLVersion() == self::VERSION_COMPATIBLE) && ($this->checkWordPressVersion() == self::VERSION_COMPATIBLE);
32
+ }
33
+
34
+ /**
35
+ * Does the same thing as checkVersions but also triggers display of the corresponding warnings.
36
+ *
37
+ * @return bool
38
+ */
39
+ public function checkVersionsAndWarn() {
40
+ //PHP
41
+ $php = $this->checkPHPVersion();
42
+ if ($php == self::VERSION_DEPRECATED) {
43
+ $this->_alertEmail(
44
+ 'phpVersionCheckDeprecationEmail_' . self::PHP_DEPRECATING,
45
+ __('PHP version too old', 'wordfence'),
46
+ sprintf(__('Your site is using a PHP version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of PHP 7.x or 5.6 but will currently support PHP versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), phpversion(), self::PHP_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_PHP))
47
+ );
48
+
49
+ $this->_adminNotice(
50
+ 'phpVersionCheckDeprecationNotice_' . self::PHP_DEPRECATING,
51
+ 'phpVersionCheck',
52
+ sprintf(__('<strong>WARNING: </strong> Your site is using a PHP version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of PHP 7.x or 5.6 but will currently support PHP versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), phpversion(), self::PHP_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_PHP) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
53
+ );
54
+
55
+ return false;
56
+ }
57
+ else if ($php == self::VERSION_UNSUPPORTED) {
58
+ $this->_alertEmail(
59
+ 'phpVersionCheckUnsupportedEmail_' . self::PHP_MINIMUM,
60
+ __('PHP version too old', 'wordfence'),
61
+ sprintf(__('Your site is using a PHP version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of PHP 7.x or 5.6 but will currently support PHP versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), phpversion(), self::PHP_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_PHP))
62
+ );
63
+
64
+ $this->_adminNotice(
65
+ 'phpVersionCheckUnsupportedNotice_' . self::PHP_MINIMUM,
66
+ 'phpVersionCheck',
67
+ sprintf(__('<strong>WARNING: </strong> Your site is using a PHP version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of PHP 7.x or 5.6 but will currently support PHP versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), phpversion(), self::PHP_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_PHP) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
68
+ );
69
+
70
+ return false;
71
+ }
72
+ else {
73
+ wfAdminNoticeQueue::removeAdminNotice(false, 'phpVersionCheck');
74
+ }
75
+
76
+ //OpenSSL
77
+ $openssl = $this->checkOpenSSLVersion();
78
+ if ($openssl == self::VERSION_DEPRECATED) {
79
+ $this->_alertEmail(
80
+ 'opensslVersionCheckDeprecationEmail_' . self::OPENSSL_DEPRECATING,
81
+ __('OpenSSL version too old', 'wordfence'),
82
+ sprintf(__('Your site is using an OpenSSL version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of OpenSSL but will currently support OpenSSL versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), self::openssl_make_text_version(), self::OPENSSL_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_OPENSSL))
83
+ );
84
+
85
+ $this->_adminNotice(
86
+ 'opensslVersionCheckDeprecationNotice_' . self::OPENSSL_DEPRECATING,
87
+ 'opensslVersionCheck',
88
+ sprintf(__('<strong>WARNING: </strong> Your site is using an OpenSSL version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of OpenSSL but will currently support OpenSSL versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), self::openssl_make_text_version(), self::OPENSSL_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_OPENSSL) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
89
+ );
90
+
91
+ return false;
92
+ }
93
+ else if ($openssl == self::VERSION_UNSUPPORTED) {
94
+ $this->_alertEmail(
95
+ 'opensslVersionCheckUnsupportedEmail_' . self::PHP_MINIMUM,
96
+ __('OpenSSL version too old', 'wordfence'),
97
+ sprintf(__('Your site is using an OpenSSL version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of OpenSSL but will currently support OpenSSL versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), self::openssl_make_text_version(), self::OPENSSL_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_OPENSSL))
98
+ );
99
+
100
+ $this->_adminNotice(
101
+ 'opensslVersionCheckUnsupportedNotice_' . self::PHP_MINIMUM,
102
+ 'opensslVersionCheck',
103
+ sprintf(__('<strong>WARNING: </strong> Your site is using an OpenSSL version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of OpenSSL but will currently support OpenSSL versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), self::openssl_make_text_version(), self::OPENSSL_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_OPENSSL) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
104
+ );
105
+
106
+ return false;
107
+ }
108
+ else {
109
+ wfAdminNoticeQueue::removeAdminNotice(false, 'opensslVersionCheck');
110
+ }
111
+
112
+ //WordPress
113
+ $wordpress = $this->checkWordPressVersion();
114
+ if ($wordpress == self::VERSION_DEPRECATED) {
115
+ require(ABSPATH . 'wp-includes/version.php'); /** @var string $wp_version */
116
+
117
+ $this->_alertEmail(
118
+ 'wordpressVersionCheckDeprecationEmail_' . self::WORDPRESS_DEPRECATING,
119
+ __('WordPress version too old', 'wordfence'),
120
+ sprintf(__('Your site is using a WordPress version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of WordPress but will currently support WordPress versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), $wp_version, self::WORDPRESS_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_WORDPRESS))
121
+ );
122
+
123
+ $this->_adminNotice(
124
+ 'wordpressVersionCheckDeprecationNotice_' . self::WORDPRESS_DEPRECATING,
125
+ 'wordpressVersionCheck',
126
+ sprintf(__('<strong>WARNING: </strong> Your site is using a WordPress version (%s) that will no longer be supported by Wordfence in an upcoming release and needs to be updated. We recommend using the newest version of WordPress but will currently support WordPress versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), $wp_version, self::WORDPRESS_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_WORDPRESS) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
127
+ );
128
+
129
+ return false;
130
+ }
131
+ else if ($wordpress == self::VERSION_UNSUPPORTED) {
132
+ require(ABSPATH . 'wp-includes/version.php'); /** @var string $wp_version */
133
+
134
+ $this->_alertEmail(
135
+ 'wordpressVersionCheckUnsupportedEmail_' . self::WORDPRESS_MINIMUM,
136
+ __('WordPress version too old', 'wordfence'),
137
+ sprintf(__('Your site is using a WordPress version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of WordPress but will currently support WordPress versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), $wp_version, self::WORDPRESS_DEPRECATING) . ' ' . sprintf(__('Learn More: %s', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_WORDPRESS))
138
+ );
139
+
140
+ $this->_adminNotice(
141
+ 'wordpressVersionCheckUnsupportedNotice_' . self::WORDPRESS_MINIMUM,
142
+ 'wordpressVersionCheck',
143
+ sprintf(__('<strong>WARNING: </strong> Your site is using a WordPress version (%s) that is no longer supported by Wordfence and needs to be updated. We recommend using the newest version of WordPress but will currently support WordPress versions as old as %s. Version checks are run regularly, so if you have successfully updated, you can dismiss this notice or check that the update has taken effect later.', 'wordfence'), $wp_version, self::WORDPRESS_DEPRECATING) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_VERSION_WORDPRESS) . '" target="_blank" rel="noopener noreferrer">' . __('Learn More', 'wordfence') . '</a>'
144
+ );
145
+
146
+ return false;
147
+ }
148
+ else {
149
+ wfAdminNoticeQueue::removeAdminNotice(false, 'wordpressVersionCheck');
150
+ }
151
+
152
+ return true;
153
+ }
154
+
155
+ private function _alertEmail($checkKey, $title, $body) {
156
+ if (!wfConfig::get($checkKey)) {
157
+ wordfence::alert($title, $body, wfUtils::getIP());
158
+ wfConfig::set($checkKey, true);
159
+ }
160
+ }
161
+
162
+ private function _adminNotice($checkKey, $noticeKey, $message) {
163
+ if (!wfConfig::get($checkKey)) {
164
+ wfAdminNoticeQueue::addAdminNotice(wfAdminNotice::SEVERITY_CRITICAL, $message, $noticeKey);
165
+ wfConfig::set($checkKey, true);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Returns whether or not the PHP version meets our minimum requirement or is a version being deprecated.
171
+ *
172
+ * @return string One of the VERSION_ constants.
173
+ */
174
+ public function checkPHPVersion() {
175
+ if (version_compare(phpversion(), self::PHP_DEPRECATING, '>=')) {
176
+ return self::VERSION_COMPATIBLE;
177
+ }
178
+
179
+ if (self::PHP_DEPRECATING != self::PHP_MINIMUM && version_compare(phpversion(), self::PHP_MINIMUM, '>=')) {
180
+ return self::VERSION_DEPRECATED;
181
+ }
182
+
183
+ return self::VERSION_UNSUPPORTED;
184
+ }
185
+
186
+ /**
187
+ * Returns whether or not the OpenSSL version meets our minimum requirement or is a version being deprecated.
188
+ *
189
+ * @return string One of the VERSION_ constants.
190
+ */
191
+ public function checkOpenSSLVersion() {
192
+ if (self::openssl_version_compare(self::OPENSSL_DEPRECATING) <= 0) {
193
+ return self::VERSION_COMPATIBLE;
194
+ }
195
+
196
+ if (self::OPENSSL_DEPRECATING != self::OPENSSL_MINIMUM && self::openssl_version_compare(self::OPENSSL_MINIMUM) <= 0) {
197
+ return self::VERSION_DEPRECATED;
198
+ }
199
+
200
+ return self::VERSION_UNSUPPORTED;
201
+ }
202
+
203
+ /**
204
+ * Returns whether or not the WordPress version meets our minimum requirement or is a version being deprecated.
205
+ *
206
+ * @return string One of the VERSION_ constants.
207
+ */
208
+ public function checkWordPressVersion() {
209
+ require(ABSPATH . 'wp-includes/version.php'); /** @var string $wp_version */
210
+
211
+ if (version_compare($wp_version, self::WORDPRESS_DEPRECATING, '>=')) {
212
+ return self::VERSION_COMPATIBLE;
213
+ }
214
+
215
+ if (self::WORDPRESS_DEPRECATING != self::WORDPRESS_MINIMUM && version_compare($wp_version, self::WORDPRESS_MINIMUM, '>=')) {
216
+ return self::VERSION_DEPRECATED;
217
+ }
218
+
219
+ return self::VERSION_UNSUPPORTED;
220
+ }
221
+
222
+ /**
223
+ * Utility Functions
224
+ */
225
+
226
+ /**
227
+ * Returns whether or not the OpenSSL version is before, after, or equal to the equivalent text version string.
228
+ *
229
+ * @param string $compareVersion
230
+ * @param int $openSSLVersion A version number in the format OpenSSL uses.
231
+ * @return bool|int Returns -1 if $compareVersion is earlier, 0 if equal, 1 if later, and false if not a valid version string.
232
+ */
233
+ public static function openssl_version_compare($compareVersion, $openSSLVersion = OPENSSL_VERSION_NUMBER) {
234
+ if (preg_match('/^(\d+)\.(\d+)\.(\d+)([a-z]?)/i', $compareVersion, $matches)) {
235
+ $primary = 0; $major = 0; $minor = 0; $fixLetterIndex = 0;
236
+ if (isset($matches[1])) { $primary = (int) $matches[1]; }
237
+ if (isset($matches[2])) { $major = (int) $matches[2]; }
238
+ if (isset($matches[3])) { $minor = (int) $matches[3]; }
239
+ if (isset($matches[4])) { $fixLetterIndex = strpos('abcdefghijklmnopqrstuvwxyz', strtolower($matches[1])) + 1; }
240
+
241
+ $compareOpenSSLVersion = self::openssl_make_number_version($primary, $major, $minor, $fixLetterIndex, 0);
242
+ if ($compareOpenSSLVersion < $openSSLVersion) { return -1; }
243
+ else if ($compareOpenSSLVersion == $openSSLVersion) { return 0; }
244
+ return 1;
245
+ }
246
+
247
+ return false;
248
+ }
249
+
250
+ /**
251
+ * Builds a number that can be compared to OPENSSL_VERSION_NUMBER from the parameters given. This is a modified
252
+ * version of the macro in the OpenSSL source.
253
+ *
254
+ * @param int $primary The '1' in 1.0.2g.
255
+ * @param int $major The '0' in 1.0.2g.
256
+ * @param int $minor The '2' in 1.0.2g.
257
+ * @param int $fixLetterIndex The 'g' in 1.0.2g.
258
+ * @param int $patch
259
+ * @return int
260
+ */
261
+ public static function openssl_make_number_version($primary, $major, $minor, $fixLetterIndex = 0, $patch = 0) {
262
+ return ((($primary & 0xff) << 28) + (($major & 0xff) << 20) + (($minor & 0xff) << 12) + (($fixLetterIndex & 0xff) << 4) + $patch);
263
+ }
264
+
265
+ /**
266
+ * Builds a text version of the OpenSSL version from a number-formatted one.
267
+ *
268
+ * @param int $number
269
+ * @return string
270
+ */
271
+ public static function openssl_make_text_version($number = OPENSSL_VERSION_NUMBER) {
272
+ $primary = (($number >> 28) & 0xff);
273
+ $major = (($number >> 20) & 0xff);
274
+ $minor = (($number >> 12) & 0xff);
275
+ $fix = (($number >> 4) & 0xff);
276
+ $patch = ($number & 0xf); //Not currently handled -- would be values like alpha, beta, rc1
277
+
278
+ $alphabet = str_split('abcdefghijklmnopqrstuvwxyz');
279
+ $fixLetters = '';
280
+ while ($fix > 26) {
281
+ $fixLetters .= 'z';
282
+ $fix -= 26;
283
+ }
284
+ if (array_key_exists($fix - 1, $alphabet)) {
285
+ $fixLetters .= $alphabet[$fix - 1];
286
+ }
287
+
288
+ $version = "{$primary}.{$major}.{$minor}{$fixLetters}";
289
+ return $version;
290
+ }
291
+ }
lib/wordfenceClass.php CHANGED
@@ -36,6 +36,7 @@ require_once(dirname(__FILE__) . '/wfImportExportController.php');
36
require_once(dirname(__FILE__) . '/wfOnboardingController.php');
37
require_once(dirname(__FILE__) . '/wfSupportController.php');
38
require_once(dirname(__FILE__) . '/wfCredentialsController.php');
39
require_once(dirname(__FILE__) . '/wfDateLocalization.php');
40
require_once(dirname(__FILE__) . '/wfAdminNoticeQueue.php');
41
@@ -65,6 +66,11 @@ class wordfence {
65
public static $commentSpamItems = array();
66
public static function installPlugin(){
67
self::runInstall();
68
//Used by MU code below
69
update_option('wordfenceActivated', 1);
70
}
@@ -102,9 +108,9 @@ class wordfence {
102
}
103
104
if(wfConfig::get('deleteTablesOnDeact')){
105
$schema = new wfSchema();
106
$schema->dropAll();
107
- wfConfig::updateTableExists();
108
foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
109
if (is_multisite() && function_exists('delete_network_option')) {
110
delete_network_option(null, $opt);
@@ -171,6 +177,8 @@ class wordfence {
171
}
172
}
173
}
174
}
175
private static function keyAlert($msg){
176
self::alert($msg, $msg . " To ensure uninterrupted Premium Wordfence protection on your site,\nplease renew your API key by visiting http://www.wordfence.com/ Sign in, go to your dashboard,\nselect the key about to expire and click the button to renew that API key.", false);
@@ -187,6 +195,7 @@ class wordfence {
187
try {
188
$keyType = wfAPI::KEY_TYPE_FREE;
189
$keyData = $api->call('ping_api_key', array(), array('supportHash' => wfConfig::get('supportHash', '')));
190
if (isset($keyData['_isPaidKey'])) {
191
$keyType = wfConfig::get('keyType');
192
}
@@ -248,6 +257,7 @@ class wordfence {
248
}
249
catch(Exception $e){
250
wordfence::status(4, 'error', "Could not verify Wordfence API Key: " . $e->getMessage());
251
}
252
253
$wfdb = new wfDB();
@@ -403,6 +413,8 @@ class wordfence {
403
public static function _completeCoreUpdateNotification() {
404
//This approach is here because WP Core updates run in a different sequence than plugin/theme updates, so we have to defer the running of the notification update sequence by an extra page load
405
delete_site_transient('wordfence_updating_notifications');
406
}
407
public static function runInstall(){
408
if(self::$runInstallCalled){ return; }
@@ -428,6 +440,7 @@ class wordfence {
428
429
$schema = new wfSchema();
430
$schema->createAll(); //if not exists
431
432
/** @var wpdb $wpdb */
433
global $wpdb;
@@ -1014,6 +1027,11 @@ SQL
1014
1015
//Install new schedule. If schedule config is blank it will install the default 'auto' schedule.
1016
wfScanner::shared()->scheduleScans();
1017
1018
//Must be the final line
1019
}
@@ -1104,6 +1122,7 @@ SQL
1104
add_action('wordfence_daily_cron', 'wordfence::dailyCron');
1105
add_action('wordfence_daily_autoUpdate', 'wfConfig::autoUpdate');
1106
add_action('wordfence_hourly_cron', 'wordfence::hourlyCron');
1107
add_action('plugins_loaded', 'wordfence::veryFirstAction');
1108
add_action('init', 'wordfence::initAction');
1109
//add_action('admin_bar_menu', 'wordfence::admin_bar_menu', 99);
@@ -1485,7 +1504,11 @@ SQL
1485
wfBlock::createLockout($reason, $IP, wfBlock::lockoutDuration(), time(), time(), 1);
1486
self::getLog()->tagRequestForLockout($reason);
1487
if (wfConfig::get('alertOn_loginLockout')) {
1488
- 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);
1489
}
1490
}
1491
@@ -1936,7 +1959,7 @@ SQL
1936
if (isset($_POST['wordfence_authFactor']) && $_POST['wordfence_authFactor'] && $twoFactorRecord) { //User authenticated with name and password, 2FA code ready to check
1937
$userID = $userDat->ID;
1938
1939
- if (get_class($authUser) == 'WP_User' && $authUser->ID == $userID) {
1940
//Do nothing. This is the code path the old method of including the code in the password field will take -- since we already have a valid $authUser, skip the nonce verification portion
1941
}
1942
else if (isset($_POST['wordfence_twoFactorNonce'])) {
@@ -1976,7 +1999,7 @@ SQL
1976
//Everything's good, let the sign in continue
1977
}
1978
else {
1979
- if (get_class($authUser) == 'WP_User' && $authUser->ID == $userID) { //Using the old method of appending the code to the password
1980
if ($mode == 'authenticator') {
1981
remove_action('login_errors', 'limit_login_fixup_error_messages'); //We're forced to do this because limit-login-attempts does not have any allowances for legitimate error messages
1982
self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: Please sign in again and add a space, the letters <code>wf</code>, and the code from your authenticator app to the end of your password (e.g., <code>wf123456</code>).'));
@@ -2081,7 +2104,7 @@ SQL
2081
delete_user_meta($userDat->ID, '_wf_twoFactorNonceTime');
2082
$authUser = $userDat; //Log in as the user we saved in the wp_authenticate action
2083
}
2084
- else if (get_class($authUser) == 'WP_User') { //User authenticated with name and password, prompt for the 2FA code
2085
//Verify at least one administrator has 2FA enabled
2086
$requireAdminTwoFactor = $hasActivatedTwoFactorUser && wfConfig::get('loginSec_requireAdminTwoFactor');
2087
@@ -2346,7 +2369,7 @@ SQL
2346
require('wfLockedOut.php');
2347
}
2348
set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
2349
- } else if(get_class($authUser) == 'WP_User'){
2350
delete_transient($tKey); //reset counter on success
2351
}
2352
}
@@ -2404,7 +2427,7 @@ SQL
2404
}
2405
2406
try {
2407
- $response = wp_remote_post(WORDFENCE_HACKATTEMPT_URL . 'multipleHackAttempts/?k=' . rawurlencode(wfConfig::get('apiKey')) . '&t=brute', array(
2408
'timeout' => 1,
2409
'user-agent' => "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]'),
2410
'body' => 'IPs=' . rawurlencode(json_encode($toSend)),
@@ -2476,7 +2499,7 @@ SQL
2476
$toSend = array_values($toSend);
2477
2478
try {
2479
- $response = wp_remote_post(WORDFENCE_HACKATTEMPT_URL . 'multipleHackAttempts/?k=' . rawurlencode(wfConfig::get('apiKey')) . '&t=brute', array(
2480
'timeout' => 1,
2481
'user-agent' => "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]'),
2482
'body' => 'IPs=' . rawurlencode(json_encode($toSend)),
@@ -2532,7 +2555,7 @@ SQL
2532
}
2533
2534
try {
2535
- $result = wp_remote_get(WORDFENCE_HACKATTEMPT_URL . 'hackAttempt/?k=' . rawurlencode(wfConfig::get('apiKey')) .
2536
'&IP=' . rawurlencode(filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? wfUtils::inet_aton($IP) : wfUtils::inet_pton($IP)) .
2537
'&t=' . rawurlencode($hitType) .
2538
'&type=' . $endpointType,
@@ -3007,7 +3030,20 @@ SQL
3007
//This ensures that if the scan crashes for some reason, the schedule will hold.
3008
wfScanner::shared()->scheduleScans();
3009
3010
- wfScanEngine::startScan();
3011
}
3012
public static function ajax_saveCountryBlocking_callback(){
3013
if(! wfConfig::get('isPaid')){
@@ -3037,6 +3073,67 @@ SQL
3037
$content .= date(DATE_RFC822, $r['ctime'] + $timeOffset) . '::' . sprintf('%.4f', $r['ctime']) . ':' . $r['level'] . ':' . $r['type'] . '::' . wp_kses_data( (string) $r['msg']) . "\n";
3038
}
3039
$content .= "\n\n";
3040
3041
ob_start();
3042
phpinfo();
@@ -3782,6 +3879,7 @@ HTACCESS;
3782
wordfence::status(10, 'info', "SUM_KILLED:A request was received to stop the previous scan.");
3783
wfUtils::clearScanLock(); //Clear the lock now because there may not be a scan running to pick up the kill request and clear the lock
3784
wfScanEngine::requestKill();
3785
wfConfig::set('lastScanFailureType', false);
3786
return array(
3787
'ok' => 1,
@@ -3957,6 +4055,32 @@ HTACCESS;
3957
'buttonTitle' => __('Close', 'wordfence'),
3958
))->render();
3959
break;
3960
}
3961
3962
wfUtils::doNotCache();
@@ -3967,7 +4091,7 @@ HTACCESS;
3967
'currentScanID' => wfScanner::shared()->lastScanTime(),
3968
'signatureUpdateTime' => wfConfig::get('signatureUpdateTime'),
3969
'scanFailedHTML' => $scanFailedHTML,
3970
- 'scanStalled' => ($scanFailed == wfIssues::SCAN_FAILED_TIMEOUT ? 1 : 0),
3971
'scanRunning' => wfScanner::shared()->isRunning() ? 1 : 0,
3972
'scanStages' => $stages,
3973
'scanStats' => $stats,
36
require_once(dirname(__FILE__) . '/wfOnboardingController.php');
37
require_once(dirname(__FILE__) . '/wfSupportController.php');
38
require_once(dirname(__FILE__) . '/wfCredentialsController.php');
39
+ require_once(dirname(__FILE__) . '/wfVersionCheckController.php');
40
require_once(dirname(__FILE__) . '/wfDateLocalization.php');
41
require_once(dirname(__FILE__) . '/wfAdminNoticeQueue.php');
42
66
public static $commentSpamItems = array();
67
public static function installPlugin(){
68
self::runInstall();
69
+
70
+ if (get_current_user_id() > 0) {
71
+ wfConfig::set('activatingIP', wfUtils::getIP());
72
+ }
73
+
74
//Used by MU code below
75
update_option('wordfenceActivated', 1);
76
}
108
}
109
110
if(wfConfig::get('deleteTablesOnDeact')){
111
+ wfConfig::updateTableExists(false);
112
$schema = new wfSchema();
113
$schema->dropAll();
114
foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
115
if (is_multisite() && function_exists('delete_network_option')) {
116
delete_network_option(null, $opt);
177
}
178
}
179
}
180
+
181
+ wfVersionCheckController::shared()->checkVersionsAndWarn();
182
}
183
private static function keyAlert($msg){
184
self::alert($msg, $msg . " To ensure uninterrupted Premium Wordfence protection on your site,\nplease renew your API key by visiting http://www.wordfence.com/ Sign in, go to your dashboard,\nselect the key about to expire and click the button to renew that API key.", false);
195
try {
196
$keyType = wfAPI::KEY_TYPE_FREE;
197
$keyData = $api->call('ping_api_key', array(), array('supportHash' => wfConfig::get('supportHash', '')));
198
+ wfConfig::set('useNoc3Secure', isset($keyData['n3']) ? wfUtils::truthyToBoolean($keyData['n3']) : false);
199
if (isset($keyData['_isPaidKey'])) {
200
$keyType = wfConfig::get('keyType');
201
}
257
}
258
catch(Exception $e){
259
wordfence::status(4, 'error', "Could not verify Wordfence API Key: " . $e->getMessage());
260
+ wfConfig::set('useNoc3Secure', false);
261
}
262
263
$wfdb = new wfDB();
413
public static function _completeCoreUpdateNotification() {
414
//This approach is here because WP Core updates run in a different sequence than plugin/theme updates, so we have to defer the running of the notification update sequence by an extra page load
415
delete_site_transient('wordfence_updating_notifications');
416
+
417
+ wfVersionCheckController::shared()->checkVersionsAndWarn();
418
}
419
public static function runInstall(){
420
if(self::$runInstallCalled){ return; }
440
441
$schema = new wfSchema();
442
$schema->createAll(); //if not exists
443
+ wfConfig::updateTableExists(true);
444
445
/** @var wpdb $wpdb */
446
global $wpdb;
1027
1028
//Install new schedule. If schedule config is blank it will install the default 'auto' schedule.
1029
wfScanner::shared()->scheduleScans();
1030
+
1031
+ //Check our minimum versions and generate the necessary warnings
1032
+ if (!wp_next_scheduled('wordfence_version_check')) {
1033
+ wp_schedule_single_event(time(), 'wordfence_version_check');
1034
+ }
1035
1036
//Must be the final line
1037
}
1122
add_action('wordfence_daily_cron', 'wordfence::dailyCron');
1123
add_action('wordfence_daily_autoUpdate', 'wfConfig::autoUpdate');
1124
add_action('wordfence_hourly_cron', 'wordfence::hourlyCron');
1125
+ add_action('wordfence_version_check', array(wfVersionCheckController::shared(), 'checkVersionsAndWarn'));
1126
add_action('plugins_loaded', 'wordfence::veryFirstAction');
1127
add_action('init', 'wordfence::initAction');
1128
//add_action('admin_bar_menu', 'wordfence::admin_bar_menu', 99);
1504
wfBlock::createLockout($reason, $IP, wfBlock::lockoutDuration(), time(), time(), 1);
1505
self::getLog()->tagRequestForLockout($reason);
1506
if (wfConfig::get('alertOn_loginLockout')) {
1507
+ $message = sprintf(__('A user with IP addr %s has been locked out from signing in or using the password recovery form for the following reason: %s.', 'wordfence'), $IP, $reason);
1508
+ if (wfBlock::lockoutDuration() > 0) {
1509
+ $message .= "\n" . sprintf(__('The duration of the lockout is %s.', 'wordfence'), wfUtils::makeDuration(wfBlock::lockoutDuration(), true));
1510
+ }
1511
+ wordfence::alert(__('User locked out from signing in', 'wordfence'), $message, $IP);
1512
}
1513
}
1514
1959
if (isset($_POST['wordfence_authFactor']) && $_POST['wordfence_authFactor'] && $twoFactorRecord) { //User authenticated with name and password, 2FA code ready to check
1960
$userID = $userDat->ID;
1961
1962
+ if (is_object($authUser) && get_class($authUser) == 'WP_User' && $authUser->ID == $userID) {
1963
//Do nothing. This is the code path the old method of including the code in the password field will take -- since we already have a valid $authUser, skip the nonce verification portion
1964
}
1965
else if (isset($_POST['wordfence_twoFactorNonce'])) {
1999
//Everything's good, let the sign in continue
2000
}
2001
else {
2002
+ if (is_object($authUser) && get_class($authUser) == 'WP_User' && $authUser->ID == $userID) { //Using the old method of appending the code to the password
2003
if ($mode == 'authenticator') {
2004
remove_action('login_errors', 'limit_login_fixup_error_messages'); //We're forced to do this because limit-login-attempts does not have any allowances for legitimate error messages
2005
self::$authError = new WP_Error('twofactor_invalid', __('<strong>INVALID CODE</strong>: Please sign in again and add a space, the letters <code>wf</code>, and the code from your authenticator app to the end of your password (e.g., <code>wf123456</code>).'));
2104
delete_user_meta($userDat->ID, '_wf_twoFactorNonceTime');
2105
$authUser = $userDat; //Log in as the user we saved in the wp_authenticate action
2106
}
2107
+ else if (is_object($authUser) && get_class($authUser) == 'WP_User') { //User authenticated with name and password, prompt for the 2FA code
2108
//Verify at least one administrator has 2FA enabled
2109
$requireAdminTwoFactor = $hasActivatedTwoFactorUser && wfConfig::get('loginSec_requireAdminTwoFactor');
2110
2369
require('wfLockedOut.php');
2370
}
2371
set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
2372
+ } else if(is_object($authUser) && get_class($authUser) == 'WP_User'){
2373
delete_transient($tKey); //reset counter on success
2374
}
2375
}
2427
}
2428
2429
try {
2430
+ $response = wp_remote_post((wfConfig::get('useNoc3Secure') ? WORDFENCE_HACKATTEMPT_URL_SEC : WORDFENCE_HACKATTEMPT_URL) . 'multipleHackAttempts/?k=' . rawurlencode(wfConfig::get('apiKey')) . '&t=brute', array(
2431
'timeout' => 1,
2432
'user-agent' => "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]'),
2433
'body' => 'IPs=' . rawurlencode(json_encode($toSend)),
2499
$toSend = array_values($toSend);
2500
2501
try {
2502
+ $response = wp_remote_post((wfConfig::get('useNoc3Secure') ? WORDFENCE_HACKATTEMPT_URL_SEC : WORDFENCE_HACKATTEMPT_URL) . 'multipleHackAttempts/?k=' . rawurlencode(wfConfig::get('apiKey')) . '&t=brute', array(
2503
'timeout' => 1,
2504
'user-agent' => "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]'),
2505
'body' => 'IPs=' . rawurlencode(json_encode($toSend)),
2555
}
2556
2557
try {
2558
+ $result = wp_remote_get((wfConfig::get('useNoc3Secure') ? WORDFENCE_HACKATTEMPT_URL_SEC : WORDFENCE_HACKATTEMPT_URL) . 'hackAttempt/?k=' . rawurlencode(wfConfig::get('apiKey')) .
2559
'&IP=' . rawurlencode(filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? wfUtils::inet_aton($IP) : wfUtils::inet_pton($IP)) .
2560
'&t=' . rawurlencode($hitType) .
2561
'&type=' . $endpointType,
3030
//This ensures that if the scan crashes for some reason, the schedule will hold.
3031
wfScanner::shared()->scheduleScans();
3032
3033
+ try {
3034
+ wfScanEngine::startScan();
3035
+ }
3036
+ catch (wfScanEngineTestCallbackFailedException $e) {
3037
+ wfConfig::set('lastScanCompleted', $e->getMessage());
3038
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED);
3039
+ wfUtils::clearScanLock();
3040
+ }
3041
+ catch (Exception $e) {
3042
+ if ($e->getCode() != wfScanEngine::SCAN_MANUALLY_KILLED) {
3043
+ wfConfig::set('lastScanCompleted', $e->getMessage());
3044
+ wfConfig::set('lastScanFailureType', wfIssues::SCAN_FAILED_GENERAL);
3045
+ }
3046
+ }
3047
}
3048
public static function ajax_saveCountryBlocking_callback(){
3049
if(! wfConfig::get('isPaid')){
3073
$content .= date(DATE_RFC822, $r['ctime'] + $timeOffset) . '::' . sprintf('%.4f', $r['ctime']) . ':' . $r['level'] . ':' . $r['type'] . '::' . wp_kses_data( (string) $r['msg']) . "\n";
3074
}
3075
$content .= "\n\n";
3076
+ $content .= str_repeat('-', 80);
3077
+ $content .= "\n\n";
3078
+
3079
+ $content .= __('# Scan Issues', 'wordfence') . "\n\n";
3080
+ $issues = wfIssues::shared()->getIssues(0, 50, 0, 50);
3081
+ $issueCounts = array_merge(array('new' => 0, 'ignoreP' => 0, 'ignoreC' => 0), wfIssues::shared()->getIssueCounts());
3082
+ $issueTypes = wfIssues::validIssueTypes();
3083
+
3084
+ $content .= sprintf(__('## New Issues (%d total)', 'wordfence'), $issueCounts['new']) . "\n\n";
3085
+ if (isset($issues['new']) && count($issues['new'])) {
3086
+ foreach ($issues['new'] as $i) {
3087
+ if (!in_array($i['type'], $issueTypes)) {
3088
+ continue;
3089
+ }
3090
+
3091
+ $viewContent = '';
3092
+ try {
3093
+ $viewContent = wfView::create('scanner/issue-' . $i['type'], array('textOutput' => $i))->render();
3094
+ }
3095
+ catch (wfViewNotFoundException $e) {
3096
+ //Ignore -- should never happen since we validate the type
3097
+ }
3098
+
3099
+ if (!empty($viewContent)) {
3100
+ $content .= $viewContent . "\n\n";
3101
+ }
3102
+ }
3103
+ }
3104
+ else {
3105
+ $content .= __('No New Issues', 'wordfence') . "\n\n";
3106
+ }
3107
+
3108
+ $content .= str_repeat('-', 10);
3109
+ $content .= "\n\n";
3110
+
3111
+ $content .= sprintf(__('## Ignored Issues (%d total)', 'wordfence'), $issueCounts['ignoreP'] + $issueCounts['ignoreC']) . "\n\n";
3112
+ if (isset($issues['new']) && count($issues['new'])) {
3113
+ foreach ($issues['ignored'] as $i) {
3114
+ if (!in_array($i['type'], $issueTypes)) {
3115
+ continue;
3116
+ }
3117
+
3118
+ $viewContent = '';
3119
+ try {
3120
+ $viewContent = wfView::create('scanner/issue-' . $i['type'], array('textOutput' => $i))->render();
3121
+ }
3122
+ catch (wfViewNotFoundException $e) {
3123
+ //Ignore -- should never happen since we validate the type
3124
+ }
3125
+
3126
+ if (!empty($viewContent)) {
3127
+ $content .= $viewContent . "\n\n";
3128
+ }
3129
+ }
3130
+ }
3131
+ else {
3132
+ $content .= __('No Ignored Issues', 'wordfence') . "\n\n";
3133
+ }
3134
+
3135
+ $content .= str_repeat('-', 80);
3136
+ $content .= "\n\n";
3137
3138
ob_start();
3139
phpinfo();
3879
wordfence::status(10, 'info', "SUM_KILLED:A request was received to stop the previous scan.");
3880
wfUtils::clearScanLock(); //Clear the lock now because there may not be a scan running to pick up the kill request and clear the lock
3881
wfScanEngine::requestKill();
3882
+ wfConfig::remove('scanStartAttempt');
3883
wfConfig::set('lastScanFailureType', false);
3884
return array(
3885
'ok' => 1,
4055
'buttonTitle' => __('Close', 'wordfence'),
4056
))->render();
4057
break;
4058
+ case wfIssues::SCAN_FAILED_START_TIMEOUT:
4059
+ case wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED:
4060
+ $scanFailedHTML = wfView::create('scanner/scan-failed', array(
4061
+ 'messageHTML' => __('The scan has failed to start. This is often because the site either cannot make outbound requests or is blocked from connecting to itself.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILED_START) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '</a>',
4062
+ 'buttonTitle' => __('Close', 'wordfence'),
4063
+ ))->render();
4064
+ break;
4065
+ case wfIssues::SCAN_FAILED_API_SSL_UNAVAILABLE:
4066
+ $scanFailedHTML = wfView::create('scanner/scan-failed', array(
4067
+ 'messageHTML' => __('Scans are not functional because SSL is unavailable.', 'wordfence'),
4068
+ 'buttonTitle' => __('Close', 'wordfence'),
4069
+ ))->render();
4070
+ break;
4071
+ case wfIssues::SCAN_FAILED_API_CALL_FAILED:
4072
+ $scanFailedHTML = wfView::create('scanner/scan-failed', array(
4073
+ 'messageHTML' => __('The scan has failed because we were unable to contact the Wordfence servers. Some sites may need adjustments to run scans reliably.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '</a>',
4074
+ 'buttonTitle' => __('Close', 'wordfence'),
4075
+ ))->render();
4076
+ break;
4077
+ case wfIssues::SCAN_FAILED_API_INVALID_RESPONSE:
4078
+ case wfIssues::SCAN_FAILED_API_ERROR_RESPONSE:
4079
+ $scanFailedHTML = wfView::create('scanner/scan-failed', array(
4080
+ 'messageHTML' => __('The scan has failed because we received an unexpected response from the Wordfence servers. This may be a temporary error, though some sites may need adjustments to run scans reliably.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '</a>',
4081
+ 'buttonTitle' => __('Close', 'wordfence'),
4082
+ ))->render();
4083
+ break;
4084
}
4085
4086
wfUtils::doNotCache();
4091
'currentScanID' => wfScanner::shared()->lastScanTime(),
4092
'signatureUpdateTime' => wfConfig::get('signatureUpdateTime'),
4093
'scanFailedHTML' => $scanFailedHTML,
4094
+ 'scanStalled' => ($scanFailed == wfIssues::SCAN_FAILED_TIMEOUT || $scanFailed == wfIssues::SCAN_FAILED_START_TIMEOUT ? 1 : 0),
4095
'scanRunning' => wfScanner::shared()->isRunning() ? 1 : 0,
4096
'scanStages' => $stages,
4097
'scanStats' => $stats,
lib/wordfenceConstants.php CHANGED
@@ -7,6 +7,7 @@ define('WORDFENCE_API_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . 'v' . WORDFEN
7
define('WORDFENCE_BREACH_URL_BASE_SEC', WORDFENCE_API_URL_SEC . 'passwords/');
8
define('WORDFENCE_BREACH_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . 'passwords/');
9
define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com/');
10
define('WORDFENCE_MAX_SCAN_LOCK_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
11
define('WORDFENCE_DEFAULT_MAX_SCAN_TIME', 10800);
12
define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
@@ -21,5 +22,6 @@ if (!defined('WORDFENCE_SCAN_ISSUES_PER_PAGE')) { define('WORDFENCE_SCAN_ISSUES_
21
if (!defined('WORDFENCE_BLOCKED_IPS_PER_PAGE')) { define('WORDFENCE_BLOCKED_IPS_PER_PAGE', 100); }
22
if (!defined('WORDFENCE_DISABLE_FILE_VIEWER')) { define('WORDFENCE_DISABLE_FILE_VIEWER', false); }
23
if (!defined('WORDFENCE_SCAN_FAILURE_THRESHOLD')) { define('WORDFENCE_SCAN_FAILURE_THRESHOLD', 300); }
24
if (!defined('WORDFENCE_PREFER_WP_HOME_FOR_WPML')) { define('WORDFENCE_PREFER_WP_HOME_FOR_WPML', false); } //When determining the unfiltered `home` and `siteurl` with WPML installed, use WP_HOME and WP_SITEURL if set instead of the database values
25
if (!defined('WORDFENCE_SCAN_MIN_EXECUTION_TIME')) { define('WORDFENCE_SCAN_MIN_EXECUTION_TIME', 8); }
7
define('WORDFENCE_BREACH_URL_BASE_SEC', WORDFENCE_API_URL_SEC . 'passwords/');
8
define('WORDFENCE_BREACH_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . 'passwords/');
9
define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com/');
10
+ define('WORDFENCE_HACKATTEMPT_URL_SEC', 'https://noc3.wordfence.com/');
11
define('WORDFENCE_MAX_SCAN_LOCK_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
12
define('WORDFENCE_DEFAULT_MAX_SCAN_TIME', 10800);
13
define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
22
if (!defined('WORDFENCE_BLOCKED_IPS_PER_PAGE')) { define('WORDFENCE_BLOCKED_IPS_PER_PAGE', 100); }
23
if (!defined('WORDFENCE_DISABLE_FILE_VIEWER')) { define('WORDFENCE_DISABLE_FILE_VIEWER', false); }
24
if (!defined('WORDFENCE_SCAN_FAILURE_THRESHOLD')) { define('WORDFENCE_SCAN_FAILURE_THRESHOLD', 300); }
25
+ if (!defined('WORDFENCE_SCAN_START_FAILURE_THRESHOLD')) { define('WORDFENCE_SCAN_START_FAILURE_THRESHOLD', 15); }
26
if (!defined('WORDFENCE_PREFER_WP_HOME_FOR_WPML')) { define('WORDFENCE_PREFER_WP_HOME_FOR_WPML', false); } //When determining the unfiltered `home` and `siteurl` with WPML installed, use WP_HOME and WP_SITEURL if set instead of the database values
27
if (!defined('WORDFENCE_SCAN_MIN_EXECUTION_TIME')) { define('WORDFENCE_SCAN_MIN_EXECUTION_TIME', 8); }
readme.txt CHANGED
@@ -2,8 +2,9 @@
2
Contributors: mmaunder
3
Tags: security, firewall, malware scanner, web application firewall, antivirus, block hackers, country blocking, clean hacked site, blacklist, waf, login security
4
Requires at least: 3.9
5
Tested up to: 4.9.5
6
- Stable tag: 7.1.2
7
8
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
@@ -167,6 +168,20 @@ Secure your website with Wordfence.
167
168
== Changelog ==
169
170
= 7.1.2 =
171
* Improvement: Added support for filtering the blocks list.
172
* Improvement: Added a flow for generating the WAF autoprepend file and retrieving the path for manual installations.
2
Contributors: mmaunder
3
Tags: security, firewall, malware scanner, web application firewall, antivirus, block hackers, country blocking, clean hacked site, blacklist, waf, login security
4
Requires at least: 3.9
5
+ Requires PHP: 5.2
6
Tested up to: 4.9.5
7
+ Stable tag: 7.1.3
8
9
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
10
168
169
== Changelog ==
170
171
+ = 7.1.3 =
172
+ * Improvement: Improved the performance of our config table status check.
173
+ * Improvement: The IP address of the user activating Wordfence is now used by the breached password check until an admin successfully logs in.
174
+ * Improvement: Added several new error displays for scan failures to help diagnose and fix issues.
175
+ * Improvement: Added the block duration to alerts generated when an IP is blocked.
176
+ * Improvement: A text version of scan results is now included in the activity log email.
177
+ * Improvement: The WAF install/uninstall process no longer asks to backup files that do not exist.
178
+ * Change: Began a phased rollout of moving brute force queries to be https-only.
179
+ * Change: Added the initial deprecation notice for PHP 5.2.
180
+ * Change: Suppressed a script tag on the diagnostics page from being output in the email version.
181
+ * Fix: Addressed an issue where plugins that return a null user during authentication would cause a PHP notice to be logged.
182
+ * Fix: Fixed an issue where plugins that use non-standard version formatting could end up with a inaccurate vulnerability status.
183
+ * Fix: Added a workaround for web email clients that erroneously encode some URL characters (e.g., #).
184
+
185
= 7.1.2 =
186
* Improvement: Added support for filtering the blocks list.
187
* Improvement: Added a flow for generating the WAF autoprepend file and retrieving the path for manual installations.
views/blocking/block-list.php CHANGED
@@ -8,7 +8,7 @@ if (!defined('WORDFENCE_VERSION')) { exit; }
8
<div class="wf-row">
9
<div class="wf-col-xs-12">
10
<div class="wf-flex-horizontal wf-flex-full-width wf-add-bottom-small">
11
- <h3 class="wf-no-top wf-no-bottom"><?php printf(__('All blocks<span class="wf-hidden-xs"> for %s</span>', 'wordfence'), preg_replace('/^https?:\/\//i', '', wfUtils::wpSiteURL())); ?></h3>
12
<div class="wf-right">
13
<div class="wf-inline-block">
14
<ul class="wf-option wf-option-toggled-boolean-switch wf-option-no-spacing" data-option="displayAutomaticBlocks" data-enabled-value="1" data-disabled-value="0" data-original-value="<?php echo wfConfig::get('displayAutomaticBlocks') ? 1 : 0; ?>">
8
<div class="wf-row">
9
<div class="wf-col-xs-12">
10
<div class="wf-flex-horizontal wf-flex-full-width wf-add-bottom-small">
11
+ <h3 class="wf-no-top wf-no-bottom"><?php printf(__('Current blocks<span class="wf-hidden-xs"> for %s</span>', 'wordfence'), preg_replace('/^https?:\/\//i', '', wfUtils::wpSiteURL())); ?></h3>
12
<div class="wf-right">
13
<div class="wf-inline-block">
14
<ul class="wf-option wf-option-toggled-boolean-switch wf-option-no-spacing" data-option="displayAutomaticBlocks" data-enabled-value="1" data-disabled-value="0" data-original-value="<?php echo wfConfig::get('displayAutomaticBlocks') ? 1 : 0; ?>">
views/scanner/issue-base.php CHANGED
@@ -11,7 +11,11 @@ if (!defined('WORDFENCE_VERSION')) { exit; }
11
* @var array $summaryControls An array of summary controls for the issue type.
12
* @var array $detailPairs An array of label/value pairs for the issue's detail data. If the entry should only be conditionally shown, the value may be an array of the format array(conditional, displayValue).
13
* @var array $detailControls An array of detail controls for the issue type.
14
*/
15
?>
16
<script type="text/x-jquery-template" id="issueTmpl_<?php echo $internalType; ?>">
17
<ul class="wf-issue wf-issue-<?php echo $internalType; ?> {{if severity == '1'}}wf-issue-severity-critical{{else}}wf-issue-severity-warning{{/if}}" data-issue-id="${id}" data-issue-type="<?php echo $internalType; ?>" data-issue-severity="${severity}" data-high-sensitivity="{{if (data.highSense == '1')}}1{{else}}0{{/if}}" data-beta-signatures="{{if (data.betaSigs == '1')}}1{{else}}0{{/if}}">
@@ -61,3 +65,102 @@ if (!defined('WORDFENCE_VERSION')) { exit; }
61
</li>
62
</ul>
63
</script>
11
* @var array $summaryControls An array of summary controls for the issue type.
12
* @var array $detailPairs An array of label/value pairs for the issue's detail data. If the entry should only be conditionally shown, the value may be an array of the format array(conditional, displayValue).
13
* @var array $detailControls An array of detail controls for the issue type.
14
+ * @var array $textOutput If provided, used the content of the array to output plain text rather than the HTML template.
15
+ * @var array $textOutputDetailPairs An array of label/value pairs for the issue's detail data if outputting via text. If the entry should only be conditionally shown, the value may be an array of the format array(conditional, displayValue) where conditional is one or more keypaths that must all be truthy. It is preprocessed lightly for output: all values prefixed with $ will be treated as keypaths in the $textOutput array. If that is prefixed with ! for the conditional, its value will be inverted.
16
*/
17
+
18
+ if (!isset($textOutput) || !is_array($textOutput)):
19
?>
20
<script type="text/x-jquery-template" id="issueTmpl_<?php echo $internalType; ?>">
21
<ul class="wf-issue wf-issue-<?php echo $internalType; ?> {{if severity == '1'}}wf-issue-severity-critical{{else}}wf-issue-severity-warning{{/if}}" data-issue-id="${id}" data-issue-type="<?php echo $internalType; ?>" data-issue-severity="${severity}" data-high-sensitivity="{{if (data.highSense == '1')}}1{{else}}0{{/if}}" data-beta-signatures="{{if (data.betaSigs == '1')}}1{{else}}0{{/if}}">
65
</li>
66
</ul>
67
</script>
68
+ <?php else: ?>
69
+ <?php
70
+ echo '[' . $displayType . ($textOutput['status'] == 'ignoreP' || $textOutput['status'] == 'ignoreP' ? ', ' . __('Ignored', 'wordfence') : '') . ']' . "\n";
71
+ echo $textOutput['shortMsg'] . "\n";
72
+ echo sprintf(__('Issue Found: %s', 'wordfence'), $textOutput['displayTime']) . "\n";
73
+ echo sprintf(__('Severity: %s', 'wordfence'), $textOutput['severity'] == 1 ? __('Critical', 'wordfence') : __('Warning', 'wordfence')) . "\n";
74
+ foreach ($textOutputDetailPairs as $label => $value) {
75
+ if ($value === null) {
76
+ echo "\n";
77
+ continue;
78
+ }
79
+
80
+ unset($conditional);
81
+ if (is_array($value)) {
82
+ $conditional = $value[0];
83
+ if (!is_array($conditional)) {
84
+ $conditional = array($conditional);
85
+ }
86
+ $value = $value[1];
87
+ }
88
+
89
+ $allow = true;
90
+ if (isset($conditional)) {
91
+ foreach ($conditional as $test) {
92
+ if (!$allow) {
93
+ break;
94
+ }
95
+
96
+ if (preg_match('/^!?\$(\S+)/', $test, $matches)) {
97
+ $invert = (strpos($test, '!') === 0);
98
+ $components = explode('.', $matches[1]);
99
+ $tier = $textOutput;
100
+ foreach ($components as $index => $c) {
101
+ if (is_array($tier) && !isset($tier[$c])) {
102
+ if (!$invert) {
103
+ $allow = false;
104
+ }
105
+ break;
106
+ }
107
+
108
+ if ($index == count($components) - 1 && is_array($tier)) {
109
+ if ((!$tier[$c] && !$invert) || ($tier[$c] && $invert)) {
110
+ $allow = false;
111
+ }
112
+ break;
113
+ }
114
+ else if (!is_array($tier)) {
115
+ $allow = false;
116
+ break;
117
+ }
118
+
119
+ $tier = $tier[$c];
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ if (!$allow) {
126
+ continue;
127
+ }
128
+
129
+ if (preg_match_all('/(?<=^|\s)\$(\S+)(?=$|\s)/', $value, $matches, PREG_OFFSET_CAPTURE)) {
130
+ array_shift($matches);
131
+ $matches = $matches[0];
132
+ $matches = array_reverse($matches);
133
+ foreach ($matches as $m) {
134
+ $resolvedKeyPath = '';
135
+ $components = explode('.', $m[0]);
136
+ $tier = $textOutput;
137
+ foreach ($components as $index => $c) {
138
+ if (is_array($tier) && !isset($tier[$c])) {
139
+ $allow = false;
140
+ break 2;
141
+ }
142
+
143
+ if ($index == count($components) - 1 && is_array($tier)) {
144
+ $resolvedKeyPath = (string) $tier[$c];
145
+ break;
146
+ }
147
+ else if (!is_array($tier)) {
148
+ $allow = false;
149
+ break 2;
150
+ }
151
+
152
+ $tier = $tier[$c];
153
+ }
154
+
155
+ $value = substr($value, 0, $m[1] - 1) . strip_tags($resolvedKeyPath) . substr($value, $m[1] + strlen($m[0]));
156
+ }
157
+ }
158
+
159
+ if (!$allow) {
160
+ continue;
161
+ }
162
+
163
+ echo $label . ': ' . $value . "\n";
164
+ }
165
+ ?>
166
+ <?php endif; ?>
views/scanner/issue-checkGSB.php CHANGED
@@ -16,4 +16,10 @@ echo wfView::create('scanner/issue-base', array(
16
'detailControls' => array(
17
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed">' . __('Mark as Fixed', 'wordfence') . '</a>',
18
),
19
))->render();
16
'detailControls' => array(
17
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed">' . __('Mark as Fixed', 'wordfence') . '</a>',
18
),
19
+ 'textOutput' => (isset($textOutput) ? $textOutput : null),
20
+ 'textOutputDetailPairs' => array(
21
+ __('Bad URL', 'wordfence') => '$data.badURL',
22
+ null,
23
+ __('Details', 'wordfence') => '$longMsg',
24
+ ),
25
))->render();
views/scanner/issue-checkHowGetIPs.php CHANGED
@@ -15,4 +15,8 @@ echo wfView::create('scanner/issue-base', array(
15
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.useRecommendedHowGetIPs(\'${id}\'); return false;">' . __('Use Recommended Value', 'wordfence') . '</a>',
16
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed">' . __('Mark as Fixed', 'wordfence') . '</a>',
17
),
18
))->render();
15
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.useRecommendedHowGetIPs(\'${id}\'); return false;">' . __('Use Recommended Value', 'wordfence') . '</a>',
16
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed">' . __('Mark as Fixed', 'wordfence') . '</a>',
17
),
18
+ 'textOutput' => (isset($textOutput) ? $textOutput : null),
19
+ 'textOutputDetailPairs' => array(
20
+ __('Details', 'wordfence') => '$longMsg',
21
+ ),
22
))->render();
views/scanner/issue-checkSpamIP.php CHANGED
@@ -14,4 +14,8 @@ echo wfView::create('scanner/issue-base', array(
14
'detailControls' => array(
15
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed">' . __('Mark as Fixed', 'wordfence') . '</a>',
16
),