Wordfence Security – Firewall & Malware Scan - Version 6.3.6

Version Description

  • Improvement: Optimized the malware signature scan to reduce memory usage.
  • Improvement: Optimized the overall scan to make fewer network calls.
  • Improvement: Running an update now automatically dismisses the corresponding scan issue if present.
  • Improvement: Added a time limit to the live activity status so only current messages are shown.
  • Improvement: WAF configuration files are now excluded by default from the recently modified files list in the activity report.
  • Improvement: Background pausing for live activity and traffic may now be disabled.
  • Improvement: Added additional WAF support to allow us to more easily address false positives.
  • Improvement: Blocking pages presented by Wordfence now indicate the source and contain information to help diagnose caching problems.
  • Fix: All external URLs in the tour are now https.
  • Fix: Corrected a typo in the unlock email template.
  • Fix: Fixed the target of a label on the options page.
Download this release

Release Info

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

Code changes from version 6.3.5 to 6.3.6

js/admin.js CHANGED
@@ -511,7 +511,7 @@
511
  }, parseInt(WordfenceAdminVars.actUpdateInterval));
512
  },
513
  updateActivityLog: function() {
514
- if (this.activityLogUpdatePending || !this.windowHasFocus()) {
515
  if (!jQuery('body').hasClass('wordfenceLiveActivityPaused') && !this.activityLogUpdatePending) {
516
  jQuery('body').addClass('wordfenceLiveActivityPaused');
517
  }
@@ -700,7 +700,7 @@
700
  }
701
  },
702
  updateTicker: function(forceUpdate) {
703
- if ((!forceUpdate) && (this.tickerUpdatePending || !this.windowHasFocus())) {
704
  if (!jQuery('body').hasClass('wordfenceLiveActivityPaused') && !this.tickerUpdatePending) {
705
  jQuery('body').addClass('wordfenceLiveActivityPaused');
706
  }
511
  }, parseInt(WordfenceAdminVars.actUpdateInterval));
512
  },
513
  updateActivityLog: function() {
514
+ if (this.activityLogUpdatePending || (!this.windowHasFocus() && WordfenceAdminVars.allowsPausing == '1')) {
515
  if (!jQuery('body').hasClass('wordfenceLiveActivityPaused') && !this.activityLogUpdatePending) {
516
  jQuery('body').addClass('wordfenceLiveActivityPaused');
517
  }
700
  }
701
  },
702
  updateTicker: function(forceUpdate) {
703
+ if ((!forceUpdate) && (this.tickerUpdatePending || (!this.windowHasFocus() && WordfenceAdminVars.allowsPausing == '1'))) {
704
  if (!jQuery('body').hasClass('wordfenceLiveActivityPaused') && !this.tickerUpdatePending) {
705
  jQuery('body').addClass('wordfenceLiveActivityPaused');
706
  }
js/tourTip.js CHANGED
@@ -143,7 +143,7 @@ window['wordfenceTour'] = {
143
 
144
  jQuery(function(){
145
  if(WordfenceAdminVars.tourClosed != '1' && WordfenceAdminVars.welcomeClosed != '1'){
146
- var formHTML = '<div style="padding: 0 5px 0 15px;" id="wordfenceEmailDiv"><form target="_new" style="display: inline;" method="post" class="af-form-wrapper" action="http://www.aweber.com/scripts/addlead.pl" ><div style="display: none;"><input type="hidden" name="meta_web_form_id" value="1428034071" /><input type="hidden" name="meta_split_id" value="" /><input type="hidden" name="listname" value="wordfence" /><input type="hidden" name="redirect" value="http://www.aweber.com/thankyou-coi.htm?m=text" id="redirect_ae9f0882518768f447c80ea8f3b7afde" /><input type="hidden" name="meta_adtracking" value="widgetForm" /><input type="hidden" name="meta_message" value="1" /><input type="hidden" name="meta_required" value="email" /><input type="hidden" name="meta_tooltip" value="" /></div><input class="text" id="wfListEmail" type="text" name="email" value="Enter your email" tabindex="500" onclick="wordfenceTour.wfClearEmailField(); return false;" /><input name="submit" type="submit" value="Get Alerted" tabindex="501" onclick="var evt = event || window.event; try { return wordfenceTour.processEmailClick(evt); } catch(err){ evt.returnValue = false; evt.preventDefault(); }" /><div style="display: none;"><img src="http://forms.aweber.com/form/displays.htm?id=jCxMHAzMLAzsjA==" alt="" /></div><div style="padding: 5px; font-size: 10px;"><input type="checkbox" id="wfJoinListCheck" value="1" checked /><span style="font-size: 10px;">Also join our WordPress Security email list to receive WordPress Security Alerts and Wordfence news.</span></div></form></div>';
147
  var elem = '#toplevel_page_Wordfence';
148
  jQuery(elem).pointer({
149
  close: function(){},
143
 
144
  jQuery(function(){
145
  if(WordfenceAdminVars.tourClosed != '1' && WordfenceAdminVars.welcomeClosed != '1'){
146
+ var formHTML = '<div style="padding: 0 5px 0 15px;" id="wordfenceEmailDiv"><form target="_new" style="display: inline;" method="post" class="af-form-wrapper" action="https://www.aweber.com/scripts/addlead.pl" ><div style="display: none;"><input type="hidden" name="meta_web_form_id" value="1428034071" /><input type="hidden" name="meta_split_id" value="" /><input type="hidden" name="listname" value="wordfence" /><input type="hidden" name="redirect" value="https://www.aweber.com/thankyou-coi.htm?m=text" id="redirect_ae9f0882518768f447c80ea8f3b7afde" /><input type="hidden" name="meta_adtracking" value="widgetForm" /><input type="hidden" name="meta_message" value="1" /><input type="hidden" name="meta_required" value="email" /><input type="hidden" name="meta_tooltip" value="" /></div><input class="text" id="wfListEmail" type="text" name="email" value="Enter your email" tabindex="500" onclick="wordfenceTour.wfClearEmailField(); return false;" /><input name="submit" type="submit" value="Get Alerted" tabindex="501" onclick="var evt = event || window.event; try { return wordfenceTour.processEmailClick(evt); } catch(err){ evt.returnValue = false; evt.preventDefault(); }" /><div style="display: none;"><img src="https://forms.aweber.com/form/displays.htm?id=jCxMHAzMLAzsjA==" alt="" /></div><div style="padding: 5px; font-size: 10px;"><input type="checkbox" id="wfJoinListCheck" value="1" checked /><span style="font-size: 10px;">Also join our WordPress Security email list to receive WordPress Security Alerts and Wordfence news.</span></div></form></div>';
147
  var elem = '#toplevel_page_Wordfence';
148
  jQuery(elem).pointer({
149
  close: function(){},
lib/dashboard.php CHANGED
@@ -27,7 +27,6 @@
27
  <?php if(wfConfig::get('scheduledScansEnabled')){ ?>
28
  <?php if(wfConfig::get('scheduledScansEnabled')){ ?><tr><td style="padding-right: 20px;">Security Scans Enabled:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
29
  <?php if(wfConfig::get('scansEnabled_public')){ ?><tr><td style="padding-right: 20px;">Scan public facing site:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
30
- <?php if(wfConfig::get('scansEnabled_heartbleed')){ ?><tr><td style="padding-right: 20px;">Scan for HeartBleed Vulnerability:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
31
  <?php if(wfConfig::get('scansEnabled_core')){ ?><tr><td style="padding-right: 20px;">Scan Core Files:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
32
  <?php if(wfConfig::get('scansEnabled_themes')){ ?><tr><td style="padding-right: 20px;">Scan Themes:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
33
  <?php if(wfConfig::get('scansEnabled_plugins')){ ?><tr><td style="padding-right: 20px;">Scan Plugins:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
27
  <?php if(wfConfig::get('scheduledScansEnabled')){ ?>
28
  <?php if(wfConfig::get('scheduledScansEnabled')){ ?><tr><td style="padding-right: 20px;">Security Scans Enabled:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
29
  <?php if(wfConfig::get('scansEnabled_public')){ ?><tr><td style="padding-right: 20px;">Scan public facing site:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
 
30
  <?php if(wfConfig::get('scansEnabled_core')){ ?><tr><td style="padding-right: 20px;">Scan Core Files:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
31
  <?php if(wfConfig::get('scansEnabled_themes')){ ?><tr><td style="padding-right: 20px;">Scan Themes:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
32
  <?php if(wfConfig::get('scansEnabled_plugins')){ ?><tr><td style="padding-right: 20px;">Scan Plugins:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
lib/live_activity.php CHANGED
@@ -4,6 +4,8 @@
4
  <div class="wf-live-activity-title">Wordfence Live Activity:</div>
5
  <div class="wf-live-activity-message"></div>
6
  </div>
 
7
  <div class="wf-live-activity-state"><p>Live Updates Paused &mdash; Click inside window to resume</p></div>
 
8
  </div>
9
  </div>
4
  <div class="wf-live-activity-title">Wordfence Live Activity:</div>
5
  <div class="wf-live-activity-message"></div>
6
  </div>
7
+ <?php if (wfConfig::get('liveActivityPauseEnabled')): ?>
8
  <div class="wf-live-activity-state"><p>Live Updates Paused &mdash; Click inside window to resume</p></div>
9
+ <?php endif; ?>
10
  </div>
11
  </div>
lib/menu_activity.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php if (wfConfig::liveTrafficEnabled()): ?>
2
  <div id="wfLiveTrafficOverlayAnchor"></div>
3
  <div id="wfLiveTrafficDisabledMessage">
4
  <h2>Live Updates Paused<br /><small>Click inside window to resume</small></h2>
@@ -194,13 +194,13 @@
194
  <div id="wf-lt-listings" data-bind="foreach: listings">
195
  <div data-bind="attr: { id: ('wfActEvent_' + id()), 'class': cssClasses }">
196
  <div>
197
- <span data-bind="if: action() != 'loginOK' && user()">
198
  <span data-bind="html: user.avatar" class="wfAvatar"></span>
199
  <a data-bind="attr: { href: user.editLink }, text: user().display_name"
200
  target="_blank"></a>
201
  </span>
202
  <span data-bind="if: loc()">
203
- <span data-bind="if: action() != 'loginOK' && user()"> in</span>
204
  <img data-bind="attr: { src: '<?php echo wfUtils::getBaseURL() . 'images/flags/'; ?>' + loc().countryCode.toLowerCase() + '.png',
205
  alt: loc().countryName, title: loc().countryName }" width="16"
206
  height="11"
@@ -211,7 +211,7 @@
211
  </span>
212
  <span data-bind="if: !loc()">
213
  <span
214
- data-bind="text: action() != 'loginOK' && user() ? 'at an' : 'An'"></span> unknown location at IP <a
215
  data-bind="text: IP, attr: { href: WFAD.makeIPTrafLink(IP()) }"
216
  target="_blank"></a>
217
  </span>
1
+ <?php if (wfConfig::liveTrafficEnabled() && wfConfig::get('liveActivityPauseEnabled')): ?>
2
  <div id="wfLiveTrafficOverlayAnchor"></div>
3
  <div id="wfLiveTrafficDisabledMessage">
4
  <h2>Live Updates Paused<br /><small>Click inside window to resume</small></h2>
194
  <div id="wf-lt-listings" data-bind="foreach: listings">
195
  <div data-bind="attr: { id: ('wfActEvent_' + id()), 'class': cssClasses }">
196
  <div>
197
+ <span data-bind="if: action() != 'loginOK' && action() != 'loginFailValidUsername' && action() != 'loginFailInvalidUsername' && user()">
198
  <span data-bind="html: user.avatar" class="wfAvatar"></span>
199
  <a data-bind="attr: { href: user.editLink }, text: user().display_name"
200
  target="_blank"></a>
201
  </span>
202
  <span data-bind="if: loc()">
203
+ <span data-bind="if: action() != 'loginOK' && action() != 'loginFailValidUsername' && action() != 'loginFailInvalidUsername' && user()"> in</span>
204
  <img data-bind="attr: { src: '<?php echo wfUtils::getBaseURL() . 'images/flags/'; ?>' + loc().countryCode.toLowerCase() + '.png',
205
  alt: loc().countryName, title: loc().countryName }" width="16"
206
  height="11"
211
  </span>
212
  <span data-bind="if: !loc()">
213
  <span
214
+ data-bind="text: action() != 'loginOK' && action() != 'loginFailValidUsername' && action() != 'loginFailInvalidUsername' && user() ? 'at an' : 'An'"></span> unknown location at IP <a
215
  data-bind="text: IP, attr: { href: WFAD.makeIPTrafLink(IP()) }"
216
  target="_blank"></a>
217
  </span>
lib/menu_options.php CHANGED
@@ -400,10 +400,6 @@ $w = new wfConfig();
400
  'id' => 'scansEnabled_checkHowGetIPs',
401
  'label' => 'Scan for misconfigured How does Wordfence get IPs <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_misconfigured_How_does_Wordfence_get_IPs" target="_blank" class="wfhelp"></a>',
402
  ),
403
- array(
404
- 'id' => 'scansEnabled_heartbleed',
405
- 'label' => 'Scan for the HeartBleed vulnerability <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_the_HeartBleed_vulnerability" target="_blank" class="wfhelp"></a>',
406
- ),
407
  array(
408
  'id' => 'scansEnabled_checkReadableConfig',
409
  'label' => 'Scan for publicly accessible configuration, backup, or log files <a href="http://docs.wordfence.com/en/Wordfence_options#Configuration_Readable" target="_blank" class="wfhelp"></a>',
@@ -648,7 +644,7 @@ $w = new wfConfig();
648
  <div class="wfMarker" id="wfMarkerLoginSecurity"></div>
649
  <h3>Login Security Options <a href="http://docs.wordfence.com/en/Wordfence_options#Login_Security_Options" target="_blank" class="wfhelp"></a></h3>
650
  <div class="wf-form-group">
651
- <label for="blockedTime" class="wf-col-sm-5 wf-control-label">Enforce strong passwords <a href="http://docs.wordfence.com/en/Wordfence_options#Enforce_strong_passwords.3F" target="_blank" class="wfhelp"></a></label>
652
  <div class="wf-col-sm-7">
653
  <select class="wf-form-control" id="loginSec_strongPasswds" name="loginSec_strongPasswds">
654
  <option value="">Do not force users to use strong passwords</option>
@@ -939,6 +935,10 @@ $w = new wfConfig();
939
  </div>
940
  <?php
941
  $options = array( //Contents should already be HTML-escaped as needed
 
 
 
 
942
  array(
943
  'id' => 'deleteTablesOnDeact',
944
  'label' => 'Delete Wordfence tables and data on deactivation <a href="http://docs.wordfence.com/en/Wordfence_options#Delete_Wordfence_tables_and_data_on_deactivation.3F" target="_blank" class="wfhelp"></a>',
400
  'id' => 'scansEnabled_checkHowGetIPs',
401
  'label' => 'Scan for misconfigured How does Wordfence get IPs <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_misconfigured_How_does_Wordfence_get_IPs" target="_blank" class="wfhelp"></a>',
402
  ),
 
 
 
 
403
  array(
404
  'id' => 'scansEnabled_checkReadableConfig',
405
  'label' => 'Scan for publicly accessible configuration, backup, or log files <a href="http://docs.wordfence.com/en/Wordfence_options#Configuration_Readable" target="_blank" class="wfhelp"></a>',
644
  <div class="wfMarker" id="wfMarkerLoginSecurity"></div>
645
  <h3>Login Security Options <a href="http://docs.wordfence.com/en/Wordfence_options#Login_Security_Options" target="_blank" class="wfhelp"></a></h3>
646
  <div class="wf-form-group">
647
+ <label for="loginSec_strongPasswds" class="wf-col-sm-5 wf-control-label">Enforce strong passwords <a href="http://docs.wordfence.com/en/Wordfence_options#Enforce_strong_passwords.3F" target="_blank" class="wfhelp"></a></label>
648
  <div class="wf-col-sm-7">
649
  <select class="wf-form-control" id="loginSec_strongPasswds" name="loginSec_strongPasswds">
650
  <option value="">Do not force users to use strong passwords</option>
935
  </div>
936
  <?php
937
  $options = array( //Contents should already be HTML-escaped as needed
938
+ array(
939
+ 'id' => 'liveActivityPauseEnabled',
940
+ 'label' => 'Pause live updates when window loses focus <a href="http://docs.wordfence.com/en/Wordfence_options#Pause_live_updates_when_window_loses_focus" target="_blank" class="wfhelp"></a>',
941
+ ),
942
  array(
943
  'id' => 'deleteTablesOnDeact',
944
  'label' => 'Delete Wordfence tables and data on deactivation <a href="http://docs.wordfence.com/en/Wordfence_options#Delete_Wordfence_tables_and_data_on_deactivation.3F" target="_blank" class="wfhelp"></a>',
lib/menu_scan.php CHANGED
@@ -1,10 +1,12 @@
1
  <?php
2
  $sigUpdateTime = wfConfig::get('signatureUpdateTime');
3
  ?>
 
4
  <div id="wfLiveTrafficOverlayAnchor"></div>
5
  <div id="wfLiveTrafficDisabledMessage">
6
  <h2>Live Updates Paused<br /><small>Click inside window to resume</small></h2>
7
  </div>
 
8
  <div class="wrap wordfence">
9
  <div class="wf-container-fluid">
10
 
1
  <?php
2
  $sigUpdateTime = wfConfig::get('signatureUpdateTime');
3
  ?>
4
+ <?php if (wfConfig::get('liveActivityPauseEnabled')): ?>
5
  <div id="wfLiveTrafficOverlayAnchor"></div>
6
  <div id="wfLiveTrafficDisabledMessage">
7
  <h2>Live Updates Paused<br /><small>Click inside window to resume</small></h2>
8
  </div>
9
+ <?php endif; ?>
10
  <div class="wrap wordfence">
11
  <div class="wf-container-fluid">
12
 
lib/menu_scan_options.php CHANGED
@@ -15,10 +15,6 @@ $w = new wfConfig();
15
  'id' => 'scansEnabled_checkHowGetIPs',
16
  'label' => 'Scan for misconfigured How does Wordfence get IPs <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_misconfigured_How_does_Wordfence_get_IPs" target="_blank" class="wfhelp"></a>',
17
  ),
18
- array(
19
- 'id' => 'scansEnabled_heartbleed',
20
- 'label' => 'Scan for the HeartBleed vulnerability <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_the_HeartBleed_vulnerability" target="_blank" class="wfhelp"></a>',
21
- ),
22
  array(
23
  'id' => 'scansEnabled_checkReadableConfig',
24
  'label' => 'Scan for publicly accessible configuration, backup, or log files <a href="http://docs.wordfence.com/en/Wordfence_options#Configuration_Readable" target="_blank" class="wfhelp"></a>',
15
  'id' => 'scansEnabled_checkHowGetIPs',
16
  'label' => 'Scan for misconfigured How does Wordfence get IPs <a href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_misconfigured_How_does_Wordfence_get_IPs" target="_blank" class="wfhelp"></a>',
17
  ),
 
 
 
 
18
  array(
19
  'id' => 'scansEnabled_checkReadableConfig',
20
  'label' => 'Scan for publicly accessible configuration, backup, or log files <a href="http://docs.wordfence.com/en/Wordfence_options#Configuration_Readable" target="_blank" class="wfhelp"></a>',
lib/wf503.php CHANGED
@@ -20,5 +20,5 @@ still benefit from the other security features that Wordfence provides.
20
  <?php require('wfUnlockMsg.php'); ?>
21
 
22
  </p>
23
- <address>This response was generated by Wordfence.</address>
24
  </body></html>
20
  <?php require('wfUnlockMsg.php'); ?>
21
 
22
  </p>
23
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
24
  </body></html>
lib/wfAPI.php CHANGED
@@ -98,13 +98,27 @@ class wfAPI {
98
  $error_message = $response->get_error_message();
99
  throw new Exception("There was an " . ($error_message ? '' : 'unknown ') . "error connecting to the the Wordfence scanning servers" . ($error_message ? ": $error_message" : '.'));
100
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  if (!empty($response['response']['code'])) {
103
  $this->lastHTTPStatus = (int) $response['response']['code'];
104
  }
105
 
106
  if (200 != $this->lastHTTPStatus) {
107
- throw new Exception("We received an error response when trying to contact the Wordfence scanning servers. The HTTP status code was [$this->lastHTTPStatus]");
108
  }
109
 
110
  $this->curlContent = wp_remote_retrieve_body($response);
98
  $error_message = $response->get_error_message();
99
  throw new Exception("There was an " . ($error_message ? '' : 'unknown ') . "error connecting to the the Wordfence scanning servers" . ($error_message ? ": $error_message" : '.'));
100
  }
101
+
102
+ $dateHeader = @$response['headers']['date'];
103
+ if (!empty($dateHeader) && (time() - wfConfig::get('timeoffset_wf_updated', 0) > 3600)) {
104
+ if (function_exists('date_create_from_format')) {
105
+ $dt = DateTime::createFromFormat('D, j M Y G:i:s O', $dateHeader);
106
+ $timestamp = $dt->getTimestamp();
107
+ }
108
+ else {
109
+ $timestamp = strtotime($dateHeader);
110
+ }
111
+ $offset = $timestamp - time();
112
+ wfConfig::set('timeoffset_wf', $offset);
113
+ wfConfig::set('timeoffset_wf_updated', time());
114
+ }
115
 
116
  if (!empty($response['response']['code'])) {
117
  $this->lastHTTPStatus = (int) $response['response']['code'];
118
  }
119
 
120
  if (200 != $this->lastHTTPStatus) {
121
+ 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]");
122
  }
123
 
124
  $this->curlContent = wp_remote_retrieve_body($response);
lib/wfConfig.php CHANGED
@@ -35,7 +35,6 @@ class wfConfig {
35
  "scheduledScansEnabled" => array('value' => true, 'autoload' => self::AUTOLOAD),
36
  "lowResourceScansEnabled" => array('value' => false, 'autoload' => self::AUTOLOAD),
37
  "scansEnabled_public" => array('value' => false, 'autoload' => self::AUTOLOAD),
38
- "scansEnabled_heartbleed" => array('value' => true, 'autoload' => self::AUTOLOAD),
39
  "scansEnabled_checkHowGetIPs" => array('value' => true, 'autoload' => self::AUTOLOAD),
40
  "scansEnabled_core" => array('value' => true, 'autoload' => self::AUTOLOAD),
41
  "scansEnabled_themes" => array('value' => false, 'autoload' => self::AUTOLOAD),
@@ -57,6 +56,7 @@ class wfConfig {
57
  "scansEnabled_highSense" => array('value' => false, 'autoload' => self::AUTOLOAD),
58
  "scansEnabled_oldVersions" => array('value' => true, 'autoload' => self::AUTOLOAD),
59
  "scansEnabled_suspiciousAdminUsers" => array('value' => true, 'autoload' => self::AUTOLOAD),
 
60
  "firewallEnabled" => array('value' => true, 'autoload' => self::AUTOLOAD),
61
  "blockFakeBots" => array('value' => false, 'autoload' => self::AUTOLOAD),
62
  "autoBlockScanners" => array('value' => true, 'autoload' => self::AUTOLOAD),
@@ -122,7 +122,7 @@ class wfConfig {
122
  'maxScanHits_action' => "throttle",
123
  'blockedTime' => "300",
124
  'email_summary_interval' => 'weekly',
125
- 'email_summary_excluded_directories' => 'wp-content/cache,wp-content/plugins/wordfence/tmp',
126
  'allowed404s' => "/favicon.ico\n/apple-touch-icon*.png\n/*@2x.png\n/browserconfig.xml",
127
  'wafAlertWhitelist' => '',
128
  'wafAlertInterval' => 600,
@@ -130,7 +130,7 @@ class wfConfig {
130
  'howGetIPs_trusted_proxies' => '',
131
  )
132
  );
133
- public static $serializedOptions = array('lastAdminLogin', 'scanSched', 'emailedIssuesList', 'wf_summaryItems', 'adminUserList', 'twoFactorUsers', 'alertFreqTrack', 'wfStatusStartMsgs', 'vulnerabilities_plugin', 'vulnerabilities_theme', 'dashboardData');
134
  public static function setDefaults() {
135
  foreach (self::$defaultConfig['checkboxes'] as $key => $config) {
136
  $val = $config['value'];
35
  "scheduledScansEnabled" => array('value' => true, 'autoload' => self::AUTOLOAD),
36
  "lowResourceScansEnabled" => array('value' => false, 'autoload' => self::AUTOLOAD),
37
  "scansEnabled_public" => array('value' => false, 'autoload' => self::AUTOLOAD),
 
38
  "scansEnabled_checkHowGetIPs" => array('value' => true, 'autoload' => self::AUTOLOAD),
39
  "scansEnabled_core" => array('value' => true, 'autoload' => self::AUTOLOAD),
40
  "scansEnabled_themes" => array('value' => false, 'autoload' => self::AUTOLOAD),
56
  "scansEnabled_highSense" => array('value' => false, 'autoload' => self::AUTOLOAD),
57
  "scansEnabled_oldVersions" => array('value' => true, 'autoload' => self::AUTOLOAD),
58
  "scansEnabled_suspiciousAdminUsers" => array('value' => true, 'autoload' => self::AUTOLOAD),
59
+ "liveActivityPauseEnabled" => array('value' => true, 'autoload' => self::AUTOLOAD),
60
  "firewallEnabled" => array('value' => true, 'autoload' => self::AUTOLOAD),
61
  "blockFakeBots" => array('value' => false, 'autoload' => self::AUTOLOAD),
62
  "autoBlockScanners" => array('value' => true, 'autoload' => self::AUTOLOAD),
122
  'maxScanHits_action' => "throttle",
123
  'blockedTime' => "300",
124
  'email_summary_interval' => 'weekly',
125
+ 'email_summary_excluded_directories' => 'wp-content/cache,wp-content/wflogs',
126
  'allowed404s' => "/favicon.ico\n/apple-touch-icon*.png\n/*@2x.png\n/browserconfig.xml",
127
  'wafAlertWhitelist' => '',
128
  'wafAlertInterval' => 600,
130
  'howGetIPs_trusted_proxies' => '',
131
  )
132
  );
133
+ public static $serializedOptions = array('lastAdminLogin', 'scanSched', 'emailedIssuesList', 'wf_summaryItems', 'adminUserList', 'twoFactorUsers', 'alertFreqTrack', 'wfStatusStartMsgs', 'vulnerabilities_plugin', 'vulnerabilities_theme', 'dashboardData', 'malwarePrefixes');
134
  public static function setDefaults() {
135
  foreach (self::$defaultConfig['checkboxes'] as $key => $config) {
136
  $val = $config['value'];
lib/wfIssues.php CHANGED
@@ -6,33 +6,49 @@ class wfIssues {
6
  //Properties that are serialized on sleep:
7
  private $updateCalled = false;
8
  private $issuesTable = '';
 
9
  private $maxIssues = 0;
10
  private $newIssues = array();
11
  public $totalIssues = 0;
12
  public $totalCriticalIssues = 0;
13
  public $totalWarningIssues = 0;
14
  public function __sleep(){ //Same order here as vars above
15
- return array('updateCalled', 'issuesTable', 'maxIssues', 'newIssues', 'totalIssues', 'totalCriticalIssues', 'totalWarningIssues');
16
  }
17
  public function __construct(){
18
  global $wpdb;
19
  $this->issuesTable = $wpdb->base_prefix . 'wfIssues';
 
20
  $this->maxIssues = wfConfig::get('scan_maxIssues', 0);
21
  }
22
  public function __wakeup(){
23
  $this->db = new wfDB();
24
  }
25
- public function addIssue($type, $severity,
26
-
 
 
 
 
 
27
  $ignoreP, /* some piece of data used for md5 for permanent ignores */
28
  $ignoreC, /* some piece of data used for md5 for ignoring until something changes */
29
- $shortMsg, $longMsg, $templateData
30
  ){
31
-
32
-
33
- $ignoreP = md5($ignoreP);
34
- $ignoreC = md5($ignoreC);
35
- $rec = $this->getDB()->querySingleRec("select status, ignoreP, ignoreC from " . $this->issuesTable . " where (ignoreP='%s' OR ignoreC='%s')", $ignoreP, $ignoreC);
 
 
 
 
 
 
 
 
 
36
  if($rec){
37
  if($rec['status'] == 'new' && ($rec['ignoreC'] == $ignoreC || $rec['ignoreP'] == $ignoreP)){
38
  if($type != 'file' && $type != 'database'){ //Filter out duplicate new issues but not infected files because we want to see all infections even if file contents are identical
@@ -43,27 +59,30 @@ class wfIssues {
43
  if($rec['status'] == 'ignoreC' && $rec['ignoreC'] == $ignoreC){ return false; }
44
  if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return false; }
45
  }
46
-
47
- if($severity == 1){
48
- $this->totalCriticalIssues++;
49
- } else if($severity == 2){
50
- $this->totalWarningIssues++;
51
- }
52
- $this->totalIssues++;
53
- if (empty($this->maxIssues) || $this->totalIssues <= $this->maxIssues)
54
- {
55
- $this->newIssues[] = array(
56
- 'type' => $type,
57
- 'severity' => $severity,
58
- 'ignoreP' => $ignoreP,
59
- 'ignoreC' => $ignoreC,
60
- 'shortMsg' => $shortMsg,
61
- 'longMsg' => $longMsg,
62
- 'tmplData' => $templateData
63
- );
 
 
 
64
  }
65
 
66
- $this->getDB()->queryWrite("insert into " . $this->issuesTable . " (time, status, type, severity, ignoreP, ignoreC, shortMsg, longMsg, data) values (unix_timestamp(), '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')",
67
  'new',
68
  $type,
69
  $severity,
@@ -212,9 +231,76 @@ class wfIssues {
212
  }
213
  return $ret; //array of lists of issues by status
214
  }
 
 
 
 
 
 
 
 
 
215
  public function getIssueCount() {
216
  return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->issuesTable . " WHERE status = 'new'");
217
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  public function updateSummaryItem($key, $val){
219
  $arr = wfConfig::get_ser('wf_summaryItems', array());
220
  $arr[$key] = $val;
6
  //Properties that are serialized on sleep:
7
  private $updateCalled = false;
8
  private $issuesTable = '';
9
+ private $pendingIssuesTable = '';
10
  private $maxIssues = 0;
11
  private $newIssues = array();
12
  public $totalIssues = 0;
13
  public $totalCriticalIssues = 0;
14
  public $totalWarningIssues = 0;
15
  public function __sleep(){ //Same order here as vars above
16
+ return array('updateCalled', 'issuesTable', 'pendingIssuesTable', 'maxIssues', 'newIssues', 'totalIssues', 'totalCriticalIssues', 'totalWarningIssues');
17
  }
18
  public function __construct(){
19
  global $wpdb;
20
  $this->issuesTable = $wpdb->base_prefix . 'wfIssues';
21
+ $this->pendingIssuesTable = $wpdb->base_prefix . 'wfPendingIssues';
22
  $this->maxIssues = wfConfig::get('scan_maxIssues', 0);
23
  }
24
  public function __wakeup(){
25
  $this->db = new wfDB();
26
  }
27
+ public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
28
+ $this->_addIssue('issue', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed);
29
+ }
30
+ public function addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData) {
31
+ $this->_addIssue('pending', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
32
+ }
33
+ private function _addIssue($group, $type, $severity,
34
  $ignoreP, /* some piece of data used for md5 for permanent ignores */
35
  $ignoreC, /* some piece of data used for md5 for ignoring until something changes */
36
+ $shortMsg, $longMsg, $templateData, $alreadyHashed = false
37
  ){
38
+
39
+ if ($group == 'pending') {
40
+ $table = $this->pendingIssuesTable;
41
+ }
42
+ else {
43
+ $table = $this->issuesTable;
44
+ }
45
+
46
+ if (!$alreadyHashed) {
47
+ $ignoreP = md5($ignoreP);
48
+ $ignoreC = md5($ignoreC);
49
+ }
50
+
51
+ $rec = $this->getDB()->querySingleRec("select status, ignoreP, ignoreC from {$this->issuesTable} where (ignoreP = '%s' OR ignoreC = '%s')", $ignoreP, $ignoreC);
52
  if($rec){
53
  if($rec['status'] == 'new' && ($rec['ignoreC'] == $ignoreC || $rec['ignoreP'] == $ignoreP)){
54
  if($type != 'file' && $type != 'database'){ //Filter out duplicate new issues but not infected files because we want to see all infections even if file contents are identical
59
  if($rec['status'] == 'ignoreC' && $rec['ignoreC'] == $ignoreC){ return false; }
60
  if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return false; }
61
  }
62
+
63
+ if ($group != 'pending') {
64
+ if ($severity == 1) {
65
+ $this->totalCriticalIssues++;
66
+ }
67
+ else if ($severity == 2) {
68
+ $this->totalWarningIssues++;
69
+ }
70
+ $this->totalIssues++;
71
+ if (empty($this->maxIssues) || $this->totalIssues <= $this->maxIssues)
72
+ {
73
+ $this->newIssues[] = array(
74
+ 'type' => $type,
75
+ 'severity' => $severity,
76
+ 'ignoreP' => $ignoreP,
77
+ 'ignoreC' => $ignoreC,
78
+ 'shortMsg' => $shortMsg,
79
+ 'longMsg' => $longMsg,
80
+ 'tmplData' => $templateData
81
+ );
82
+ }
83
  }
84
 
85
+ $this->getDB()->queryWrite("insert into {$table} (time, status, type, severity, ignoreP, ignoreC, shortMsg, longMsg, data) values (unix_timestamp(), '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')",
86
  'new',
87
  $type,
88
  $severity,
231
  }
232
  return $ret; //array of lists of issues by status
233
  }
234
+ public function getPendingIssues($offset = 0, $limit = 100){
235
+ /** @var wpdb $wpdb */
236
+ global $wpdb;
237
+ $issues = $this->getDB()->querySelect("SELECT * FROM {$this->pendingIssuesTable} ORDER BY id ASC LIMIT %d,%d", $offset, $limit);
238
+ foreach($issues as &$i){
239
+ $i['data'] = unserialize($i['data']);
240
+ }
241
+ return $issues;
242
+ }
243
  public function getIssueCount() {
244
  return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->issuesTable . " WHERE status = 'new'");
245
  }
246
+ public function getPendingIssueCount() {
247
+ return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->pendingIssuesTable . " WHERE status = 'new'");
248
+ }
249
+ public function reconcileUpgradeIssues($report = null, $useCachedValued = false) {
250
+ if ($report === null) {
251
+ $report = new wfActivityReport();
252
+ }
253
+
254
+ $updatesNeeded = $report->getUpdatesNeeded($useCachedValued);
255
+ if ($updatesNeeded) {
256
+ if (!$updatesNeeded['core']) {
257
+ $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfUpgrade'");
258
+ }
259
+
260
+ if ($updatesNeeded['plugins']) {
261
+ $upgradeNames = array();
262
+ foreach ($updatesNeeded['plugins'] as $p) {
263
+ $name = $p['Name'];
264
+ $upgradeNames[$name] = 1;
265
+ }
266
+ $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfPluginUpgrade'");
267
+ foreach ($upgradeIssues as $issue) {
268
+ $data = unserialize($issue['data']);
269
+ $name = $data['Name'];
270
+ if (!isset($upgradeNames[$name])) { //Some plugins don't have a slug associated with them, so we anchor on the name
271
+ $this->deleteIssue($issue['id']);
272
+ }
273
+ }
274
+ }
275
+ else {
276
+ $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfPluginUpgrade'");
277
+ }
278
+
279
+ if ($updatesNeeded['themes']) {
280
+ $upgradeNames = array();
281
+ foreach ($updatesNeeded['themes'] as $t) {
282
+ $name = $t['Name'];
283
+ $upgradeNames[$name] = 1;
284
+ }
285
+ $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfThemeUpgrade'");
286
+ foreach ($upgradeIssues as $issue) {
287
+ $data = unserialize($issue['data']);
288
+ $name = $data['Name'];
289
+ if (!isset($upgradeNames[$name])) { //Some themes don't have a slug associated with them, so we anchor on the name
290
+ $this->deleteIssue($issue['id']);
291
+ }
292
+ }
293
+ }
294
+ else {
295
+ $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfThemeUpgrade'");
296
+ }
297
+ }
298
+ else {
299
+ $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
300
+ }
301
+
302
+ wfScanEngine::refreshScanNotification($this);
303
+ }
304
  public function updateSummaryItem($key, $val){
305
  $arr = wfConfig::get_ser('wf_summaryItems', array());
306
  $arr[$key] = $val;
lib/wfScanEngine.php CHANGED
@@ -122,7 +122,6 @@ class wfScanEngine {
122
  $this->jobList[] = 'checkSpamvertized';
123
  $this->jobList[] = 'checkSpamIP';
124
  $this->jobList[] = 'checkGSB';
125
- $this->jobList[] = 'heartbleed';
126
  $this->jobList[] = 'checkHowGetIPs_init';
127
  $this->jobList[] = 'checkHowGetIPs_main';
128
  $this->jobList[] = 'knownFiles_init';
@@ -278,24 +277,6 @@ class wfScanEngine {
278
  public function getCurrentJob(){
279
  return $this->jobList[0];
280
  }
281
- private function scan_heartbleed(){
282
- if(wfConfig::get('scansEnabled_heartbleed')){
283
- $this->statusIDX['heartbleed'] = wordfence::statusStart("Scanning your site for the HeartBleed vulnerability");
284
- $result = $this->api->call('scan_heartbleed', array(), array(
285
- 'siteURL' => site_url()
286
- ));
287
- $haveIssues = false;
288
- if($result['haveIssues'] && is_array($result['issues']) ){
289
- foreach($result['issues'] as $issue){
290
- $this->addIssue($issue['type'], $issue['level'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data']);
291
- $haveIssues = true;
292
- }
293
- }
294
- wordfence::statusEnd($this->statusIDX['heartbleed'], $haveIssues);
295
- } else {
296
- wordfence::statusDisabled("Skipping HeartBleed scan");
297
- }
298
- }
299
  private function scan_publicSite(){
300
  if(wfConfig::get('isPaid')){
301
  if(wfConfig::get('scansEnabled_public')){
@@ -594,7 +575,7 @@ class wfScanEngine {
594
  }
595
  private function scan_knownFiles_init(){
596
  $this->status(1, 'info', "Contacting Wordfence to initiate scan");
597
- $this->api->call('log_scan', array(), array());
598
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
599
  $baseContents = scandir(ABSPATH);
600
  if(! is_array($baseContents)){
@@ -627,7 +608,8 @@ class wfScanEngine {
627
  $this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
628
  $this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
629
 
630
- $this->hasher = new wordfenceHash(strlen(ABSPATH), ABSPATH, $includeInKnownFilesScan, $knownFilesThemes, $knownFilesPlugins, $this);
 
631
  }
632
  private function scan_knownFiles_main(){
633
  $this->hasher->run($this); //Include this so we can call addIssue and ->api->
@@ -1337,8 +1319,17 @@ class wfScanEngine {
1337
  public function status($level, $type, $msg){
1338
  wordfence::status($level, $type, $msg);
1339
  }
1340
- public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
1341
- return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
 
 
 
 
 
 
 
 
 
1342
  }
1343
  public static function requestKill(){
1344
  wfConfig::set('wfKillRequested', time(), wfConfig::DONT_AUTOLOAD);
122
  $this->jobList[] = 'checkSpamvertized';
123
  $this->jobList[] = 'checkSpamIP';
124
  $this->jobList[] = 'checkGSB';
 
125
  $this->jobList[] = 'checkHowGetIPs_init';
126
  $this->jobList[] = 'checkHowGetIPs_main';
127
  $this->jobList[] = 'knownFiles_init';
277
  public function getCurrentJob(){
278
  return $this->jobList[0];
279
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  private function scan_publicSite(){
281
  if(wfConfig::get('isPaid')){
282
  if(wfConfig::get('scansEnabled_public')){
575
  }
576
  private function scan_knownFiles_init(){
577
  $this->status(1, 'info', "Contacting Wordfence to initiate scan");
578
+ $response = $this->api->call('log_scan', array(), array());
579
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
580
  $baseContents = scandir(ABSPATH);
581
  if(! is_array($baseContents)){
608
  $this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
609
  $this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
610
 
611
+ $malwarePrefixesHash = (isset($response['malwarePrefixes']) ? wfUtils::hex2bin($response['malwarePrefixes']) : '');
612
+ $this->hasher = new wordfenceHash(strlen(ABSPATH), ABSPATH, $includeInKnownFilesScan, $knownFilesThemes, $knownFilesPlugins, $this, $malwarePrefixesHash);
613
  }
614
  private function scan_knownFiles_main(){
615
  $this->hasher->run($this); //Include this so we can call addIssue and ->api->
1319
  public function status($level, $type, $msg){
1320
  wordfence::status($level, $type, $msg);
1321
  }
1322
+ public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
1323
+ return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed);
1324
+ }
1325
+ public function addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
1326
+ return $this->i->addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
1327
+ }
1328
+ public function getPendingIssueCount() {
1329
+ return $this->i->getPendingIssueCount();
1330
+ }
1331
+ public function getPendingIssues($offset = 0, $limit = 100) {
1332
+ return $this->i->getPendingIssues($offset, $limit);
1333
  }
1334
  public static function requestKill(){
1335
  wfConfig::set('wfKillRequested', time(), wfConfig::DONT_AUTOLOAD);
lib/wfSchema.php CHANGED
@@ -58,17 +58,31 @@ class wfSchema {
58
  KEY k2(IP, ctime)
59
  ) default charset=latin1",
60
  "wfIssues" => "(
61
- id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
62
- time int UNSIGNED NOT NULL,
63
- status varchar(10) NOT NULL,
64
- type varchar(20) NOT NULL,
65
- severity tinyint UNSIGNED NOT NULL,
66
- ignoreP char(32) NOT NULL,
67
- ignoreC char(32) NOT NULL,
68
- shortMsg varchar(255) NOT NULL,
69
- longMsg text,
70
- data text
71
- ) default charset=utf8",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  "wfLeechers" => "(
73
  eMin int UNSIGNED NOT NULL,
74
  IP int UNSIGNED NOT NULL,
58
  KEY k2(IP, ctime)
59
  ) default charset=latin1",
60
  "wfIssues" => "(
61
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
62
+ `time` int(10) unsigned NOT NULL,
63
+ `status` varchar(10) NOT NULL,
64
+ `type` varchar(20) NOT NULL,
65
+ `severity` tinyint(3) unsigned NOT NULL,
66
+ `ignoreP` char(32) NOT NULL,
67
+ `ignoreC` char(32) NOT NULL,
68
+ `shortMsg` varchar(255) NOT NULL,
69
+ `longMsg` text,
70
+ `data` text,
71
+ PRIMARY KEY (`id`)
72
+ ) DEFAULT CHARSET=utf8",
73
+ "wfPendingIssues" => "(
74
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
75
+ `time` int(10) unsigned NOT NULL,
76
+ `status` varchar(10) NOT NULL,
77
+ `type` varchar(20) NOT NULL,
78
+ `severity` tinyint(3) unsigned NOT NULL,
79
+ `ignoreP` char(32) NOT NULL,
80
+ `ignoreC` char(32) NOT NULL,
81
+ `shortMsg` varchar(255) NOT NULL,
82
+ `longMsg` text,
83
+ `data` text,
84
+ PRIMARY KEY (`id`)
85
+ ) DEFAULT CHARSET=utf8",
86
  "wfLeechers" => "(
87
  eMin int UNSIGNED NOT NULL,
88
  IP int UNSIGNED NOT NULL,
lib/wfUtils.php CHANGED
@@ -1775,6 +1775,12 @@ class wfUtils {
1775
  return 'unknown';
1776
  }
1777
 
 
 
 
 
 
 
1778
  /**
1779
  * Identical to the same functions in wfWAFUtils.
1780
  *
@@ -1930,6 +1936,21 @@ class wfUtils {
1930
  $args = func_get_args();
1931
  return self::callMBSafeStrFunction('strrpos', $args);
1932
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1933
  }
1934
 
1935
  // GeoIP lib uses these as well
1775
  return 'unknown';
1776
  }
1777
 
1778
+ public static function hex2bin($string) { //Polyfill for PHP < 5.4
1779
+ if (!is_string($string)) { return false; }
1780
+ if (strlen($string) % 2 == 1) { return false; }
1781
+ return pack('H*', $string);
1782
+ }
1783
+
1784
  /**
1785
  * Identical to the same functions in wfWAFUtils.
1786
  *
1936
  $args = func_get_args();
1937
  return self::callMBSafeStrFunction('strrpos', $args);
1938
  }
1939
+
1940
+ /**
1941
+ * Returns the current timestamp, adjusted as needed to get close to what we consider a true timestamp. We use this
1942
+ * because a significant number of servers are using a drastically incorrect time.
1943
+ *
1944
+ * @return int
1945
+ */
1946
+ public static function normalizedTime() {
1947
+ $offset = wfConfig::get('timeoffset_ntp', false);
1948
+ if ($offset === false) {
1949
+ $offset = wfConfig::get('timeoffset_wf', false);
1950
+ if ($offset === false) { $offset = 0; }
1951
+ }
1952
+ return time() + $offset;
1953
+ }
1954
  }
1955
 
1956
  // GeoIP lib uses these as well
lib/wordfenceClass.php CHANGED
@@ -121,19 +121,7 @@ class wordfence {
121
  $wfdb = new wfDB();
122
 
123
  if(wfConfig::get('other_WFNet')){
124
- $q1 = $wfdb->querySelect("select URI from $p"."wfNet404s where ctime > unix_timestamp() - 3600 limit 1000");
125
- $URIs = array();
126
- foreach($q1 as $rec){
127
- $URIs[] = $rec['URI'];
128
- }
129
  $wfdb->truncate($p . "wfNet404s");
130
- if(sizeof($URIs) > 0){
131
- try {
132
- $api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) ));
133
- } catch(Exception $e){
134
- //Ignore
135
- }
136
- }
137
 
138
  $q2 = $wfdb->querySelect("select IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
139
  $scanCont = "";
@@ -361,6 +349,9 @@ class wordfence {
361
  }
362
  }
363
 
 
 
 
364
  wp_schedule_single_event(time(), 'wordfence_completeCoreUpdateNotification');
365
  }
366
  public static function _completeCoreUpdateNotification() {
@@ -481,11 +472,6 @@ SQL
481
  $db->queryWrite("update $prefix"."wfConfig set val='1' where name='scansEnabled_options'");
482
  }
483
 
484
- $optScanEnabled = $db->querySingle("select val from $prefix"."wfConfig where name='scansEnabled_heartbleed'");
485
- if($optScanEnabled != '0' && $optScanEnabled != '1'){ //Enable heartbleed if no value is set.
486
- wfConfig::set('scansEnabled_heartbleed', 1);
487
- }
488
-
489
  // IPv6 schema changes for 6.0.1
490
  $tables_with_ips = array(
491
  'wfCrawlers',
@@ -692,6 +678,28 @@ SQL
692
  $wpdb->query("ALTER TABLE {$blockedIPLogTable} ADD PRIMARY KEY (IP, unixday, blockType)");
693
  }
694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
  //Check the How does Wordfence get IPs setting
696
  wfUtils::requestDetectProxyCallback();
697
 
@@ -1230,7 +1238,7 @@ SQL
1230
  ));
1231
  wp_mail($email, "Unlock email requested", $content, "Content-Type: text/html");
1232
  }
1233
- echo "<html><body><h1>Your request was received</h1><p>We received a request to email \"" . wp_kses($email, array()) . "\" instructions to unlock their access. If that is the email address of a site administrator or someone on the Wordfence alert list, then they have been emailed instructions on how to regain access to this sytem. The instructions we sent will expire 30 minutes from now.</body></html>";
1234
  exit();
1235
  } else if($wfFunc == 'unlockAccess'){
1236
  if (!preg_match('/^(?:(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9](?::|$)){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))$/i', get_transient('wfunlock_' . $_GET['key']))) {
@@ -1300,6 +1308,13 @@ SQL
1300
  $waf->getStorageEngine()->setConfig($key, $value);
1301
  }
1302
 
 
 
 
 
 
 
 
1303
  if (class_exists('wfWAFIPBlocksController')) {
1304
  wfWAFIPBlocksController::synchronizeConfigSettings();
1305
  }
@@ -3547,7 +3562,7 @@ HTACCESS;
3547
  $jsonData = array(
3548
  'serverTime' => $serverTime,
3549
  'serverMicrotime' => microtime(true),
3550
- 'msg' => wp_kses_data( (string) $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1"))
3551
  );
3552
  $events = array();
3553
  $alsoGet = $_POST['alsoGet'];
@@ -4841,6 +4856,7 @@ HTML;
4841
  'cacheType' => wfConfig::get('cacheType'),
4842
  'liveTrafficEnabled' => wfConfig::liveTrafficEnabled(),
4843
  'scanIssuesPerPage' => WORDFENCE_SCAN_ISSUES_PER_PAGE,
 
4844
  ));
4845
  }
4846
  public static function activation_warning(){
121
  $wfdb = new wfDB();
122
 
123
  if(wfConfig::get('other_WFNet')){
 
 
 
 
 
124
  $wfdb->truncate($p . "wfNet404s");
 
 
 
 
 
 
 
125
 
126
  $q2 = $wfdb->querySelect("select IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
127
  $scanCont = "";
349
  }
350
  }
351
 
352
+ $i = new wfIssues();
353
+ $i->reconcileUpgradeIssues($report, true);
354
+
355
  wp_schedule_single_event(time(), 'wordfence_completeCoreUpdateNotification');
356
  }
357
  public static function _completeCoreUpdateNotification() {
472
  $db->queryWrite("update $prefix"."wfConfig set val='1' where name='scansEnabled_options'");
473
  }
474
 
 
 
 
 
 
475
  // IPv6 schema changes for 6.0.1
476
  $tables_with_ips = array(
477
  'wfCrawlers',
678
  $wpdb->query("ALTER TABLE {$blockedIPLogTable} ADD PRIMARY KEY (IP, unixday, blockType)");
679
  }
680
 
681
+ //6.3.6
682
+ if (!wfConfig::get('migration636_email_summary_excluded_directories')) {
683
+ $excluded_directories = explode(',', (string) wfConfig::get('email_summary_excluded_directories'));
684
+ $key = array_search('wp-content/plugins/wordfence/tmp', $excluded_directories); if ($key !== false) { unset($excluded_directories[$key]); }
685
+ $key = array_search('wp-content/wflogs', $excluded_directories); if ($key === false) { $excluded_directories[] = 'wp-content/wflogs'; }
686
+ wfConfig::set('email_summary_excluded_directories', implode(',', $excluded_directories));
687
+ wfConfig::set('migration636_email_summary_excluded_directories', 1, wfConfig::DONT_AUTOLOAD);
688
+ }
689
+
690
+ $fileModsTable = wfDB::networkPrefix() . 'wfFileMods';
691
+ $hasSHAC = $wpdb->get_col($wpdb->prepare(<<<SQL
692
+ SELECT * FROM information_schema.COLUMNS
693
+ WHERE TABLE_SCHEMA=DATABASE()
694
+ AND COLUMN_NAME='SHAC'
695
+ AND TABLE_NAME=%s
696
+ SQL
697
+ , $fileModsTable));
698
+ if (!$hasSHAC) {
699
+ $wpdb->query("ALTER TABLE {$fileModsTable} ADD COLUMN `SHAC` BINARY(32) NOT NULL DEFAULT '' AFTER `newMD5`");
700
+ $wpdb->query("ALTER TABLE {$fileModsTable} ADD COLUMN `isSafeFile` VARCHAR(1) NOT NULL DEFAULT '?' AFTER `stoppedOnPosition`");
701
+ }
702
+
703
  //Check the How does Wordfence get IPs setting
704
  wfUtils::requestDetectProxyCallback();
705
 
1238
  ));
1239
  wp_mail($email, "Unlock email requested", $content, "Content-Type: text/html");
1240
  }
1241
+ echo "<html><body><h1>Your request was received</h1><p>We received a request to email \"" . wp_kses($email, array()) . "\" instructions to unlock their access. If that is the email address of a site administrator or someone on the Wordfence alert list, they have been emailed instructions on how to regain access to this system. The instructions we sent will expire 30 minutes from now.</body></html>";
1242
  exit();
1243
  } else if($wfFunc == 'unlockAccess'){
1244
  if (!preg_match('/^(?:(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9](?::|$)){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))$/i', get_transient('wfunlock_' . $_GET['key']))) {
1308
  $waf->getStorageEngine()->setConfig($key, $value);
1309
  }
1310
 
1311
+ if (wfConfig::get('timeoffset_wf') !== false) {
1312
+ $waf->getStorageEngine()->setConfig('timeoffset_wf', wfConfig::get('timeoffset_wf'));
1313
+ }
1314
+ else {
1315
+ $waf->getStorageEngine()->unsetConfig('timeoffset_wf');
1316
+ }
1317
+
1318
  if (class_exists('wfWAFIPBlocksController')) {
1319
  wfWAFIPBlocksController::synchronizeConfigSettings();
1320
  }
3562
  $jsonData = array(
3563
  'serverTime' => $serverTime,
3564
  'serverMicrotime' => microtime(true),
3565
+ 'msg' => wp_kses_data( (string) $wfdb->querySingle("SELECT msg FROM {$p}wfStatus WHERE level < 3 AND ctime > (UNIX_TIMESTAMP() - 3600) ORDER BY ctime DESC LIMIT 1"))
3566
  );
3567
  $events = array();
3568
  $alsoGet = $_POST['alsoGet'];
4856
  'cacheType' => wfConfig::get('cacheType'),
4857
  'liveTrafficEnabled' => wfConfig::liveTrafficEnabled(),
4858
  'scanIssuesPerPage' => WORDFENCE_SCAN_ISSUES_PER_PAGE,
4859
+ 'allowsPausing' => wfConfig::get('liveActivityPauseEnabled'),
4860
  ));
4861
  }
4862
  public static function activation_warning(){
lib/wordfenceHash.php CHANGED
@@ -41,7 +41,7 @@ class wordfenceHash {
41
  * @param wfScanEngine $engine
42
  * @throws Exception
43
  */
44
- public function __construct($striplen, $path, $only, $themes, $plugins, $engine){
45
  $this->striplen = $striplen;
46
  $this->path = $path;
47
  $this->only = $only;
@@ -71,6 +71,7 @@ class wordfenceHash {
71
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
72
  $this->db->truncate($this->db->prefix() . "wfFileMods");
73
  $this->db->truncate($this->db->prefix() . "wfKnownFileList");
 
74
  $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
75
  try {
76
  $this->knownFiles = $this->engine->getKnownFilesLoader()
@@ -80,21 +81,32 @@ class wordfenceHash {
80
  throw $e;
81
  }
82
  wordfence::statusEnd($fetchCoreHashesStatus, false, true);
83
- if($this->malwareEnabled){
84
  $malwarePrefixStatus = wordfence::statusStart("Fetching list of known malware files from Wordfence");
85
- $malwareData = $engine->api->getStaticURL('/malwarePrefixes.bin');
86
- if(! $malwareData){
87
- wordfence::statusEndErr();
88
- throw new Exception("Could not fetch malware signatures from Wordfence servers.");
89
- }
90
- if(strlen($malwareData) % 4 != 0){
91
- wordfence::statusEndErr();
92
- throw new Exception("Malware data received from Wordfence servers was not valid.");
93
  }
94
- $this->malwareData = array();
95
- for($i = 0; $i < strlen($malwareData); $i += 4){
96
- $this->malwareData[substr($malwareData, $i, 4)] = '1';
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
 
 
98
  wordfence::statusEnd($malwarePrefixStatus, false, true);
99
  }
100
 
@@ -118,12 +130,18 @@ class wordfenceHash {
118
  if($this->coreUnknownEnabled){ $this->status['coreUnknown'] = wordfence::statusStart("Scanning for unknown files in wp-admin and wp-includes"); } else { wordfence::statusDisabled("Skipping unknown core file scan"); }
119
  }
120
  public function __sleep(){
121
- return array('striplen', 'totalFiles', 'totalDirs', 'totalData', 'stoppedOnFile', 'coreEnabled', 'pluginsEnabled', 'themesEnabled', 'malwareEnabled', 'coreUnknownEnabled', 'knownFiles', 'malwareData', 'haveIssues', 'status', 'possibleMalware', 'path', 'only', 'totalForks', 'alertedOnUnknownWordPressVersion', 'foldersProcessed', 'suspectedFiles', 'indexed', 'indexSize', 'currentIndex', 'foldersEntered');
122
  }
123
  public function __wakeup(){
124
  $this->db = new wfDB();
125
  $this->startTime = microtime(true);
126
  $this->totalForks++;
 
 
 
 
 
 
127
  }
128
  public function getSuspectedFiles() {
129
  return array_keys($this->suspectedFiles);
@@ -164,6 +182,9 @@ class wordfenceHash {
164
  $this->_checkForTimeout($file);
165
  }
166
 
 
 
 
167
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data.");
168
  if($this->coreEnabled){ wordfence::statusEnd($this->status['core'], $this->haveIssues['core']); }
169
  if($this->themesEnabled){ wordfence::statusEnd($this->status['themes'], $this->haveIssues['themes']); }
@@ -409,28 +430,24 @@ class wordfenceHash {
409
  {
410
  $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
411
  $fileContents = @file_get_contents($localFile);
412
- if ($fileContents && (!preg_match('/<\?' . 'php[\r\n\s\t]*\/\/[\r\n\s\t]*Silence is golden\.[\r\n\s\t]*(?:\?>)?[\r\n\s\t]*$/s', $fileContents)))
413
- { //<?php
414
- if (!$this->isSafeFile($shac))
415
- {
416
-
417
- $this->haveIssues['core'] = true;
418
- $this->engine->addIssue(
419
- 'knownfile',
420
- 1,
421
- 'coreModified' . $file . $md5,
422
- 'coreModified' . $file,
423
- 'WordPress core file modified: ' . $file,
424
- "This WordPress core file has been modified and differs from the original file distributed with this version of WordPress.",
425
- array(
426
- 'file' => $file,
427
- 'cType' => 'core',
428
- 'canDiff' => true,
429
- 'canFix' => true,
430
- 'canDelete' => false
431
- )
432
- );
433
- }
434
  }
435
  }
436
  }
@@ -448,13 +465,12 @@ class wordfenceHash {
448
  $shouldGenerateIssue = false;
449
  }
450
 
451
- if (!$this->isSafeFile($shac) && $shouldGenerateIssue)
452
  {
453
  $itemName = $this->knownFiles['plugins'][$file][0];
454
  $itemVersion = $this->knownFiles['plugins'][$file][1];
455
  $cKey = $this->knownFiles['plugins'][$file][2];
456
- $this->haveIssues['plugins'] = true;
457
- $this->engine->addIssue(
458
  'knownfile',
459
  2,
460
  'modifiedplugin' . $file . $md5,
@@ -469,7 +485,8 @@ class wordfenceHash {
469
  'canDelete' => false,
470
  'cName' => $itemName,
471
  'cVersion' => $itemVersion,
472
- 'cKey' => $cKey
 
473
  )
474
  );
475
  }
@@ -490,13 +507,12 @@ class wordfenceHash {
490
  $shouldGenerateIssue = false;
491
  }
492
 
493
- if (!$this->isSafeFile($shac) && $shouldGenerateIssue)
494
  {
495
  $itemName = $this->knownFiles['themes'][$file][0];
496
  $itemVersion = $this->knownFiles['themes'][$file][1];
497
  $cKey = $this->knownFiles['themes'][$file][2];
498
- $this->haveIssues['themes'] = true;
499
- $this->engine->addIssue(
500
  'knownfile',
501
  2,
502
  'modifiedtheme' . $file . $md5,
@@ -511,7 +527,8 @@ class wordfenceHash {
511
  'canDelete' => false,
512
  'cName' => $itemName,
513
  'cVersion' => $itemVersion,
514
- 'cKey' => $cKey
 
515
  )
516
  );
517
  }
@@ -536,7 +553,7 @@ class wordfenceHash {
536
  'cType' => 'core',
537
  'canDiff' => false,
538
  'canFix' => false,
539
- 'canDelete' => true
540
  )
541
  );
542
  }
@@ -544,9 +561,10 @@ class wordfenceHash {
544
  }
545
  }
546
  // knownFile means that the file is both part of core or a known plugin or theme AND that we recognize the file's hash.
547
- // we could split this into files who's path we recognize and file's who's path we recognize AND who have a valid sig.
548
- // But because we want to scan files who's sig we don't recognize, regardless of known path or not, we only need one "knownFile" field.
549
- $this->db->queryWrite("insert into " . $this->db->prefix() . "wfFileMods (filename, filenameMD5, knownFile, oldMD5, newMD5) values ('%s', unhex(md5('%s')), %d, '', unhex('%s')) ON DUPLICATE KEY UPDATE newMD5=unhex('%s'), knownFile=%d", $file, $file, $knownFile, $md5, $md5, $knownFile);
 
550
 
551
  $this->totalFiles++;
552
  $this->totalData += @filesize($realFile); //We already checked if file overflows int in the fileTooBig routine above
@@ -558,6 +576,61 @@ class wordfenceHash {
558
  }
559
  wfUtils::endProcessingFile();
560
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  public static function wfHash($file){
562
  $fp = @fopen($file, "rb");
563
  if (!$fp) {
@@ -639,13 +712,57 @@ class wordfenceHash {
639
  return false;
640
  }
641
  private function isMalwarePrefix($hexMD5){
642
- $binPrefix = pack("H*", substr($hexMD5, 0, 8));
643
- if(isset($this->malwareData[$binPrefix])){
644
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  }
 
646
  return false;
647
  }
648
- private function isSafeFile($shac){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  $result = $this->engine->api->call('is_safe_file', array(), array('shac' => strtoupper($shac)));
650
  if(isset($result['isSafe']) && $result['isSafe'] == 1){
651
  return true;
41
  * @param wfScanEngine $engine
42
  * @throws Exception
43
  */
44
+ public function __construct($striplen, $path, $only, $themes, $plugins, $engine, $malwarePrefixesHash){
45
  $this->striplen = $striplen;
46
  $this->path = $path;
47
  $this->only = $only;
71
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
72
  $this->db->truncate($this->db->prefix() . "wfFileMods");
73
  $this->db->truncate($this->db->prefix() . "wfKnownFileList");
74
+ $this->db->truncate($this->db->prefix() . "wfPendingIssues");
75
  $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
76
  try {
77
  $this->knownFiles = $this->engine->getKnownFilesLoader()
81
  throw $e;
82
  }
83
  wordfence::statusEnd($fetchCoreHashesStatus, false, true);
84
+ if ($this->malwareEnabled) {
85
  $malwarePrefixStatus = wordfence::statusStart("Fetching list of known malware files from Wordfence");
86
+
87
+ $stored = wfConfig::get_ser('malwarePrefixes', array(), false);
88
+ if (is_array($stored) && isset($stored['hash']) && $stored['hash'] == $malwarePrefixesHash && isset($stored['prefixes']) && wfWAFUtils::strlen($stored['prefixes']) % 4 == 0) {
89
+ wordfence::status(4, 'info', "Using cached malware prefixes");
 
 
 
 
90
  }
91
+ else {
92
+ wordfence::status(4, 'info', "Fetching fresh malware prefixes");
93
+
94
+ $malwareData = $engine->api->getStaticURL('/malwarePrefixes.bin');
95
+ if (!$malwareData) {
96
+ wordfence::statusEndErr();
97
+ throw new Exception("Could not fetch malware signatures from Wordfence servers.");
98
+ }
99
+
100
+ if (wfWAFUtils::strlen($malwareData) % 4 != 0) {
101
+ wordfence::statusEndErr();
102
+ throw new Exception("Malware data received from Wordfence servers was not valid.");
103
+ }
104
+
105
+ $stored = array('hash' => $malwarePrefixesHash, 'prefixes' => $malwareData);
106
+ wfConfig::set_ser('malwarePrefixes', $stored, true, wfConfig::DONT_AUTOLOAD);
107
  }
108
+
109
+ $this->malwareData = $stored['prefixes'];
110
  wordfence::statusEnd($malwarePrefixStatus, false, true);
111
  }
112
 
130
  if($this->coreUnknownEnabled){ $this->status['coreUnknown'] = wordfence::statusStart("Scanning for unknown files in wp-admin and wp-includes"); } else { wordfence::statusDisabled("Skipping unknown core file scan"); }
131
  }
132
  public function __sleep(){
133
+ return array('striplen', 'totalFiles', 'totalDirs', 'totalData', 'stoppedOnFile', 'coreEnabled', 'pluginsEnabled', 'themesEnabled', 'malwareEnabled', 'coreUnknownEnabled', 'knownFiles', 'haveIssues', 'status', 'possibleMalware', 'path', 'only', 'totalForks', 'alertedOnUnknownWordPressVersion', 'foldersProcessed', 'suspectedFiles', 'indexed', 'indexSize', 'currentIndex', 'foldersEntered');
134
  }
135
  public function __wakeup(){
136
  $this->db = new wfDB();
137
  $this->startTime = microtime(true);
138
  $this->totalForks++;
139
+
140
+ $stored = wfConfig::get_ser('malwarePrefixes', array(), false);
141
+ if (!isset($stored['prefixes'])) {
142
+ $stored['prefixes'] = '';
143
+ }
144
+ $this->malwareData = $stored['prefixes'];
145
  }
146
  public function getSuspectedFiles() {
147
  return array_keys($this->suspectedFiles);
182
  $this->_checkForTimeout($file);
183
  }
184
 
185
+ wordfence::status(4, 'info', "Processing pending issues");
186
+ $this->_processPendingIssues();
187
+
188
  wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data.");
189
  if($this->coreEnabled){ wordfence::statusEnd($this->status['core'], $this->haveIssues['core']); }
190
  if($this->themesEnabled){ wordfence::statusEnd($this->status['themes'], $this->haveIssues['themes']); }
430
  {
431
  $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
432
  $fileContents = @file_get_contents($localFile);
433
+ if ($fileContents && (!preg_match('/<\?' . 'php[\r\n\s\t]*\/\/[\r\n\s\t]*Silence is golden\.[\r\n\s\t]*(?:\?>)?[\r\n\s\t]*$/s', $fileContents))) {
434
+ $this->haveIssues['core'] = true;
435
+ $this->engine->addPendingIssue(
436
+ 'knownfile',
437
+ 1,
438
+ 'coreModified' . $file . $md5,
439
+ 'coreModified' . $file,
440
+ 'WordPress core file modified: ' . $file,
441
+ "This WordPress core file has been modified and differs from the original file distributed with this version of WordPress.",
442
+ array(
443
+ 'file' => $file,
444
+ 'cType' => 'core',
445
+ 'canDiff' => true,
446
+ 'canFix' => true,
447
+ 'canDelete' => false,
448
+ 'haveIssues' => 'core'
449
+ )
450
+ );
 
 
 
 
451
  }
452
  }
453
  }
465
  $shouldGenerateIssue = false;
466
  }
467
 
468
+ if ($shouldGenerateIssue)
469
  {
470
  $itemName = $this->knownFiles['plugins'][$file][0];
471
  $itemVersion = $this->knownFiles['plugins'][$file][1];
472
  $cKey = $this->knownFiles['plugins'][$file][2];
473
+ $this->engine->addPendingIssue(
 
474
  'knownfile',
475
  2,
476
  'modifiedplugin' . $file . $md5,
485
  'canDelete' => false,
486
  'cName' => $itemName,
487
  'cVersion' => $itemVersion,
488
+ 'cKey' => $cKey,
489
+ 'haveIssues' => 'plugins'
490
  )
491
  );
492
  }
507
  $shouldGenerateIssue = false;
508
  }
509
 
510
+ if ($shouldGenerateIssue)
511
  {
512
  $itemName = $this->knownFiles['themes'][$file][0];
513
  $itemVersion = $this->knownFiles['themes'][$file][1];
514
  $cKey = $this->knownFiles['themes'][$file][2];
515
+ $this->engine->addPendingIssue(
 
516
  'knownfile',
517
  2,
518
  'modifiedtheme' . $file . $md5,
527
  'canDelete' => false,
528
  'cName' => $itemName,
529
  'cVersion' => $itemVersion,
530
+ 'cKey' => $cKey,
531
+ 'haveIssues' => 'themes'
532
  )
533
  );
534
  }
553
  'cType' => 'core',
554
  'canDiff' => false,
555
  'canFix' => false,
556
+ 'canDelete' => true,
557
  )
558
  );
559
  }
561
  }
562
  }
563
  // knownFile means that the file is both part of core or a known plugin or theme AND that we recognize the file's hash.
564
+ // we could split this into files whose path we recognize and file's whose path we recognize AND who have a valid sig.
565
+ // But because we want to scan files whose sig we don't recognize, regardless of known path or not, we only need one "knownFile" field.
566
+ $fileModsTable = $this->db->prefix() . 'wfFileMods';
567
+ $this->db->queryWrite("INSERT INTO {$fileModsTable} (filename, filenameMD5, knownFile, oldMD5, newMD5, SHAC) VALUES ('%s', UNHEX(MD5('%s')), %d, '', UNHEX('%s'), UNHEX('%s')) ON DUPLICATE KEY UPDATE newMD5 = UNHEX('%s'), SHAC = UNHEX('%s'), knownFile = %d", $file, $file, $knownFile, $md5, $shac, $md5, $shac, $knownFile);
568
 
569
  $this->totalFiles++;
570
  $this->totalData += @filesize($realFile); //We already checked if file overflows int in the fileTooBig routine above
576
  }
577
  wfUtils::endProcessingFile();
578
  }
579
+ private function _processPendingIssues() {
580
+ $fileModsTable = $this->db->prefix() . 'wfFileMods';
581
+
582
+ $count = $this->engine->getPendingIssueCount();
583
+ $offset = 0;
584
+ while ($offset < $count) {
585
+ $issues = $this->engine->getPendingIssues($offset);
586
+ if (count($issues) == 0) {
587
+ break;
588
+ }
589
+
590
+ //Do a bulk check of is_safe_file
591
+ $hashesToCheck = array();
592
+ foreach ($issues as &$i) {
593
+ $shac = $this->db->querySingle("SELECT HEX(SHAC) FROM {$fileModsTable} WHERE filename = '%s' AND isSafeFile = '?'", $i['data']['file']);
594
+ $shac = strtoupper($shac);
595
+ $i['shac'] = null;
596
+ if ($shac !== null) {
597
+ $shac = strtoupper($shac);
598
+ $i['shac'] = $shac;
599
+ $hashesToCheck[] = $shac;
600
+ }
601
+ }
602
+
603
+ $safeFiles = array();
604
+ if (count($hashesToCheck) > 0) {
605
+ $safeFiles = $this->isSafeFile($hashesToCheck);
606
+ }
607
+
608
+ //Migrate non-safe file issues to official issues
609
+ foreach ($issues as &$i) {
610
+ if (!in_array($i['shac'], $safeFiles)) {
611
+ $haveIssuesType = $i['data']['haveIssues'];
612
+ $this->haveIssues[$haveIssuesType] = true;
613
+ $this->engine->addIssue(
614
+ $i['type'],
615
+ $i['severity'],
616
+ $i['ignoreP'],
617
+ $i['ignoreC'],
618
+ $i['shortMsg'],
619
+ $i['longMsg'],
620
+ $i['data'],
621
+ true //Prevent ignoreP and ignoreC from being hashed again
622
+ );
623
+ $this->db->queryWrite("UPDATE {$fileModsTable} SET isSafeFile = '0' WHERE SHAC = UNHEX('%s')", $i['shac']);
624
+ }
625
+ else {
626
+ $this->db->queryWrite("UPDATE {$fileModsTable} SET isSafeFile = '1' WHERE SHAC = UNHEX('%s')", $i['shac']);
627
+ }
628
+ }
629
+
630
+ $offset += count($issues);
631
+ $this->engine->checkForKill();
632
+ }
633
+ }
634
  public static function wfHash($file){
635
  $fp = @fopen($file, "rb");
636
  if (!$fp) {
712
  return false;
713
  }
714
  private function isMalwarePrefix($hexMD5){
715
+ $hasPrefix = $this->_prefixListContainsMD5($this->malwareData, wfUtils::hex2bin($hexMD5));
716
+ return $hasPrefix !== false;
717
+ }
718
+
719
+ /**
720
+ * @param $prefixList The prefix list to search, sorted as a binary string.
721
+ * @param $md5 The binary MD5 hash to search for.
722
+ * @return bool|int false if not found, otherwise the index in the list
723
+ */
724
+ private function _prefixListContainsMD5($prefixList, $md5) {
725
+ $size = 4; //bytes
726
+ $p = substr($md5, 0, $size);
727
+
728
+ $count = ceil(wfWAFUtils::strlen($prefixList) / $size);
729
+ $low = 0;
730
+ $high = $count - 1;
731
+
732
+ while ($low <= $high) {
733
+ $mid = (int) (($high + $low) / 2);
734
+ $val = wfWAFUtils::substr($prefixList, $mid * $size, $size);
735
+ $cmp = strcmp($val, $p);
736
+ if ($cmp < 0) {
737
+ $low = $mid + 1;
738
+ }
739
+ else if ($cmp > 0) {
740
+ $high = $mid - 1;
741
+ }
742
+ else {
743
+ return $mid;
744
+ }
745
  }
746
+
747
  return false;
748
  }
749
+
750
+ /**
751
+ * Queries the is_safe_file endpoint. If provided an array, it does a bulk check and returns an array containing the
752
+ * hashes that were marked as safe. If provided a string, it returns a boolean to indicate the safeness of the file.
753
+ *
754
+ * @param string|array $shac
755
+ * @return array|bool
756
+ */
757
+ private function isSafeFile($shac) {
758
+ if (is_array($shac)) {
759
+ $result = $this->engine->api->call('is_safe_file', array(), array('multipleSHAC' => json_encode($shac)));
760
+ if (isset($result['isSafe'])) {
761
+ return $result['isSafe'];
762
+ }
763
+ return array();
764
+ }
765
+
766
  $result = $this->engine->api->call('is_safe_file', array(), array('shac' => strtoupper($shac)));
767
  if(isset($result['isSafe']) && $result['isSafe'] == 1){
768
  return true;
lib/wordfenceScanner.php CHANGED
@@ -301,21 +301,20 @@ class wordfenceScanner {
301
 
302
  $treatAsBinary = ($isPHP || $isHTML || wfConfig::get('scansEnabled_scanImages'));
303
  if ($treatAsBinary && wfUtils::strpos($data, '$allowed'.'Sites') !== false && wfUtils::strpos($data, "define ('VER"."SION', '1.") !== false && wfUtils::strpos($data, "TimThum"."b script created by") !== false) {
304
- if(! $this->isSafeFile($this->path . $file)){
305
- $this->addResult(array(
306
- 'type' => 'file',
307
- 'severity' => 1,
308
- 'ignoreP' => $this->path . $file,
309
- 'ignoreC' => $fileSum,
310
- 'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
311
- 'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it." . $extraMsg,
312
- 'data' => array_merge(array(
313
- 'file' => $file,
314
- ), $dataForFile),
315
- ));
316
- break;
317
- }
318
- }
319
  else {
320
  $regexMatched = false;
321
  foreach ($this->patterns['rules'] as $rule) {
@@ -336,28 +335,27 @@ class wordfenceScanner {
336
  else if (($type == 'both' || $type == 'browser') && !$treatAsBinary) { continue; }
337
 
338
  if (preg_match('/(' . $rule[2] . ')/i', $data, $matches, PREG_OFFSET_CAPTURE)) {
339
- if (!$this->isSafeFile($this->path . $file)) {
340
- $matchString = $matches[1][0];
341
- $matchOffset = $matches[1][1];
342
- $beforeString = wfWAFUtils::substr($data, max(0, $matchOffset - 100), $matchOffset - max(0, $matchOffset - 100));
343
- $afterString = wfWAFUtils::substr($data, $matchOffset + strlen($matchString), 100);
344
- if (!$logOnly) {
345
- $this->addResult(array(
346
- 'type' => 'file',
347
- 'severity' => 1,
348
- 'ignoreP' => $this->path . $file,
349
- 'ignoreC' => $fileSum,
350
- 'shortMsg' => "File appears to be malicious: " . esc_html($file),
351
- 'longMsg' => "This file appears to be installed by a hacker to perform malicious activity. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\" class=\"wf-split-word\">\"" . wfUtils::potentialBinaryStringToHTML((wfUtils::strlen($matchString) > 200 ? wfUtils::substr($matchString, 0, 200) . '...' : $matchString)) . "\"</strong>. The infection type is: <strong>" . esc_html($rule[3]) . '</strong>.' . $extraMsg,
352
- 'data' => array_merge(array(
353
- 'file' => $file,
354
- ), $dataForFile),
355
- ));
356
- }
357
- $regexMatched = true;
358
- $this->scanEngine->recordMetric('malwareSignature', $rule[0], array('file' => $file, 'match' => $matchString, 'before' => $beforeString, 'after' => $afterString), false);
359
- break;
360
  }
 
 
 
361
  }
362
 
363
  if ($forkObj->shouldFork()) {
@@ -383,20 +381,19 @@ class wordfenceScanner {
383
  }
384
  }
385
  if ($badStringFound) {
386
- if (!$this->isSafeFile($this->path . $file)) {
387
- $this->addResult(array(
388
- 'type' => 'file',
389
- 'severity' => 1,
390
- 'ignoreP' => $this->path . $file,
391
- 'ignoreC' => $fileSum,
392
- 'shortMsg' => "This file may contain malicious executable code: " . esc_html($this->path . $file),
393
- 'longMsg' => "This file is a PHP executable file and contains the word 'eval' (without quotes) and the word '<span class=\"wf-split-word\">" . esc_html($badStringFound) . "</span>' (without quotes). The eval() function along with an encoding function like the one mentioned are commonly used by hackers to hide their code. If you know about this file you can choose to ignore it to exclude it from future scans. This file was detected because you have enabled HIGH SENSITIVITY scanning. This option is more aggressive than the usual scans, and may cause false positives.",
394
- 'data' => array_merge(array(
395
- 'file' => $file,
396
- ), $dataForFile),
397
- ));
398
- break;
399
- }
400
  }
401
  }
402
 
@@ -430,6 +427,7 @@ class wordfenceScanner {
430
  $siteURL = get_site_url();
431
  $siteHost = parse_url($siteURL, PHP_URL_HOST);
432
  foreach($hooverResults as $file => $hresults){
 
433
  $dataForFile = $this->dataForFile($file, $this->path . $file);
434
 
435
  foreach($hresults as $result){
@@ -447,41 +445,54 @@ class wordfenceScanner {
447
  }
448
 
449
  if($result['badList'] == 'goog-malware-shavar'){
450
- if(! $this->isSafeFile($this->path . $file)){
451
- $this->addResult(array(
452
- 'type' => 'file',
453
- 'severity' => 1,
454
- 'ignoreP' => $this->path . $file,
455
- 'ignoreC' => md5_file($this->path . $file),
456
- 'shortMsg' => "File contains suspected malware URL: " . esc_html($this->path . $file),
457
- 'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes " . esc_html($this->patterns['word3']) . " when scanning files so the URL may not be visible if you view this file. The URL is: " . esc_html($result['URL']) . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
458
- 'data' => array_merge(array(
459
- 'file' => $file,
460
- 'badURL' => $result['URL'],
461
- 'gsb' => 'goog-malware-shavar'
462
- ), $dataForFile),
463
- ));
464
- }
465
  } else if($result['badList'] == 'googpub-phish-shavar'){
466
- if(! $this->isSafeFile($this->path . $file)){
467
- $this->addResult(array(
468
- 'type' => 'file',
469
- 'severity' => 1,
470
- 'ignoreP' => $this->path . $file,
471
- 'ignoreC' => md5_file($this->path . $file),
472
- 'shortMsg' => "File contains suspected phishing URL: " . esc_html($this->path . $file),
473
- 'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . esc_html($result['URL']),
474
- 'data' => array_merge(array(
475
- 'file' => $file,
476
- 'badURL' => $result['URL'],
477
- 'gsb' => 'googpub-phish-shavar'
478
- ), $dataForFile),
479
- ));
480
- }
481
  }
482
  }
483
  }
484
  wfUtils::endProcessingFile();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
486
  return $this->results;
487
  }
@@ -502,13 +513,28 @@ class wordfenceScanner {
502
  //We don't have a results for this file so append
503
  $this->results[] = $result;
504
  }
505
- private function isSafeFile($file){
 
 
 
 
 
 
 
 
506
  if(! $this->api){
507
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
508
  }
509
-
510
- $wfHash = wordfenceHash::wfHash($file);
511
- $result = $this->api->call('is_safe_file', array(), array('shac' => strtoupper($wfHash[1])));
 
 
 
 
 
 
 
512
  if(isset($result['isSafe']) && $result['isSafe'] == 1){
513
  return true;
514
  }
@@ -584,15 +610,19 @@ class wordfenceScanner {
584
  * @property string $filename
585
  * @property string $filenameMD5
586
  * @property string $newMD5
 
587
  * @property string $stoppedOnSignature
588
  * @property string $stoppedOnPosition
 
589
  */
590
  class wordfenceMalwareScanFile {
591
  protected $_filename;
592
  protected $_filenameMD5;
593
  protected $_newMD5;
 
594
  protected $_stoppedOnSignature;
595
  protected $_stoppedOnPosition;
 
596
 
597
  protected static function getDB() {
598
  static $db = null;
@@ -609,20 +639,28 @@ class wordfenceMalwareScanFile {
609
 
610
  public static function files($limit = 500) {
611
  $db = self::getDB();
612
- $result = $db->querySelect("SELECT filename, filenameMD5, HEX(newMD5) AS newMD5, stoppedOnSignature, stoppedOnPosition FROM " . wfDB::networkPrefix() . "wfFileMods WHERE oldMD5 != newMD5 AND knownFile = 0 limit %d", $limit);
613
  $files = array();
614
  foreach ($result as $row) {
615
- $files[] = new wordfenceMalwareScanFile($row['filename'], $row['filenameMD5'], $row['newMD5'], $row['stoppedOnSignature'], $row['stoppedOnPosition']);
616
  }
617
  return $files;
618
  }
619
 
620
- public function __construct($filename, $filenameMD5, $newMD5, $stoppedOnSignature, $stoppedOnPosition) {
 
 
 
 
 
 
621
  $this->_filename = $filename;
622
  $this->_filenameMD5 = $filenameMD5;
623
  $this->_newMD5 = $newMD5;
 
624
  $this->_stoppedOnSignature = $stoppedOnSignature;
625
  $this->_stoppedOnPosition = $stoppedOnPosition;
 
626
  }
627
 
628
  public function __get($key) {
@@ -633,10 +671,14 @@ class wordfenceMalwareScanFile {
633
  return $this->_filenameMD5;
634
  case 'newMD5':
635
  return $this->_newMD5;
 
 
636
  case 'stoppedOnSignature':
637
  return $this->_stoppedOnSignature;
638
  case 'stoppedOnPosition':
639
  return $this->_stoppedOnPosition;
 
 
640
  }
641
  }
642
 
@@ -655,6 +697,18 @@ class wordfenceMalwareScanFile {
655
  $db = self::getDB();
656
  $db->queryWrite("UPDATE " . wfDB::networkPrefix() . "wfFileMods SET stoppedOnSignature = '%s', stoppedOnPosition = %d WHERE filenameMD5 = '%s'", $this->stoppedOnSignature, $this->stoppedOnPosition, $this->filenameMD5);
657
  }
 
 
 
 
 
 
 
 
 
 
 
 
658
  }
659
 
660
  ?>
301
 
302
  $treatAsBinary = ($isPHP || $isHTML || wfConfig::get('scansEnabled_scanImages'));
303
  if ($treatAsBinary && wfUtils::strpos($data, '$allowed'.'Sites') !== false && wfUtils::strpos($data, "define ('VER"."SION', '1.") !== false && wfUtils::strpos($data, "TimThum"."b script created by") !== false) {
304
+ $this->addResult(array(
305
+ 'type' => 'file',
306
+ 'severity' => 1,
307
+ 'ignoreP' => $this->path . $file,
308
+ 'ignoreC' => $fileSum,
309
+ 'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
310
+ 'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it." . $extraMsg,
311
+ 'data' => array_merge(array(
312
+ 'file' => $file,
313
+ 'shac' => $record->SHAC,
314
+ ), $dataForFile),
315
+ ));
316
+ break;
317
+ }
 
318
  else {
319
  $regexMatched = false;
320
  foreach ($this->patterns['rules'] as $rule) {
335
  else if (($type == 'both' || $type == 'browser') && !$treatAsBinary) { continue; }
336
 
337
  if (preg_match('/(' . $rule[2] . ')/i', $data, $matches, PREG_OFFSET_CAPTURE)) {
338
+ $matchString = $matches[1][0];
339
+ $matchOffset = $matches[1][1];
340
+ $beforeString = wfWAFUtils::substr($data, max(0, $matchOffset - 100), $matchOffset - max(0, $matchOffset - 100));
341
+ $afterString = wfWAFUtils::substr($data, $matchOffset + strlen($matchString), 100);
342
+ if (!$logOnly) {
343
+ $this->addResult(array(
344
+ 'type' => 'file',
345
+ 'severity' => 1,
346
+ 'ignoreP' => $this->path . $file,
347
+ 'ignoreC' => $fileSum,
348
+ 'shortMsg' => "File appears to be malicious: " . esc_html($file),
349
+ 'longMsg' => "This file appears to be installed by a hacker to perform malicious activity. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\" class=\"wf-split-word\">\"" . wfUtils::potentialBinaryStringToHTML((wfUtils::strlen($matchString) > 200 ? wfUtils::substr($matchString, 0, 200) . '...' : $matchString)) . "\"</strong>. The infection type is: <strong>" . esc_html($rule[3]) . '</strong>.' . $extraMsg,
350
+ 'data' => array_merge(array(
351
+ 'file' => $file,
352
+ 'shac' => $record->SHAC,
353
+ ), $dataForFile),
354
+ ));
 
 
 
 
355
  }
356
+ $regexMatched = true;
357
+ $this->scanEngine->recordMetric('malwareSignature', $rule[0], array('file' => $file, 'match' => $matchString, 'before' => $beforeString, 'after' => $afterString), false);
358
+ break;
359
  }
360
 
361
  if ($forkObj->shouldFork()) {
381
  }
382
  }
383
  if ($badStringFound) {
384
+ $this->addResult(array(
385
+ 'type' => 'file',
386
+ 'severity' => 1,
387
+ 'ignoreP' => $this->path . $file,
388
+ 'ignoreC' => $fileSum,
389
+ 'shortMsg' => "This file may contain malicious executable code: " . esc_html($this->path . $file),
390
+ 'longMsg' => "This file is a PHP executable file and contains the word 'eval' (without quotes) and the word '<span class=\"wf-split-word\">" . esc_html($badStringFound) . "</span>' (without quotes). The eval() function along with an encoding function like the one mentioned are commonly used by hackers to hide their code. If you know about this file you can choose to ignore it to exclude it from future scans. This file was detected because you have enabled HIGH SENSITIVITY scanning. This option is more aggressive than the usual scans, and may cause false positives.",
391
+ 'data' => array_merge(array(
392
+ 'file' => $file,
393
+ 'shac' => $record->SHAC,
394
+ ), $dataForFile),
395
+ ));
396
+ break;
 
397
  }
398
  }
399
 
427
  $siteURL = get_site_url();
428
  $siteHost = parse_url($siteURL, PHP_URL_HOST);
429
  foreach($hooverResults as $file => $hresults){
430
+ $record = wordfenceMalwareScanFile::fileForPath($file);
431
  $dataForFile = $this->dataForFile($file, $this->path . $file);
432
 
433
  foreach($hresults as $result){
445
  }
446
 
447
  if($result['badList'] == 'goog-malware-shavar'){
448
+ $this->addResult(array(
449
+ 'type' => 'file',
450
+ 'severity' => 1,
451
+ 'ignoreP' => $this->path . $file,
452
+ 'ignoreC' => md5_file($this->path . $file),
453
+ 'shortMsg' => "File contains suspected malware URL: " . esc_html($this->path . $file),
454
+ 'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes " . esc_html($this->patterns['word3']) . " when scanning files so the URL may not be visible if you view this file. The URL is: " . esc_html($result['URL']) . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
455
+ 'data' => array_merge(array(
456
+ 'file' => $file,
457
+ 'shac' => $record->SHAC,
458
+ 'badURL' => $result['URL'],
459
+ 'gsb' => 'goog-malware-shavar'
460
+ ), $dataForFile),
461
+ ));
 
462
  } else if($result['badList'] == 'googpub-phish-shavar'){
463
+ $this->addResult(array(
464
+ 'type' => 'file',
465
+ 'severity' => 1,
466
+ 'ignoreP' => $this->path . $file,
467
+ 'ignoreC' => md5_file($this->path . $file),
468
+ 'shortMsg' => "File contains suspected phishing URL: " . esc_html($this->path . $file),
469
+ 'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . esc_html($result['URL']),
470
+ 'data' => array_merge(array(
471
+ 'file' => $file,
472
+ 'shac' => $record->SHAC,
473
+ 'badURL' => $result['URL'],
474
+ 'gsb' => 'googpub-phish-shavar'
475
+ ), $dataForFile),
476
+ ));
 
477
  }
478
  }
479
  }
480
  wfUtils::endProcessingFile();
481
+
482
+ wordfence::status(4, 'info', "Finalizing malware scan results");
483
+ $hashesToCheck = array();
484
+ foreach ($this->results as $r) {
485
+ $hashesToCheck[] = $r['data']['shac'];
486
+ }
487
+
488
+ if (count($hashesToCheck) > 0) {
489
+ $safeFiles = $this->isSafeFile($hashesToCheck);
490
+ foreach ($this->results as $index => $value) {
491
+ if (in_array($value['data']['shac'], $safeFiles)) {
492
+ unset($this->results[$index]);
493
+ }
494
+ }
495
+ }
496
 
497
  return $this->results;
498
  }
513
  //We don't have a results for this file so append
514
  $this->results[] = $result;
515
  }
516
+
517
+ /**
518
+ * Queries the is_safe_file endpoint. If provided an array, it does a bulk check and returns an array containing the
519
+ * hashes that were marked as safe. If provided a string, it returns a boolean to indicate the safeness of the file.
520
+ *
521
+ * @param string|array $shac
522
+ * @return array|bool
523
+ */
524
+ private function isSafeFile($shac) {
525
  if(! $this->api){
526
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
527
  }
528
+
529
+ if (is_array($shac)) {
530
+ $result = $this->api->call('is_safe_file', array(), array('multipleSHAC' => json_encode($shac)));
531
+ if (isset($result['isSafe'])) {
532
+ return $result['isSafe'];
533
+ }
534
+ return array();
535
+ }
536
+
537
+ $result = $this->api->call('is_safe_file', array(), array('shac' => strtoupper($shac)));
538
  if(isset($result['isSafe']) && $result['isSafe'] == 1){
539
  return true;
540
  }
610
  * @property string $filename
611
  * @property string $filenameMD5
612
  * @property string $newMD5
613
+ * @property string $SHAC
614
  * @property string $stoppedOnSignature
615
  * @property string $stoppedOnPosition
616
+ * @property string $isSafeFile
617
  */
618
  class wordfenceMalwareScanFile {
619
  protected $_filename;
620
  protected $_filenameMD5;
621
  protected $_newMD5;
622
+ protected $_shac;
623
  protected $_stoppedOnSignature;
624
  protected $_stoppedOnPosition;
625
+ protected $_isSafeFile;
626
 
627
  protected static function getDB() {
628
  static $db = null;
639
 
640
  public static function files($limit = 500) {
641
  $db = self::getDB();
642
+ $result = $db->querySelect("SELECT filename, filenameMD5, HEX(newMD5) AS newMD5, HEX(SHAC) AS SHAC, stoppedOnSignature, stoppedOnPosition, isSafeFile FROM " . wfDB::networkPrefix() . "wfFileMods WHERE oldMD5 != newMD5 AND knownFile = 0 limit %d", $limit);
643
  $files = array();
644
  foreach ($result as $row) {
645
+ $files[] = new wordfenceMalwareScanFile($row['filename'], $row['filenameMD5'], $row['newMD5'], $row['SHAC'], $row['stoppedOnSignature'], $row['stoppedOnPosition'], $row['isSafeFile']);
646
  }
647
  return $files;
648
  }
649
 
650
+ public static function fileForPath($file) {
651
+ $db = self::getDB();
652
+ $row = $db->querySingleRec("SELECT filename, filenameMD5, HEX(newMD5) AS newMD5, HEX(SHAC) AS SHAC, stoppedOnSignature, stoppedOnPosition, isSafeFile FROM " . wfDB::networkPrefix() . "wfFileMods WHERE filename = '%s'", $file);
653
+ return new wordfenceMalwareScanFile($row['filename'], $row['filenameMD5'], $row['newMD5'], $row['SHAC'], $row['stoppedOnSignature'], $row['stoppedOnPosition'], $row['isSafeFile']);
654
+ }
655
+
656
+ public function __construct($filename, $filenameMD5, $newMD5, $shac, $stoppedOnSignature, $stoppedOnPosition, $isSafeFile) {
657
  $this->_filename = $filename;
658
  $this->_filenameMD5 = $filenameMD5;
659
  $this->_newMD5 = $newMD5;
660
+ $this->_shac = strtoupper($shac);
661
  $this->_stoppedOnSignature = $stoppedOnSignature;
662
  $this->_stoppedOnPosition = $stoppedOnPosition;
663
+ $this->_isSafeFile = $isSafeFile;
664
  }
665
 
666
  public function __get($key) {
671
  return $this->_filenameMD5;
672
  case 'newMD5':
673
  return $this->_newMD5;
674
+ case 'SHAC':
675
+ return $this->_shac;
676
  case 'stoppedOnSignature':
677
  return $this->_stoppedOnSignature;
678
  case 'stoppedOnPosition':
679
  return $this->_stoppedOnPosition;
680
+ case 'isSafeFile':
681
+ return $this->_isSafeFile;
682
  }
683
  }
684
 
697
  $db = self::getDB();
698
  $db->queryWrite("UPDATE " . wfDB::networkPrefix() . "wfFileMods SET stoppedOnSignature = '%s', stoppedOnPosition = %d WHERE filenameMD5 = '%s'", $this->stoppedOnSignature, $this->stoppedOnPosition, $this->filenameMD5);
699
  }
700
+
701
+ public function markSafe() {
702
+ $db = self::getDB();
703
+ $db->queryWrite("UPDATE " . wfDB::networkPrefix() . "wfFileMods SET isSafeFile = '1' WHERE filenameMD5 = '%s'", $this->filenameMD5);
704
+ $this->isSafeFile = '1';
705
+ }
706
+
707
+ public function markUnsafe() {
708
+ $db = self::getDB();
709
+ $db->queryWrite("UPDATE " . wfDB::networkPrefix() . "wfFileMods SET isSafeFile = '0' WHERE filenameMD5 = '%s'", $this->filenameMD5);
710
+ $this->isSafeFile = '0';
711
+ }
712
  }
713
 
714
  ?>
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.7.3
6
- Stable tag: 6.3.5
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
@@ -42,8 +42,7 @@ Wordfence Security is Multi-Site compatible and includes Cellphone Sign-in which
42
  * Checks the strength of all user and admin passwords to enhance login security.
43
  * Includes login security to lock out brute force hacks and to stop WordPress from revealing info that will compromise WordPress security.
44
 
45
- = Security Scanning =
46
- * Scans for the HeartBleed vulnerability - included in the free scan for all users.
47
  * Scans core files, themes and plugins against WordPress.org repository versions to check their integrity. Verify security of your source.
48
  * See how files have changed. Optionally repair changed files that are security threats.
49
  * Scans for signatures of over 44,000 known malware variants that are known WordPress security threats.
@@ -151,16 +150,29 @@ Designed for every skill level, [The WordPress Security Learning Center](https:/
151
 
152
  Secure your website with Wordfence.
153
 
154
- 1. The dashboard of Wordfene Security where you can get a quick overview of any important notifications and attacks your site has been protected from.
155
- 2. The Web Application Firewall of Wordfence Security where you can configure your protection level and view which vulnerabilities you're protected from.
156
- 3. The scan page of Wordfence Security where you can see a summary, manage security issues and do a manual security scan.
157
- 4. The Live Traffic view of Wordfence Security where you can see real-time activity on your site.
158
- 5. The "Blocked IPs" page where you can manage blocked IPs, locked out IPs and see recently throttled IPs that violated security rules.
159
- 6. The basic view of Wordfence Security options. There is very little to configure other than your alert email address and security level.
160
- 7. If you're technically minded, this is the under-the-hood view of Wordfence Security options where you can fine-tune your security settings.
161
 
162
  == Changelog ==
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  = 6.3.5 =
165
  * Improvement: Sites can now specify a list of trusted proxies when using X-Forwarded-For for IP resolution.
166
  * Improvement: Added options to customize which dashboard notifications are shown.
@@ -737,7 +749,7 @@ Secure your website with Wordfence.
737
  * Fixed issue that caused litespeed users to receive multiple warnings about the noabort issue.
738
  * Added detection for 5 new malware variants. Thanks to Dave M. and others for the samples. Keep them coming folks!
739
  * Updated Wordfence server API to version 2.12.
740
- * Added facility at bottom of Wordfence options page to send a test email from your WordPress sytem to check if email sending is working.
741
  * Suppress LOCK_EX flock() warnings in falcon engine that were being generated by sites that use NFS and don't support flock() or reliable file locking.
742
  * Updated to the October 2014 version of the Geo IP country DB. (newest edition)
743
 
@@ -1486,7 +1498,7 @@ Secure your website with Wordfence.
1486
  * Added ability for admin's to unlock login and unblock their IP addresses if they're accidentally locked out by the firewall or login security. Uses two security tokens to prevent abuse.
1487
  * Admins can now also disable firewall and login security from the unlock-me email, just in case of emergency.
1488
  * Made advanced security options visible so you know they exist.
1489
- * Fixed dns_get_record() function not existing bug on Windows sytems pre PHP 5.3.0. Was causing scans to hang.
1490
  * Increased login lockout defaults to be much higher which still protects against brute force hacks.
1491
  * Removed CURLOPT_MAXREDIRS in curl to avoid safe mode warnings.
1492
  * Fixed ability to view and diff files on blogs installed in subdirectories.
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.7.3
6
+ Stable tag: 6.3.6
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
42
  * Checks the strength of all user and admin passwords to enhance login security.
43
  * Includes login security to lock out brute force hacks and to stop WordPress from revealing info that will compromise WordPress security.
44
 
45
+ = Security Scanning =
 
46
  * Scans core files, themes and plugins against WordPress.org repository versions to check their integrity. Verify security of your source.
47
  * See how files have changed. Optionally repair changed files that are security threats.
48
  * Scans for signatures of over 44,000 known malware variants that are known WordPress security threats.
150
 
151
  Secure your website with Wordfence.
152
 
153
+ 1. The dashboard gives you an overview of your site's security including notifications, attack statistics and Wordfence feature status.
154
+ 2. The Web Application Firewall protects your site from common types of attacks and known security vulnerabilities.
155
+ 3. The Wordfence Malware Scanner lets you know if your site has been compromised and alerts you to other security issues that need to be addressed.
156
+ 4. The Wordfence Security Live Traffic view shows you real-time activity on your site including bot traffic and exploit attempts.
157
+ 5. Block IPs that are known to be malicious, manage IPs that have been locked out and see recently throttled IPs that violated security rules.
158
+ 6. The Wordfence Options page is where you manage high-level Wordfence features and upgrade your license to Premium.
159
+ 7. The Advanced Options page allows technically-minded users fine-tune their security settings.
160
 
161
  == Changelog ==
162
 
163
+ = 6.3.6 =
164
+ * Improvement: Optimized the malware signature scan to reduce memory usage.
165
+ * Improvement: Optimized the overall scan to make fewer network calls.
166
+ * Improvement: Running an update now automatically dismisses the corresponding scan issue if present.
167
+ * Improvement: Added a time limit to the live activity status so only current messages are shown.
168
+ * Improvement: WAF configuration files are now excluded by default from the recently modified files list in the activity report.
169
+ * Improvement: Background pausing for live activity and traffic may now be disabled.
170
+ * Improvement: Added additional WAF support to allow us to more easily address false positives.
171
+ * Improvement: Blocking pages presented by Wordfence now indicate the source and contain information to help diagnose caching problems.
172
+ * Fix: All external URLs in the tour are now https.
173
+ * Fix: Corrected a typo in the unlock email template.
174
+ * Fix: Fixed the target of a label on the options page.
175
+
176
  = 6.3.5 =
177
  * Improvement: Sites can now specify a list of trusted proxies when using X-Forwarded-For for IP resolution.
178
  * Improvement: Added options to customize which dashboard notifications are shown.
749
  * Fixed issue that caused litespeed users to receive multiple warnings about the noabort issue.
750
  * Added detection for 5 new malware variants. Thanks to Dave M. and others for the samples. Keep them coming folks!
751
  * Updated Wordfence server API to version 2.12.
752
+ * Added facility at bottom of Wordfence options page to send a test email from your WordPress system to check if email sending is working.
753
  * Suppress LOCK_EX flock() warnings in falcon engine that were being generated by sites that use NFS and don't support flock() or reliable file locking.
754
  * Updated to the October 2014 version of the Geo IP country DB. (newest edition)
755
 
1498
  * Added ability for admin's to unlock login and unblock their IP addresses if they're accidentally locked out by the firewall or login security. Uses two security tokens to prevent abuse.
1499
  * Admins can now also disable firewall and login security from the unlock-me email, just in case of emergency.
1500
  * Made advanced security options visible so you know they exist.
1501
+ * Fixed dns_get_record() function not existing bug on Windows systems pre PHP 5.3.0. Was causing scans to hang.
1502
  * Increased login lockout defaults to be much higher which still protects against brute force hacks.
1503
  * Removed CURLOPT_MAXREDIRS in curl to avoid safe mode warnings.
1504
  * Fixed ability to view and diff files on blogs installed in subdirectories.
vendor/wordfence/wf-waf/src/init.php CHANGED
@@ -4,7 +4,7 @@ define('WFWAF_VERSION', '1.0.3');
4
  define('WFWAF_PATH', dirname(__FILE__) . '/');
5
  define('WFWAF_LIB_PATH', WFWAF_PATH . 'lib/');
6
  define('WFWAF_VIEW_PATH', WFWAF_PATH . 'views/');
7
- define('WFWAF_API_URL_SEC', 'https://noc4.wordfence.com/v1.5/');
8
  if (!defined('WFWAF_DEBUG')) {
9
  define('WFWAF_DEBUG', false);
10
  }
4
  define('WFWAF_PATH', dirname(__FILE__) . '/');
5
  define('WFWAF_LIB_PATH', WFWAF_PATH . 'lib/');
6
  define('WFWAF_VIEW_PATH', WFWAF_PATH . 'views/');
7
+ define('WFWAF_API_URL_SEC', 'https://noc4.wordfence.com/v1.6/');
8
  if (!defined('WFWAF_DEBUG')) {
9
  define('WFWAF_DEBUG', false);
10
  }
vendor/wordfence/wf-waf/src/lib/parser/parser.php CHANGED
@@ -367,6 +367,14 @@ class wfWAFRuleParser extends wfWAFBaseParser {
367
  } else if ($token->getValue() === 'rules') {
368
  $rules = $this->expectLiteral();
369
  $urlParam->setRules($rules);
 
 
 
 
 
 
 
 
370
  }
371
 
372
  break;
@@ -601,6 +609,14 @@ class wfWAFRuleParserURLParam {
601
  * @var null
602
  */
603
  private $rules;
 
 
 
 
 
 
 
 
604
 
605
  /**
606
  * @param string $param
@@ -617,24 +633,28 @@ class wfWAFRuleParserURLParam {
617
  * @param string $param
618
  * @param null $rules
619
  */
620
- public function __construct($url = null, $param = null, $rules = null) {
621
  $this->url = $url;
622
  $this->param = $param;
623
  $this->rules = $rules;
 
 
624
  }
625
 
626
  /**
627
  * Return format:
628
- * blacklistParam(url='/\/uploadify\.php$/i', param=request.fileNames.Filedata, rules=[3, 14])
629
  *
630
  * @param string $action
631
  * @return string
632
  */
633
  public function renderRule($action) {
634
- return sprintf('%s(url=%s, param=%s%s)', $action,
635
  wfWAFRule::exportString($this->getUrl()),
636
  $this->renderParam($this->getParam()),
637
- $this->getRules() ? ', rules=[' . join(', ', array_map('intval', $this->getRules())) . ']' : '');
 
 
638
  }
639
 
640
  /**
@@ -700,6 +720,34 @@ class wfWAFRuleParserURLParam {
700
  public function setRules($rules) {
701
  $this->rules = $rules;
702
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  }
704
 
705
  class wfWAFRuleParserSyntaxError extends wfWAFParserSyntaxError {
367
  } else if ($token->getValue() === 'rules') {
368
  $rules = $this->expectLiteral();
369
  $urlParam->setRules($rules);
370
+ } else if ($token->getValue() === 'conditional') {
371
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
372
+ $conditional = $this->parseConditional();
373
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
374
+ $urlParam->setConditional($conditional);
375
+ } else if ($token->getValue() === 'minVersion') {
376
+ $minVersion = $this->expectLiteral();
377
+ $urlParam->setMinVersion($minVersion);
378
  }
379
 
380
  break;
609
  * @var null
610
  */
611
  private $rules;
612
+ /**
613
+ * @var null
614
+ */
615
+ private $conditional;
616
+ /**
617
+ * @var float
618
+ */
619
+ private $minVersion;
620
 
621
  /**
622
  * @param string $param
633
  * @param string $param
634
  * @param null $rules
635
  */
636
+ public function __construct($url = null, $param = null, $rules = null, $conditional = null, $minVersion = null) {
637
  $this->url = $url;
638
  $this->param = $param;
639
  $this->rules = $rules;
640
+ $this->conditional = $conditional;
641
+ $this->minVersion = $minVersion;
642
  }
643
 
644
  /**
645
  * Return format:
646
+ * blacklistParam(url='/\/uploadify\.php$/i', param=request.fileNames.Filedata, rules=[3, 14], conditional=(match('1', request.body.field)))
647
  *
648
  * @param string $action
649
  * @return string
650
  */
651
  public function renderRule($action) {
652
+ return sprintf('%s(url=%s, param=%s%s%s)', $action,
653
  wfWAFRule::exportString($this->getUrl()),
654
  $this->renderParam($this->getParam()),
655
+ $this->getRules() ? ', rules=[' . join(', ', array_map('intval', $this->getRules())) . ']' : '',
656
+ $this->getConditional() ? ', conditional=(' . $this->getConditional()->renderRule() . ')' : '');
657
+ //minVersion not included in re-rendering
658
  }
659
 
660
  /**
720
  public function setRules($rules) {
721
  $this->rules = $rules;
722
  }
723
+
724
+ /**
725
+ * @return null
726
+ */
727
+ public function getConditional() {
728
+ return $this->conditional;
729
+ }
730
+
731
+ /**
732
+ * @param null $conditional
733
+ */
734
+ public function setConditional($conditional) {
735
+ $this->conditional = $conditional;
736
+ }
737
+
738
+ /**
739
+ * @return float|null
740
+ */
741
+ public function getMinVersion() {
742
+ return $this->minVersion;
743
+ }
744
+
745
+ /**
746
+ * @param float $minVersion
747
+ */
748
+ public function setMinVersion($minVersion) {
749
+ $this->minVersion = $minVersion;
750
+ }
751
  }
752
 
753
  class wfWAFRuleParserSyntaxError extends wfWAFParserSyntaxError {
vendor/wordfence/wf-waf/src/lib/request.php CHANGED
@@ -443,7 +443,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
443
  * @param string|null $baseKey The base key used when recursing.
444
  * @return string
445
  */
446
- public function getCookieString($cookies = null, $baseKey = null) {
447
  if ($cookies == null) {
448
  $cookies = $this->getCookies();
449
  }
@@ -465,7 +465,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
465
  $cookieString .= $nestedCookies;
466
  }
467
  else {
468
- if (strpos($resolvedName, 'wordpress_') === 0) {
469
  $cookieValue = '<redacted>';
470
  }
471
 
@@ -548,7 +548,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
548
  * @return string
549
  */
550
  public function highlightFailedParams($failedParams = array(), $highlightParamFormat = '[param]%s[/param]',
551
- $highlightMatchFormat = '[match]%s[/match]') {
552
  $highlights = array();
553
 
554
  // Cap at 47.5kb
@@ -622,7 +622,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
622
  switch (wfWAFUtils::strtolower($header)) {
623
  case 'cookie':
624
  // TODO: Hook up highlights to cookies
625
- $request .= 'Cookie: ' . trim($this->getCookieString()) . "\n";
626
  break;
627
 
628
  case 'host':
@@ -632,7 +632,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
632
  case 'authorization':
633
  $hasAuth = true;
634
  if ($auth) {
635
- $request .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\n";
636
  }
637
  break;
638
 
@@ -644,7 +644,7 @@ class wfWAFRequest implements wfWAFRequestInterface {
644
  }
645
 
646
  if (!$hasAuth && $auth) {
647
- $request .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\n";
648
  }
649
 
650
  $body = $this->getBody();
443
  * @param string|null $baseKey The base key used when recursing.
444
  * @return string
445
  */
446
+ public function getCookieString($cookies = null, $baseKey = null, $preventRedaction = false) {
447
  if ($cookies == null) {
448
  $cookies = $this->getCookies();
449
  }
465
  $cookieString .= $nestedCookies;
466
  }
467
  else {
468
+ if (strpos($resolvedName, 'wordpress_') === 0 && !$preventRedaction) {
469
  $cookieValue = '<redacted>';
470
  }
471
 
548
  * @return string
549
  */
550
  public function highlightFailedParams($failedParams = array(), $highlightParamFormat = '[param]%s[/param]',
551
+ $highlightMatchFormat = '[match]%s[/match]', $preventRedaction = false) {
552
  $highlights = array();
553
 
554
  // Cap at 47.5kb
622
  switch (wfWAFUtils::strtolower($header)) {
623
  case 'cookie':
624
  // TODO: Hook up highlights to cookies
625
+ $request .= 'Cookie: ' . trim($this->getCookieString(null, null, $preventRedaction)) . "\n";
626
  break;
627
 
628
  case 'host':
632
  case 'authorization':
633
  $hasAuth = true;
634
  if ($auth) {
635
+ $request .= 'Authorization: Basic ' . ($preventRedaction ? base64_encode($auth['user'] . ':' . $auth['password']) : '<redacted>') . "\n";
636
  }
637
  break;
638
 
644
  }
645
 
646
  if (!$hasAuth && $auth) {
647
+ $request .= 'Authorization: Basic ' . ($preventRedaction ? base64_encode($auth['user'] . ':' . $auth['password']) : '<redacted>') . "\n";
648
  }
649
 
650
  $body = $this->getBody();
vendor/wordfence/wf-waf/src/lib/storage/file.php CHANGED
@@ -3,6 +3,7 @@
3
  class wfWAFStorageFile implements wfWAFStorageInterface {
4
 
5
  const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n";
 
6
  const IP_BLOCK_RECORD_SIZE = 24;
7
 
8
  public static function atomicFilePutContents($file, $content, $prefix = 'config') {
@@ -349,7 +350,7 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
349
 
350
  $files = array(
351
  array($this->getIPCacheFile(), 'ipCacheFileHandle', self::LOG_FILE_HEADER),
352
- array($this->getConfigFile(), 'configFileHandle', self::LOG_FILE_HEADER . serialize($this->getDefaultConfiguration())),
353
  );
354
  foreach ($files as $file) {
355
  list($filePath, $fileHandle, $defaultContents) = $file;
@@ -506,6 +507,9 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
506
  while (!feof($this->configFileHandle)) {
507
  $serializedData .= fread($this->configFileHandle, 1024);
508
  }
 
 
 
509
  $this->data = @unserialize($serializedData);
510
 
511
  if ($this->data === false) {
@@ -533,9 +537,9 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
533
  if (WFWAF_IS_WINDOWS) {
534
  self::lock($this->configFileHandle, LOCK_UN);
535
  fclose($this->configFileHandle);
536
- file_put_contents($this->getConfigFile(), self::LOG_FILE_HEADER . serialize($this->data), LOCK_EX);
537
  } else {
538
- wfWAFStorageFile::atomicFilePutContents($this->getConfigFile(), self::LOG_FILE_HEADER . serialize($this->data));
539
  }
540
 
541
  if (WFWAF_IS_WINDOWS) {
3
  class wfWAFStorageFile implements wfWAFStorageInterface {
4
 
5
  const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n";
6
+ const LOG_INFO_HEADER = "******************************************************************\nThis file is used by the Wordfence Web Application Firewall. Read \nmore at https://docs.wordfence.com/en/Web_Application_Firewall_FAQ\n******************************************************************\n";
7
  const IP_BLOCK_RECORD_SIZE = 24;
8
 
9
  public static function atomicFilePutContents($file, $content, $prefix = 'config') {
350
 
351
  $files = array(
352
  array($this->getIPCacheFile(), 'ipCacheFileHandle', self::LOG_FILE_HEADER),
353
+ array($this->getConfigFile(), 'configFileHandle', self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->getDefaultConfiguration())),
354
  );
355
  foreach ($files as $file) {
356
  list($filePath, $fileHandle, $defaultContents) = $file;
507
  while (!feof($this->configFileHandle)) {
508
  $serializedData .= fread($this->configFileHandle, 1024);
509
  }
510
+ if (wfWAFUtils::substr($serializedData, 0, 1) == '*') {
511
+ $serializedData = wfWAFUtils::substr($serializedData, wfWAFUtils::strlen(self::LOG_INFO_HEADER));
512
+ }
513
  $this->data = @unserialize($serializedData);
514
 
515
  if ($this->data === false) {
537
  if (WFWAF_IS_WINDOWS) {
538
  self::lock($this->configFileHandle, LOCK_UN);
539
  fclose($this->configFileHandle);
540
+ file_put_contents($this->getConfigFile(), self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->data), LOCK_EX);
541
  } else {
542
+ wfWAFStorageFile::atomicFilePutContents($this->getConfigFile(), self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->data));
543
  }
544
 
545
  if (WFWAF_IS_WINDOWS) {
vendor/wordfence/wf-waf/src/lib/utils.php CHANGED
@@ -767,4 +767,25 @@ class wfWAFUtils {
767
  }
768
  return $data;
769
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  }
767
  }
768
  return $data;
769
  }
770
+
771
+ /**
772
+ * Returns the current timestamp, adjusted as needed to get close to what we consider a true timestamp. We use this
773
+ * because a significant number of servers are using a drastically incorrect time.
774
+ *
775
+ * @return int
776
+ */
777
+ public static function normalizedTime() {
778
+ $offset = 0;
779
+ try {
780
+ $offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_ntp', false);
781
+ if ($offset === false) {
782
+ $offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_wf', false);
783
+ if ($offset === false) { $offset = 0; }
784
+ }
785
+ }
786
+ catch (Exception $e) {
787
+ //Ignore
788
+ }
789
+ return time() + $offset;
790
+ }
791
  }
vendor/wordfence/wf-waf/src/lib/waf.php CHANGED
@@ -571,17 +571,26 @@ PHP
571
  if (isset($rules[$key]) && is_array($rules[$key])) {
572
  /** @var wfWAFRuleParserURLParam $urlParam */
573
  foreach ($rules[$key] as $urlParam) {
574
- if ($urlParam->getRules()) {
575
- $url = array(
576
- 'url' => $urlParam->getUrl(),
577
- 'rules' => $urlParam->getRules(),
578
- );
579
- } else {
580
- $url = $urlParam->getUrl();
 
 
 
 
 
 
 
 
 
 
 
 
581
  }
582
-
583
- $exportedCode .= sprintf("\$this->{$key}[%s][] = %s;\n", var_export($urlParam->getParam(), true),
584
- var_export($url, true));
585
  }
586
  $exportedCode .= "\n";
587
  }
@@ -969,6 +978,9 @@ HTML
969
  if (!in_array($ruleID, $urlRegex['rules'])) {
970
  continue;
971
  }
 
 
 
972
  $urlRegex = $urlRegex['url'];
973
  }
974
  if (preg_match($urlRegex, $urlPath)) {
@@ -1103,6 +1115,9 @@ HTML
1103
  if (!in_array($ruleID, $urlRegex['rules'])) {
1104
  continue;
1105
  }
 
 
 
1106
  $urlRegex = $urlRegex['url'];
1107
  }
1108
  if (preg_match($urlRegex, $urlPath)) {
571
  if (isset($rules[$key]) && is_array($rules[$key])) {
572
  /** @var wfWAFRuleParserURLParam $urlParam */
573
  foreach ($rules[$key] as $urlParam) {
574
+ if ($urlParam->getConditional()) {
575
+
576
+ $exportedCode .= sprintf("\$this->{$key}[%s][] = array(\n%s => %s,\n%s => %s,\n%s => %s\n);\n", var_export($urlParam->getParam(), true),
577
+ var_export('url', true), var_export($urlParam->getUrl(), true),
578
+ var_export('rules', true), var_export($urlParam->getRules(), true),
579
+ var_export('conditional', true), $urlParam->getConditional()->render());
580
+ }
581
+ else {
582
+ if ($urlParam->getRules()) {
583
+ $url = array(
584
+ 'url' => $urlParam->getUrl(),
585
+ 'rules' => $urlParam->getRules(),
586
+ );
587
+ } else {
588
+ $url = $urlParam->getUrl();
589
+ }
590
+
591
+ $exportedCode .= sprintf("\$this->{$key}[%s][] = %s;\n", var_export($urlParam->getParam(), true),
592
+ var_export($url, true));
593
  }
 
 
 
594
  }
595
  $exportedCode .= "\n";
596
  }
978
  if (!in_array($ruleID, $urlRegex['rules'])) {
979
  continue;
980
  }
981
+ if (isset($urlRegex['conditional']) && !$urlRegex['conditional']->evaluate()) {
982
+ continue;
983
+ }
984
  $urlRegex = $urlRegex['url'];
985
  }
986
  if (preg_match($urlRegex, $urlPath)) {
1115
  if (!in_array($ruleID, $urlRegex['rules'])) {
1116
  continue;
1117
  }
1118
+ if (isset($urlRegex['conditional']) && !$urlRegex['conditional']->evaluate()) {
1119
+ continue;
1120
+ }
1121
  $urlRegex = $urlRegex['url'];
1122
  }
1123
  if (preg_match($urlRegex, $urlPath)) {
vendor/wordfence/wf-waf/src/views/403-blacklist.php CHANGED
@@ -209,5 +209,7 @@ $payload = "-----BEGIN REPORT-----\n" . implode("\n", str_split($message, 60)) .
209
 
210
  <p><a href="#" id="reportButton" class="btn disabled" target="_blank">Report Problem</a></p>
211
 
 
 
212
  </body>
213
  </html>
209
 
210
  <p><a href="#" id="reportButton" class="btn disabled" target="_blank">Report Problem</a></p>
211
 
212
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
213
+
214
  </body>
215
  </html>
vendor/wordfence/wf-waf/src/views/403-roadblock.php CHANGED
@@ -113,5 +113,7 @@ foreach ($waf->getFailedRules() as $paramKey => $categories) {
113
 
114
  <?php endif ?>
115
 
 
 
116
  </body>
117
  </html>
113
 
114
  <?php endif ?>
115
 
116
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
117
+
118
  </body>
119
  </html>
vendor/wordfence/wf-waf/src/views/403.php CHANGED
@@ -10,5 +10,7 @@
10
 
11
  <p>A potentially unsafe operation has been detected in your request to this site.</p>
12
 
 
 
13
  </body>
14
  </html>
10
 
11
  <p>A potentially unsafe operation has been detected in your request to this site.</p>
12
 
13
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
14
+
15
  </body>
16
  </html>
vendor/wordfence/wf-waf/src/views/503-lockout.php CHANGED
@@ -28,7 +28,7 @@ if (!empty($homeURL) && !empty($nonce)) : ?>
28
  </form>
29
  <?php endif; ?>
30
  </p>
31
- <p style="font-style: italic;">Generated by Wordfence.</p>
32
  </body>
33
  </html>
34
  <?php exit(); ?>
28
  </form>
29
  <?php endif; ?>
30
  </p>
31
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
32
  </body>
33
  </html>
34
  <?php exit(); ?>
vendor/wordfence/wf-waf/src/views/503.php CHANGED
@@ -31,5 +31,5 @@ If you are a site administrator and have been accidentally locked out, please en
31
  <?php endif; ?>
32
  <br /><br />
33
 
34
- <address>This response was generated by Wordfence.</address>
35
  </body></html>
31
  <?php endif; ?>
32
  <br /><br />
33
 
34
+ <p style="color: #999999;margin-top: 2rem;"><em>Generated by Wordfence at <?php echo gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime()); ?>.<br>Your computer's time: <script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
35
  </body></html>
views/reports/activity-report-email-inline.php CHANGED
@@ -331,6 +331,8 @@ h6 a:visited { color: purple !important; }
331
  </tbody>
332
  </table>
333
 
 
 
334
  <?php wfHelperString::cycle(); ?>
335
 
336
  <h2 style="font-size: 20px; vertical-align: baseline; clear: both; color: #222 !important; margin: 20px 0 4px; padding: 0; border: 0;">Updates Needed</h2>
331
  </tbody>
332
  </table>
333
 
334
+ <div style="font-size: 12px; font-style: italic; vertical-align: baseline; clear: both; margin: 8px 0 4px; padding: 0; border: 0;">This list may include WordPress core/plugin/theme updates, error logs, cache files, and other normal changes.</div>
335
+
336
  <?php wfHelperString::cycle(); ?>
337
 
338
  <h2 style="font-size: 20px; vertical-align: baseline; clear: both; color: #222 !important; margin: 20px 0 4px; padding: 0; border: 0;">Updates Needed</h2>
wordfence.php CHANGED
@@ -4,14 +4,14 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
- Version: 6.3.5
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
- define('WORDFENCE_VERSION', '6.3.5');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17
 
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
+ Version: 6.3.6
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
+ define('WORDFENCE_VERSION', '6.3.6');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17