Wordfence Security – Firewall & Malware Scan - Version 3.0.3

Version Description

  • Issue that caused all core files to show as missing has been fixed.
  • We now handle all API server errors gracefully using exceptions.
  • If your installation didn't activate correctly you now get a friendly message.
  • Removed unused menu_config.php code.
  • The 503 message now tells you why your access to the site has been limited so that admin's can tune firewall rules better.
  • We no longer reuse the WordPress wpdb handle because we get better stability with our own connection.
Download this release

Release Info

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

Code changes from version 3.0.2 to 3.0.3

js/admin.js CHANGED
@@ -327,25 +327,6 @@ window['wordfenceAdmin'] = {
327
  }
328
  }, false, false);
329
  },
330
- activateWF: function(key){
331
- jQuery('.wfAjax24').show();
332
- this.ajax('wordfence_activate', {
333
- key: jQuery('#wordfenceKey').val(),
334
- email: jQuery('#email').val()
335
- },
336
- function(res){
337
- jQuery('.wfAjax24').hide();
338
- if(res.ok){
339
- window.location = "admin.php?page=Wordfence&wfAct=" + Math.floor(Math.random()*999999999);
340
- return;
341
- } else if(res.errorAlert){
342
- jQuery.colorbox({ width: '400px', html:
343
- "<h3>An error occurred:</h3><p>" + res.errorAlert + "</p>"
344
- });
345
- }
346
-
347
- });
348
- },
349
  killScan: function(){
350
  var self = this;
351
  this.ajax('wordfence_killScan', {}, function(res){
327
  }
328
  }, false, false);
329
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  killScan: function(){
331
  var self = this;
332
  this.ajax('wordfence_killScan', {}, function(res){
lib/menu_config.php DELETED
@@ -1,27 +0,0 @@
1
- <div class="wrap wordfence">
2
- <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2>Welcome to Wordfence</h2>
3
- <table class="form-table">
4
- <tr><th><nobr>Enter your Wordfence API key:</nobr></th><td><input type="text" id="wordfenceKey" size="30" value="" />&nbsp;(<a href="http://wordfence.com/signup-step2/" target="_blank">click here to get a free API key</a>)</td></tr>
5
- <tr><th><nobr>Enter an email to send alerts to:</nobr></th><td><input type="text" id="email" size="30" value="<?php echo htmlspecialchars(get_option('admin_email')); ?>" /></td></tr>
6
- <tr><td colspan="2">
7
- <table border="0" cellpadding="0" cellspacing="0"><tr><td>
8
- <input type="button" name="submit" id="submit" class="button-primary" value="Save Changes and Activate Wordfence" onclick="wordfenceAdmin.activateWF(); return false;" />
9
- </td><td>
10
- <div class="wfAjax24"></div>
11
- </td></tr></table>
12
-
13
- </td></tr>
14
- </table>
15
-
16
- </div>
17
- <script type="text/x-jquery-template" id="wfActivateError">
18
- <div>
19
- <h3>Wordfence Activation Failed</h3>
20
- <p>
21
- We could not activate your Wordfence. The error was:
22
- <br /><br />
23
- ${err}
24
- <br /><br />
25
- </p>
26
- </div>
27
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/wf503.php CHANGED
@@ -1,9 +1,18 @@
1
  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
2
  <html><head>
3
- <title>503 Service Unavailable</title>
4
  </head><body>
5
- <h1>Service Unavailable</h1>
6
  <p>Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)</p>
 
 
 
 
 
 
 
 
 
7
  <hr>
8
  <br /><br />
9
  <?php require('wfUnlockMsg.php'); ?>
1
  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
2
  <html><head>
3
+ <title>Wordfence has limited your access</title>
4
  </head><body>
5
+ <h1>Wordfence has limited your access to this site</h1>
6
  <p>Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)</p>
7
+ <p>Reason: <span style="color: #F00;"><?php echo $reason; ?></span></p>
8
+ <p><b>Important note for site admins: </b>If you are the administrator of this website note that your access has been limited because you broke one of the Wordfence firewall rules.
9
+ The reason you access was limited is: <b>"<?php echo $reason; ?>"</b>. If this is a false positive, meaning that your access to your own site has been limited incorrectly, then you
10
+ will need to regain access to your site, go to the Wordfence "options" page, go to the section for Firewall Rules and disable the rule that caused you to be blocked. For example,
11
+ if you were blocked because it was detected that you are a fake Google crawler, then disable the rule that blocks fake google crawlers. Or if you were blocked because you
12
+ were accessing your site too quickly, then increase the number of accesses allowed per minute. If you're still having trouble, then simply disable the Wordfence firwall and you will
13
+ still benefit from the other security features that Wordfence provides. You will find instructions below on how to regain access if you are a site administrator.
14
+ </p>
15
+
16
  <hr>
17
  <br /><br />
18
  <?php require('wfUnlockMsg.php'); ?>
lib/wfAPI.php CHANGED
@@ -2,40 +2,33 @@
2
  require_once('wordfenceConstants.php');
3
  require_once('wordfenceClass.php');
4
  class wfAPI {
5
- public $errorMsg = false;
6
- public $lastURLError = '';
7
  public $lastHTTPStatus = '';
8
  public $lastCurlErrorNo = '';
9
  private $curlDataWritten = 0;
10
  private $curlContent = 0;
11
  private $APIKey = '';
12
  private $wordpressVersion = '';
 
13
  public function __construct($apiKey, $wordpressVersion){
14
  $this->APIKey = $apiKey;
15
  $this->wordpressVersion = $wordpressVersion;
16
  }
17
  public function call($action, $getParams = array(), $postParams = array()){
18
- $this->errorMsg = false;
19
  $json = $this->getURL($this->getAPIURL() . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&' . http_build_query(
20
  array_merge(
21
  array('action' => $action),
22
  $getParams
23
  )), $postParams);
24
  if(! $json){
25
- if($this->lastHTTPStatus == '502' || $this->lastCurlErrorNo == 7){
26
- $this->errorMsg = "Wordfence is currently down for maintenance. Please try again later.";
27
- } else {
28
- $this->errorMsg = "We could not fetch data from the API when calling '$action': " . $this->lastURLError;
29
- }
30
- return false;
31
  }
32
 
33
  $dat = json_decode($json, true);
34
  if(! is_array($dat)){
35
- $this->errorMsg = "We could not understand the Wordfence API response when calling '$action'.";
36
  }
37
- if(empty($dat['errorMsg']) === false){
38
- $this->errorMsg = $dat['errorMsg'];
39
  }
40
  return $dat;
41
  }
@@ -48,7 +41,6 @@ class wfAPI {
48
  }
49
  }
50
  protected function getURL($url, $postParams = array()){
51
- $this->lastURLError = '';
52
  if(function_exists('curl_init')){
53
  $this->curlDataWritten = 0;
54
  $this->curlContent = "";
@@ -70,17 +62,15 @@ class wfAPI {
70
  curl_close($curl);
71
  return $this->curlContent;
72
  } else {
73
- $this->lastURLError = "HTTP status $httpStatus from server. " . curl_error($curl);
74
- $this->lastHTTPStatus = $httpStatus;
75
  curl_close($curl);
76
- return false;
77
  }
78
  } else {
79
  $data = $this->fileGet($url, $postParams);
80
  if($data === false){
81
  $err = error_get_last();
82
- $this->lastURLError = $err;
83
- return false;
84
  }
85
  return $data;
86
  }
@@ -109,7 +99,6 @@ class wfAPI {
109
  return @file_get_contents($url, false, $context, -1);
110
  }
111
  public function binCall($func, $postData){
112
- $this->errorMsg = false;
113
  $url = $this->getAPIURL() . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&action=' . $func;
114
  if(function_exists('curl_init')){
115
  $curl = curl_init($url);
@@ -127,19 +116,22 @@ class wfAPI {
127
  }
128
  $data = curl_exec($curl);
129
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
 
 
 
 
 
130
  } else {
131
  $data = $this->fileGet($url, $postData);
132
  if($data === false){
133
- $this->errorMsg = error_get_last();
134
- return false;
135
  }
136
  $httpStatus = '200';
137
  }
138
  if(preg_match('/\{.*errorMsg/', $data)){
139
  $jdat = @json_decode($data, true);
140
  if(is_array($jdat) && $jdat['errorMsg']){
141
- $this->errorMsg = $jdat['errorMsg'];
142
- return false;
143
  }
144
  }
145
  return array('code' => $httpStatus, 'data' => $data);
2
  require_once('wordfenceConstants.php');
3
  require_once('wordfenceClass.php');
4
  class wfAPI {
 
 
5
  public $lastHTTPStatus = '';
6
  public $lastCurlErrorNo = '';
7
  private $curlDataWritten = 0;
8
  private $curlContent = 0;
9
  private $APIKey = '';
10
  private $wordpressVersion = '';
11
+ private static $maintMsg = "The Wordfence scanning server may be down for maintenance.";
12
  public function __construct($apiKey, $wordpressVersion){
13
  $this->APIKey = $apiKey;
14
  $this->wordpressVersion = $wordpressVersion;
15
  }
16
  public function call($action, $getParams = array(), $postParams = array()){
 
17
  $json = $this->getURL($this->getAPIURL() . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&' . http_build_query(
18
  array_merge(
19
  array('action' => $action),
20
  $getParams
21
  )), $postParams);
22
  if(! $json){
23
+ throw new Exception(self::$maintMsg);
 
 
 
 
 
24
  }
25
 
26
  $dat = json_decode($json, true);
27
  if(! is_array($dat)){
28
+ throw new Exception("We could not understand the Wordfence API response when calling '$action'.");
29
  }
30
+ if(is_array($dat) && isset($dat['errorMsg'])){
31
+ throw new Exception($dat['errorMsg']);
32
  }
33
  return $dat;
34
  }
41
  }
42
  }
43
  protected function getURL($url, $postParams = array()){
 
44
  if(function_exists('curl_init')){
45
  $this->curlDataWritten = 0;
46
  $this->curlContent = "";
62
  curl_close($curl);
63
  return $this->curlContent;
64
  } else {
65
+ $cerror = curl_error($curl);
 
66
  curl_close($curl);
67
+ throw new Exception(self::$maintMsg . " Got HTTP status code [$httpStatus] and curl error: $cerror");
68
  }
69
  } else {
70
  $data = $this->fileGet($url, $postParams);
71
  if($data === false){
72
  $err = error_get_last();
73
+ throw new Exception(self::$maintMsg . " Got HTTP error: " . $err);
 
74
  }
75
  return $data;
76
  }
99
  return @file_get_contents($url, false, $context, -1);
100
  }
101
  public function binCall($func, $postData){
 
102
  $url = $this->getAPIURL() . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&action=' . $func;
103
  if(function_exists('curl_init')){
104
  $curl = curl_init($url);
116
  }
117
  $data = curl_exec($curl);
118
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
119
+ if($httpStatus != 200){
120
+ $cError = curl_error($curl);
121
+ curl_close($curl);
122
+ throw new Exception(self::$maintMsg . " Got HTTP status [$httpStatus] and curl error: $cError");
123
+ }
124
  } else {
125
  $data = $this->fileGet($url, $postData);
126
  if($data === false){
127
+ throw new Exception(self::$maintMsg . " Got HTTP error: " . error_get_last());
 
128
  }
129
  $httpStatus = '200';
130
  }
131
  if(preg_match('/\{.*errorMsg/', $data)){
132
  $jdat = @json_decode($data, true);
133
  if(is_array($jdat) && $jdat['errorMsg']){
134
+ throw new Exception($jdat['errorMsg']);
 
135
  }
136
  }
137
  return array('code' => $httpStatus, 'data' => $data);
lib/wfConfig.php CHANGED
@@ -117,19 +117,19 @@ class wfConfig {
117
  "loginSec_lockoutMins" => "5",
118
  'loginSec_maxFailures' => "50",
119
  'loginSec_maxForgotPasswd' => "50",
120
- 'maxGlobalRequests' => "960",
121
  'maxGlobalRequests_action' => "throttle",
122
- 'maxRequestsCrawlers' => "960",
123
  'maxRequestsCrawlers_action' => "throttle",
124
- 'maxRequestsHumans' => "60",
125
  'maxRequestsHumans_action' => "throttle",
126
- 'max404Crawlers' => "240",
127
  'max404Crawlers_action' => "throttle",
128
- 'max404Humans' => "60",
129
  'max404Humans_action' => "throttle",
130
- 'maxScanHits' => "60",
131
  'maxScanHits_action' => "throttle",
132
- 'blockedTime' => "3600"
133
  )
134
  ),
135
  array( //level 2
@@ -180,19 +180,19 @@ class wfConfig {
180
  "loginSec_lockoutMins" => "240",
181
  'loginSec_maxFailures' => "20",
182
  'loginSec_maxForgotPasswd' => "20",
183
- 'maxGlobalRequests' => "960",
184
  'maxGlobalRequests_action' => "throttle",
185
- 'maxRequestsCrawlers' => "960",
186
  'maxRequestsCrawlers_action' => "throttle",
187
- 'maxRequestsHumans' => "120",
188
  'maxRequestsHumans_action' => "throttle",
189
- 'max404Crawlers' => "240",
190
  'max404Crawlers_action' => "throttle",
191
- 'max404Humans' => "30",
192
  'max404Humans_action' => "throttle",
193
- 'maxScanHits' => "15",
194
  'maxScanHits_action' => "throttle",
195
- 'blockedTime' => "7200"
196
  )
197
  ),
198
  array( //level 3
@@ -248,14 +248,14 @@ class wfConfig {
248
  'maxRequestsCrawlers' => "960",
249
  'maxRequestsCrawlers_action' => "throttle",
250
  'maxRequestsHumans' => "60",
251
- 'maxRequestsHumans_action' => "block",
252
  'max404Crawlers' => "60",
253
- 'max404Crawlers_action' => "block",
254
- 'max404Humans' => "30",
255
- 'max404Humans_action' => "block",
256
- 'maxScanHits' => "10",
257
- 'maxScanHits_action' => "block",
258
- 'blockedTime' => "86400"
259
  )
260
  ),
261
  array( //level 4
@@ -312,13 +312,13 @@ class wfConfig {
312
  'maxRequestsCrawlers_action' => "throttle",
313
  'maxRequestsHumans' => "30",
314
  'maxRequestsHumans_action' => "block",
315
- 'max404Crawlers' => "10",
316
  'max404Crawlers_action' => "block",
317
- 'max404Humans' => "5",
318
  'max404Humans_action' => "block",
319
- 'maxScanHits' => "2",
320
  'maxScanHits_action' => "block",
321
- 'blockedTime' => "86400"
322
  )
323
  )
324
  );
117
  "loginSec_lockoutMins" => "5",
118
  'loginSec_maxFailures' => "50",
119
  'loginSec_maxForgotPasswd' => "50",
120
+ 'maxGlobalRequests' => "DISABLED",
121
  'maxGlobalRequests_action' => "throttle",
122
+ 'maxRequestsCrawlers' => "DISABLED",
123
  'maxRequestsCrawlers_action' => "throttle",
124
+ 'maxRequestsHumans' => "DISABLED",
125
  'maxRequestsHumans_action' => "throttle",
126
+ 'max404Crawlers' => "DISABLED",
127
  'max404Crawlers_action' => "throttle",
128
+ 'max404Humans' => "DISABLED",
129
  'max404Humans_action' => "throttle",
130
+ 'maxScanHits' => "DISABLED",
131
  'maxScanHits_action' => "throttle",
132
+ 'blockedTime' => "300"
133
  )
134
  ),
135
  array( //level 2
180
  "loginSec_lockoutMins" => "240",
181
  'loginSec_maxFailures' => "20",
182
  'loginSec_maxForgotPasswd' => "20",
183
+ 'maxGlobalRequests' => "DISABLED",
184
  'maxGlobalRequests_action' => "throttle",
185
+ 'maxRequestsCrawlers' => "DISABLED",
186
  'maxRequestsCrawlers_action' => "throttle",
187
+ 'maxRequestsHumans' => "DISABLED",
188
  'maxRequestsHumans_action' => "throttle",
189
+ 'max404Crawlers' => "DISABLED",
190
  'max404Crawlers_action' => "throttle",
191
+ 'max404Humans' => "DISABLED",
192
  'max404Humans_action' => "throttle",
193
+ 'maxScanHits' => "DISABLED",
194
  'maxScanHits_action' => "throttle",
195
+ 'blockedTime' => "300"
196
  )
197
  ),
198
  array( //level 3
248
  'maxRequestsCrawlers' => "960",
249
  'maxRequestsCrawlers_action' => "throttle",
250
  'maxRequestsHumans' => "60",
251
+ 'maxRequestsHumans_action' => "throttle",
252
  'max404Crawlers' => "60",
253
+ 'max404Crawlers_action' => "throttle",
254
+ 'max404Humans' => "60",
255
+ 'max404Humans_action' => "throttle",
256
+ 'maxScanHits' => "30",
257
+ 'maxScanHits_action' => "throttle",
258
+ 'blockedTime' => "1800"
259
  )
260
  ),
261
  array( //level 4
312
  'maxRequestsCrawlers_action' => "throttle",
313
  'maxRequestsHumans' => "30",
314
  'maxRequestsHumans_action' => "block",
315
+ 'max404Crawlers' => "30",
316
  'max404Crawlers_action' => "block",
317
+ 'max404Humans' => "60",
318
  'max404Humans_action' => "block",
319
+ 'maxScanHits' => "10",
320
  'maxScanHits_action' => "block",
321
+ 'blockedTime' => "7200"
322
  )
323
  )
324
  );
lib/wfDB.php CHANGED
@@ -37,41 +37,22 @@ class wfDB {
37
  }
38
  }
39
  }
40
- if($createNewHandle){
 
 
 
 
41
  $dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
42
- if($dbh === false){
43
- self::criticalError("Wordfence could not connect to your database. Error was: " . mysql_error());
44
- return;
45
- }
46
  mysql_select_db($this->dbname, $dbh);
47
- $this->dbh = $dbh;
48
- $this->query("SET NAMES 'utf8'");
49
-
50
- //Set big packets for set_ser when it serializes a scan in between forks
51
- $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
52
- } else {
53
- $handleKey = md5($dbhost . $dbuser . $dbpassword . $dbname);
54
- if(isset(self::$dbhCache[$handleKey])){
55
- $this->dbh = self::$dbhCache[$handleKey];
56
  } else {
57
- global $wpdb;
58
- if(isset($wpdb) && isset($wpdb->dbh) && is_resource($wpdb->dbh)){
59
- $dbh = $wpdb->dbh;
60
- self::$dbhCache[$handleKey] = $dbh;
61
- $this->dbh = self::$dbhCache[$handleKey];
62
- } else {
63
- $dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
64
- if($dbh === false){
65
- self::criticalError("Wordfence could not connect to your database. The error was: " . mysql_error());
66
- return;
67
- }
68
- mysql_select_db($this->dbname, $dbh);
69
- self::$dbhCache[$handleKey] = $dbh;
70
- $this->dbh = self::$dbhCache[$handleKey];
71
- $this->query("SET NAMES 'utf8'");
72
- }
73
- $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
74
  }
 
 
 
75
  }
76
  }
77
  public function querySingleRec(){
@@ -202,6 +183,9 @@ class wfDB {
202
  global $wpdb;
203
  return $wpdb->base_prefix;
204
  }
 
 
 
205
  }
206
 
207
  ?>
37
  }
38
  }
39
  }
40
+ //We tried reusing wpdb but got disconnection errors from many users.
41
+ $handleKey = md5($dbhost . $dbuser . $dbpassword . $dbname);
42
+ if( (! $createNewHandle) && isset(self::$dbhCache[$handleKey]) && mysql_ping(self::$dbhCache[$handleKey]) ){
43
+ $this->dbh = self::$dbhCache[$handleKey];
44
+ } else {
45
  $dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
 
 
 
 
46
  mysql_select_db($this->dbname, $dbh);
47
+ if($createNewHandle){
48
+ $this->dbh = $dbh;
 
 
 
 
 
 
 
49
  } else {
50
+ self::$dbhCache[$handleKey] = $dbh;
51
+ $this->dbh = self::$dbhCache[$handleKey];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
+ $this->query("SET NAMES 'utf8'");
54
+ $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
55
+ $this->queryIgnoreError("SET GLOBAL wait_timeout=28800");
56
  }
57
  }
58
  public function querySingleRec(){
183
  global $wpdb;
184
  return $wpdb->base_prefix;
185
  }
186
+ public function getAffectedRows(){
187
+ return mysql_affected_rows($this->dbh);
188
+ }
189
  }
190
 
191
  ?>
lib/wfLog.php CHANGED
@@ -475,9 +475,11 @@ class wfLog {
475
  }
476
  public function firewallBadIPs(){
477
  $IP = wfUtils::inet_aton(wfUtils::getIP());
478
- if($secsToGo = $this->getDB()->querySingle("select (blockedTime + %s) - unix_timestamp() as secsToGo from " . $this->blocksTable . " where IP=%s and (permanent=1 OR blockedTime + %s > unix_timestamp())", wfConfig::get('blockedTime'), $IP, wfConfig::get('blockedTime'))){
 
 
479
  $this->getDB()->query("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
480
- $this->do503($secsToGo);
481
  }
482
  }
483
  private function takeBlockingAction($configVar, $reason){
@@ -502,12 +504,12 @@ class wfLog {
502
  wordfence::status(2, 'info', "Throttling IP $IP. $reason");
503
  $secsToGo = 60;
504
  }
505
- $this->do503($secsToGo);
506
  } else {
507
  return;
508
  }
509
  }
510
- private function do503($secsToGo = false){
511
  header('HTTP/1.1 503 Service Temporarily Unavailable');
512
  header('Status: 503 Service Temporarily Unavailable');
513
  if($secsToGo){
475
  }
476
  public function firewallBadIPs(){
477
  $IP = wfUtils::inet_aton(wfUtils::getIP());
478
+ if($rec = $this->getDB()->querySingleRec("select (blockedTime + %s) - unix_timestamp() as secsToGo, reason from " . $this->blocksTable . " where IP=%s and (permanent=1 OR blockedTime + %s > unix_timestamp())", wfConfig::get('blockedTime'), $IP, wfConfig::get('blockedTime'))){
479
+ $secsToGo = $rec['secsToGo'];
480
+ $reason = $rec[1];
481
  $this->getDB()->query("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
482
+ $this->do503($secsToGo, $rec['reason']);
483
  }
484
  }
485
  private function takeBlockingAction($configVar, $reason){
504
  wordfence::status(2, 'info', "Throttling IP $IP. $reason");
505
  $secsToGo = 60;
506
  }
507
+ $this->do503($secsToGo, $reason);
508
  } else {
509
  return;
510
  }
511
  }
512
+ private function do503($secsToGo, $reason){
513
  header('HTTP/1.1 503 Service Temporarily Unavailable');
514
  header('Status: 503 Service Temporarily Unavailable');
515
  if($secsToGo){
lib/wfScanEngine.php CHANGED
@@ -17,7 +17,6 @@ class wfScanEngine {
17
  private $i = false;
18
  private $wp_version = false;
19
  private $apiKey = false;
20
- private $errorStopped = false;
21
  private $startTime = 0;
22
  private $scanStep = 0;
23
  private $maxExecTime = 10; //If more than $maxExecTime has elapsed since last check, fork a new scan process and continue
@@ -38,7 +37,7 @@ class wfScanEngine {
38
  'unknown' => false
39
  );
40
  public function __sleep(){ //Same order here as above for properties that are included in serialization
41
- return array('hasher', 'hashes', 'jobList', 'i', 'wp_version', 'apiKey', 'errorStopped', 'startTime', 'scanStep', 'maxExecTime', 'malwareScanEnabled', 'pluginScanEnabled', 'coreScanEnabled', 'themeScanEnabled', 'unknownFiles', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX');
42
  }
43
  public function __construct(){
44
  $this->startTime = time();
@@ -71,18 +70,18 @@ class wfScanEngine {
71
  try {
72
  self::checkForKill();
73
  $this->doScan();
 
74
  self::checkForKill();
75
- if(! $this->errorStopped){
76
- wfConfig::set('lastScanCompleted', 'ok');
77
- }
78
  //updating this scan ID will trigger the scan page to load/reload the results.
79
  $this->i->setScanTimeNow();
80
  //scan ID only incremented at end of scan to make UI load new results
81
  $this->emailNewIssues();
 
82
  } catch(Exception $e){
83
- $this->errorStop($e->getMessage());
 
 
84
  }
85
- wordfence::scheduleNextScan(true);
86
  }
87
  public function forkIfNeeded(){
88
  self::checkForKill();
@@ -106,9 +105,6 @@ class wfScanEngine {
106
  $jobName = $this->jobList[0];
107
  call_user_func(array($this, 'scan_' . $jobName));
108
  array_shift($this->jobList); //only shift once we're done because we may pause halfway through a job and need to pick up where we left off
109
- if($this->errorStopped){
110
- return;
111
- }
112
  self::checkForKill();
113
  $this->fork();
114
  }
@@ -128,10 +124,6 @@ class wfScanEngine {
128
  private function scan_knownFiles_init(){
129
  $this->status(1, 'info', "Contacting Wordfence to initiate scan");
130
  $this->api->call('log_scan', array(), array());
131
- if($this->api->errorMsg){
132
- $this->errorStop($this->api->errorMsg);
133
- return;
134
- }
135
  if(wfConfig::get('scansEnabled_core')){
136
  $this->coreScanEnabled = true;
137
  $this->statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
@@ -241,16 +233,15 @@ class wfScanEngine {
241
  'hashStorageID' => $this->hasher->getHashStorageID()
242
  );
243
  $content = json_encode($scanData);
244
- $dataArr = $this->api->binCall('main_scan', $content);
245
- if($this->api->errorMsg){
246
- $this->errorStop($this->api->errorMsg);
247
  wordfence::statusEndErr();
248
- return;
249
  }
250
  if(! is_array($dataArr)){
251
- $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
252
  wordfence::statusEndErr();
253
- return;
254
  }
255
  //Data is an encoded string of <4 bytes of total length including these 4 bytes><2 bytes of filename length><filename>
256
  $totalUStrLen = unpack('N', substr($dataArr['data'], 0, 4));
@@ -258,9 +249,8 @@ class wfScanEngine {
258
  $this->unknownFiles = substr($dataArr['data'], 4, ($totalUStrLen - 4)); //subtruct the first 4 bytes which is an INT that is the total length of unknown string including the 4 bytes
259
  $resultArr = json_decode(substr($dataArr['data'], $totalUStrLen), true);
260
  if(! (is_array($resultArr) && isset($resultArr['results'])) ){
261
- $this->errorStop("We received an incorrect response from the Wordfence server when scanning core, plugin and theme files.");
262
  wordfence::statusEndErr();
263
- return;
264
  }
265
 
266
  $this->status(2, 'info', "Processing scan results");
@@ -296,7 +286,7 @@ class wfScanEngine {
296
  private function scan_fileContents_finish(){
297
  $this->status(2, 'info', "Done file contents scan");
298
  if($this->scanner->errorMsg){
299
- $this->errorStop($this->scanner->errorMsg);
300
  }
301
  $this->scanner = null;
302
  $haveIssues = false;
@@ -352,9 +342,8 @@ class wfScanEngine {
352
  $hooverResults = $this->hoover->getBaddies();
353
  $this->status(2, 'info', "Done examining URls");
354
  if($this->hoover->errorMsg){
355
- $this->errorStop($this->hoover->errorMsg);
356
  wordfence::statusEndErr();
357
- return;
358
 
359
  }
360
  $haveIssues = false;
@@ -450,9 +439,8 @@ class wfScanEngine {
450
  private function scan_comments_finish(){
451
  $hooverResults = $this->hoover->getBaddies();
452
  if($this->hoover->errorMsg){
453
- $this->errorStop($this->hoover->errorMsg);
454
  wordfence::statusEndErr();
455
- return;
456
  }
457
  $haveIssues = false;
458
  foreach($hooverResults as $idString => $hresults){
@@ -817,11 +805,6 @@ class wfScanEngine {
817
  }
818
  wordfence::statusEnd($this->statusIDX['oldVersions'], $haveIssues);
819
  }
820
- private function errorStop($msg){
821
- $this->errorStopped = true;
822
- $this->status(1, 'error', $msg);
823
- wfConfig::set('lastScanCompleted', $msg);
824
- }
825
  public function status($level, $type, $msg){
826
  wordfence::status($level, $type, $msg);
827
  }
@@ -835,9 +818,8 @@ class wfScanEngine {
835
  $kill = wfConfig::get('wfKillRequested', 0);
836
  if($kill && time() - $kill < 600){ //Kill lasts for 10 minutes
837
  $wfdb = new wfDB();
838
- wordfence::status(2, 'info', "Killing current scan");
839
  wordfence::status(10, 'info', "SUM_KILLED:Previous scan was killed successfully.");
840
- exit(0);
841
  }
842
  }
843
  public static function startScan($isFork = false){
17
  private $i = false;
18
  private $wp_version = false;
19
  private $apiKey = false;
 
20
  private $startTime = 0;
21
  private $scanStep = 0;
22
  private $maxExecTime = 10; //If more than $maxExecTime has elapsed since last check, fork a new scan process and continue
37
  'unknown' => false
38
  );
39
  public function __sleep(){ //Same order here as above for properties that are included in serialization
40
+ return array('hasher', 'hashes', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'scanStep', 'maxExecTime', 'malwareScanEnabled', 'pluginScanEnabled', 'coreScanEnabled', 'themeScanEnabled', 'unknownFiles', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX');
41
  }
42
  public function __construct(){
43
  $this->startTime = time();
70
  try {
71
  self::checkForKill();
72
  $this->doScan();
73
+ wfConfig::set('lastScanCompleted', 'ok');
74
  self::checkForKill();
 
 
 
75
  //updating this scan ID will trigger the scan page to load/reload the results.
76
  $this->i->setScanTimeNow();
77
  //scan ID only incremented at end of scan to make UI load new results
78
  $this->emailNewIssues();
79
+ wordfence::scheduleNextScan(true);
80
  } catch(Exception $e){
81
+ wfConfig::set('lastScanCompleted', $e->getMessage());
82
+ wordfence::scheduleNextScan(true);
83
+ throw $e;
84
  }
 
85
  }
86
  public function forkIfNeeded(){
87
  self::checkForKill();
105
  $jobName = $this->jobList[0];
106
  call_user_func(array($this, 'scan_' . $jobName));
107
  array_shift($this->jobList); //only shift once we're done because we may pause halfway through a job and need to pick up where we left off
 
 
 
108
  self::checkForKill();
109
  $this->fork();
110
  }
124
  private function scan_knownFiles_init(){
125
  $this->status(1, 'info', "Contacting Wordfence to initiate scan");
126
  $this->api->call('log_scan', array(), array());
 
 
 
 
127
  if(wfConfig::get('scansEnabled_core')){
128
  $this->coreScanEnabled = true;
129
  $this->statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
233
  'hashStorageID' => $this->hasher->getHashStorageID()
234
  );
235
  $content = json_encode($scanData);
236
+ try {
237
+ $dataArr = $this->api->binCall('main_scan', $content);
238
+ } catch(Exception $e){
239
  wordfence::statusEndErr();
240
+ throw $e;
241
  }
242
  if(! is_array($dataArr)){
 
243
  wordfence::statusEndErr();
244
+ throw new Exception("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
245
  }
246
  //Data is an encoded string of <4 bytes of total length including these 4 bytes><2 bytes of filename length><filename>
247
  $totalUStrLen = unpack('N', substr($dataArr['data'], 0, 4));
249
  $this->unknownFiles = substr($dataArr['data'], 4, ($totalUStrLen - 4)); //subtruct the first 4 bytes which is an INT that is the total length of unknown string including the 4 bytes
250
  $resultArr = json_decode(substr($dataArr['data'], $totalUStrLen), true);
251
  if(! (is_array($resultArr) && isset($resultArr['results'])) ){
 
252
  wordfence::statusEndErr();
253
+ throw new Exception("We received an incorrect response from the Wordfence server when scanning core, plugin and theme files.");
254
  }
255
 
256
  $this->status(2, 'info', "Processing scan results");
286
  private function scan_fileContents_finish(){
287
  $this->status(2, 'info', "Done file contents scan");
288
  if($this->scanner->errorMsg){
289
+ throw new Exception($this->scanner->errorMsg);
290
  }
291
  $this->scanner = null;
292
  $haveIssues = false;
342
  $hooverResults = $this->hoover->getBaddies();
343
  $this->status(2, 'info', "Done examining URls");
344
  if($this->hoover->errorMsg){
 
345
  wordfence::statusEndErr();
346
+ throw new Exception($this->hoover->errorMsg);
347
 
348
  }
349
  $haveIssues = false;
439
  private function scan_comments_finish(){
440
  $hooverResults = $this->hoover->getBaddies();
441
  if($this->hoover->errorMsg){
 
442
  wordfence::statusEndErr();
443
+ throw new Exception($this->hoover->errorMsg);
444
  }
445
  $haveIssues = false;
446
  foreach($hooverResults as $idString => $hresults){
805
  }
806
  wordfence::statusEnd($this->statusIDX['oldVersions'], $haveIssues);
807
  }
 
 
 
 
 
808
  public function status($level, $type, $msg){
809
  wordfence::status($level, $type, $msg);
810
  }
818
  $kill = wfConfig::get('wfKillRequested', 0);
819
  if($kill && time() - $kill < 600){ //Kill lasts for 10 minutes
820
  $wfdb = new wfDB();
 
821
  wordfence::status(10, 'info', "SUM_KILLED:Previous scan was killed successfully.");
822
+ throw new Exception("Scan was killed on administrator request.");
823
  }
824
  }
825
  public static function startScan($isFork = false){
lib/wfUtils.php CHANGED
@@ -308,35 +308,40 @@ class wfUtils {
308
  }
309
  if(sizeof($toResolve) > 0){
310
  $api = new wfAPI(wfConfig::get('apiKey'), $wp_version);
311
- $freshIPs = $api->call('resolve_ips', array(), array(
312
- 'ips' => implode(',', $toResolve)
313
- ));
314
- if(is_array($freshIPs)){
315
- foreach($freshIPs as $IP => $value){
316
- if($value == 'failed'){
317
- $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed) values (%s, unix_timestamp(), 1)", ($isInt ? $IP : self::inet_aton($IP)) );
318
- $IPLocs[$IP] = false;
319
- } else {
320
- $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed, city, region, countryName, countryCode, lat, lon) values (%s, unix_timestamp(), 0, '%s', '%s', '%s', '%s', %s, %s)",
321
- ($isInt ? $IP : self::inet_aton($IP)),
322
- $value[3], //city
323
- $value[2], //region
324
- $value[1], //countryName
325
- $value[0],//countryCode
326
- $value[4],//lat
327
- $value[5]//lon
328
- );
329
- $IPLocs[$IP] = array(
330
- 'IP' => $IP,
331
- 'city' => $value[3],
332
- 'region' => $value[2],
333
- 'countryName' => $value[1],
334
- 'countryCode' => $value[0],
335
- 'lat' => $value[4],
336
- 'lon' => $value[5]
337
- );
 
 
338
  }
339
  }
 
 
 
340
  }
341
  }
342
  return $IPLocs;
308
  }
309
  if(sizeof($toResolve) > 0){
310
  $api = new wfAPI(wfConfig::get('apiKey'), $wp_version);
311
+ try {
312
+ $freshIPs = $api->call('resolve_ips', array(), array(
313
+ 'ips' => implode(',', $toResolve)
314
+ ));
315
+ if(is_array($freshIPs)){
316
+ foreach($freshIPs as $IP => $value){
317
+ if($value == 'failed'){
318
+ $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed) values (%s, unix_timestamp(), 1)", ($isInt ? $IP : self::inet_aton($IP)) );
319
+ $IPLocs[$IP] = false;
320
+ } else {
321
+ $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed, city, region, countryName, countryCode, lat, lon) values (%s, unix_timestamp(), 0, '%s', '%s', '%s', '%s', %s, %s)",
322
+ ($isInt ? $IP : self::inet_aton($IP)),
323
+ $value[3], //city
324
+ $value[2], //region
325
+ $value[1], //countryName
326
+ $value[0],//countryCode
327
+ $value[4],//lat
328
+ $value[5]//lon
329
+ );
330
+ $IPLocs[$IP] = array(
331
+ 'IP' => $IP,
332
+ 'city' => $value[3],
333
+ 'region' => $value[2],
334
+ 'countryName' => $value[1],
335
+ 'countryCode' => $value[0],
336
+ 'lat' => $value[4],
337
+ 'lon' => $value[5]
338
+ );
339
+ }
340
  }
341
  }
342
+ } catch(Exception $e){
343
+ wordfence::status(2, 'error', "Call to Wordfence API to resolve IPs failed: " . $e->getMessage());
344
+ return array();
345
  }
346
  }
347
  return $IPLocs;
lib/wordfenceClass.php CHANGED
@@ -46,12 +46,17 @@ class wordfence {
46
  public static function hourlyCron(){
47
  global $wpdb; $p = $wpdb->base_prefix;
48
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
49
- $patData = $api->call('get_known_vuln_pattern');
50
- if(is_array($patData) && $patData['pat']){
51
- if(@preg_match($patData['pat'], 'wordfence_test_vuln_match')){
52
- wfConfig::set('vulnRegex', $pat);
 
 
53
  }
 
 
54
  }
 
55
 
56
  if(wfConfig::get('other_WFNet')){
57
  $wfdb = new wfDB();
@@ -62,7 +67,11 @@ class wordfence {
62
  }
63
  $wfdb->query("truncate table $p"."wfNet404s");
64
  if(sizeof($URIs) > 0){
65
- $api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) ));
 
 
 
 
66
  }
67
 
68
  $q2 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
@@ -79,18 +88,22 @@ class wordfence {
79
  }
80
  if(strlen($lockCont) > 0 || strlen($scanCont) > 0){
81
  $cont = pack('N', strlen($lockCont) / 4) . $lockCont . pack('N', strlen($scanCont) / 4) . $scanCont;
82
- $resp = $api->binCall('get_net_bad_ips', $cont);
83
- if($resp['code'] == 200){
84
- $len = strlen($resp['data']);
85
- $reason = "WFSN: Blocked by Wordfence Security Network";
86
- $wfdb->query("delete from $p"."wfBlocks where wfsn=1");
87
- if($len > 0 && $len % 4 == 0){
88
- for($i = 0; $i < $len; $i += 4){
89
- list($ipLong) = array_values(unpack('N', substr($resp['data'], $i, 4)));
90
- $IPStr = long2ip($ipLong);
91
- self::getLog()->blockIP($IPStr, $reason, true);
 
 
92
  }
93
  }
 
 
94
  }
95
  }
96
  }
@@ -161,14 +174,16 @@ class wordfence {
161
 
162
  if(! wfConfig::get('apiKey')){
163
  $api = new wfAPI('', wfUtils::getWPVersion());
164
- $keyData = $api->call('get_anon_api_key');
165
- if($api->errorMsg){
166
- die("Error fetching free API key from Wordfence: " . $api->errorMsg);
167
- }
168
- if($keyData['ok'] && $keyData['apiKey']){
169
- wfConfig::set('apiKey', $keyData['apiKey']);
170
- } else {
171
- die("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
 
 
172
  }
173
  }
174
  wp_clear_scheduled_hook('wordfence_daily_cron');
@@ -225,6 +240,9 @@ class wordfence {
225
  add_action('wp_logout','wordfence::logoutAction');
226
  add_action('profile_update', 'wordfence::profileUpdateAction', '99', 2);
227
  add_action('lostpassword_post', 'wordfence::lostPasswordPost', '1');
 
 
 
228
  add_filter('pre_comment_approved', 'wordfence::preCommentApprovedFilter', '99', 2);
229
  add_filter('authenticate', 'wordfence::authenticateFilter', 99, 3);
230
  //html|xhtml|atom|rss2|rdf|comment|export
@@ -475,20 +493,21 @@ class wordfence {
475
  return array('errorMsg' => "An invalid type was specified to get file.");
476
  }
477
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
478
- $contResult = $api->binCall('get_wp_file_content', array(
479
- 'v' => wfUtils::getWPVersion(),
480
- 'file' => $file,
481
- 'cType' => $cType,
482
- 'cName' => $cName,
483
- 'cVersion' => $cVersion
484
- ));
485
- if($api->errorMsg){
486
- return array('errorMsg' => $api->errorMsg);
487
- }
488
- if($contResult['data']){
489
- return array('fileContent' => $contResult['data']);
490
- } else {
491
- return array('errorMsg' => "We could not fetch a core WordPress file from the Wordfence API.");
 
492
  }
493
  }
494
  public static function ajax_sendActivityLog_callback(){
@@ -602,18 +621,20 @@ class wordfence {
602
  $paidKeyMsg = false;
603
  if($opts['apiKey'] != wfConfig::get('apiKey')){
604
  $api = new wfAPI($opts['apiKey'], wfUtils::getWPVersion());
605
- $res = $api->call('check_api_key', array(), array());
606
- if($res['ok'] && isset($res['isPaid'])){
607
- wfConfig::set('apiKey', $opts['apiKey']);
608
- $reload = 'reload';
609
- wfConfig::set('isPaid', $res['isPaid']);
610
- if($res['isPaid']){
611
- $paidKeyMsg = true;
 
 
 
 
612
  }
613
- } else if($res['errorMsg']){
614
- return array('errorMsg' => $res['errorMsg']);
615
- } else {
616
- return array('errorMsg' => "We could not change your API key. Please try again in a few minutes.");
617
  }
618
  }
619
 
@@ -861,37 +882,6 @@ class wordfence {
861
  'file' => $localFile
862
  );
863
  }
864
- public static function ajax_activate_callback(){
865
- $key = trim($_POST['key']);
866
- $email = trim($_POST['email']);
867
- $key = preg_replace('/[^a-fA-F0-9]+/', '', $key);
868
- if(strlen($key) < 10){
869
- return array("errorAlert" => "You entered an invalid API key." );
870
- }
871
- if(! preg_match('/.+\@.+/', $email)){
872
- return array("errorAlert" => "Please enter a valid email address where Wordfence can send alerts.");
873
- }
874
-
875
- wfConfig::set('apiKey', $key);
876
- wfConfig::set('alertEmails', $email);
877
- $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
878
- $result = $api->call('activate', array(), array());
879
- if($api->errorMsg){
880
- wfConfig::set('apiKey', '');
881
- return array("errorMsg" => $api->errorMsg );
882
- }
883
- if($result['ok'] && isset($result['isPaid'])){
884
- wfConfig::set('isPaid', $result['isPaid']);
885
- $err = wfScanEngine::startScan();
886
- if($err){
887
- return array('errorMsg' => $err);
888
- } else {
889
- return array("ok" => 1);
890
- }
891
- } else {
892
- return array('errorAlert' => "An unknown error occurred trying to activate Wordfence. Please try again in a few minutes." );
893
- }
894
- }
895
  public static function ajax_scan_callback(){
896
  self::status(4, 'info', "Ajax request received to start scan.");
897
  $err = wfScanEngine::startScan();
@@ -1147,6 +1137,10 @@ class wordfence {
1147
 
1148
  }
1149
  }
 
 
 
 
1150
  public static function admin_menus(){
1151
  if(! wfUtils::isAdmin()){ return; }
1152
  if(! wfConfig::get('alertEmails')){
@@ -1156,6 +1150,13 @@ class wordfence {
1156
  add_action('admin_notices', 'wordfence::configure_warning');
1157
  }
1158
  }
 
 
 
 
 
 
 
1159
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1160
  add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', wfUtils::getBaseURL() . 'images/wordfence-logo-16x16.png');
1161
  if(wfConfig::get('liveTrafficEnabled')){
@@ -1170,9 +1171,6 @@ class wordfence {
1170
  public static function menu_blockedIPs(){
1171
  require 'menu_blockedIPs.php';
1172
  }
1173
- public static function menu_config(){
1174
- require 'menu_config.php';
1175
- }
1176
  public static function menu_activity(){
1177
  require 'menu_activity.php';
1178
  }
@@ -1201,7 +1199,7 @@ class wordfence {
1201
  }
1202
  }
1203
  public static function genFilter($gen, $type){
1204
- if(wfConfig::get('other_hidegetWPVersion')){
1205
  return '';
1206
  } else {
1207
  return $gen;
@@ -1270,16 +1268,17 @@ class wordfence {
1270
  return;
1271
  }
1272
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
1273
- $result = $api->call('get_next_scan_time', array(), array());
1274
- if(empty($result['errorMsg']) === false){
1275
- return $result['errorMsg'];
1276
- }
1277
- $secsToGo = 3600 * 6; //In case we can't contact the API, schedule next scan 6 hours from now.
1278
- if(is_array($result) && $result['secsToGo'] > 1800){
1279
- $secsToGo = $result['secsToGo'];
 
 
 
1280
  }
1281
- wp_clear_scheduled_hook('wordfence_scheduled_scan');
1282
- wp_schedule_single_event(time() + $secsToGo, 'wordfence_scheduled_scan');
1283
  } else {
1284
  wp_clear_scheduled_hook('wordfence_scheduled_scan');
1285
  }
@@ -1344,5 +1343,12 @@ class wordfence {
1344
  }
1345
  return self::$debugOn;
1346
  }
 
 
 
 
 
 
 
1347
  }
1348
  ?>
46
  public static function hourlyCron(){
47
  global $wpdb; $p = $wpdb->base_prefix;
48
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
49
+ try {
50
+ $patData = $api->call('get_known_vuln_pattern');
51
+ if(is_array($patData) && $patData['pat']){
52
+ if(@preg_match($patData['pat'], 'wordfence_test_vuln_match')){
53
+ wfConfig::set('vulnRegex', $pat);
54
+ }
55
  }
56
+ } catch(Exception $e){
57
+ wordfence::status(2, 'error', "Could not fetch vulnerability patterns in hourly scheduled job: " . $e->getMessage());
58
  }
59
+
60
 
61
  if(wfConfig::get('other_WFNet')){
62
  $wfdb = new wfDB();
67
  }
68
  $wfdb->query("truncate table $p"."wfNet404s");
69
  if(sizeof($URIs) > 0){
70
+ try {
71
+ $api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) ));
72
+ } catch(Exception $e){
73
+ //Ignore
74
+ }
75
  }
76
 
77
  $q2 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
88
  }
89
  if(strlen($lockCont) > 0 || strlen($scanCont) > 0){
90
  $cont = pack('N', strlen($lockCont) / 4) . $lockCont . pack('N', strlen($scanCont) / 4) . $scanCont;
91
+ try {
92
+ $resp = $api->binCall('get_net_bad_ips', $cont);
93
+ if($resp['code'] == 200){
94
+ $len = strlen($resp['data']);
95
+ $reason = "WFSN: Blocked by Wordfence Security Network";
96
+ $wfdb->query("delete from $p"."wfBlocks where wfsn=1");
97
+ if($len > 0 && $len % 4 == 0){
98
+ for($i = 0; $i < $len; $i += 4){
99
+ list($ipLong) = array_values(unpack('N', substr($resp['data'], $i, 4)));
100
+ $IPStr = long2ip($ipLong);
101
+ self::getLog()->blockIP($IPStr, $reason, true);
102
+ }
103
  }
104
  }
105
+ } catch(Exception $e){
106
+ //Ignore
107
  }
108
  }
109
  }
174
 
175
  if(! wfConfig::get('apiKey')){
176
  $api = new wfAPI('', wfUtils::getWPVersion());
177
+ try {
178
+ $keyData = $api->call('get_anon_api_key');
179
+ if($keyData['ok'] && $keyData['apiKey']){
180
+ wfConfig::set('apiKey', $keyData['apiKey']);
181
+ } else {
182
+ throw new Exception("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
183
+ }
184
+ } catch(Exception $e){
185
+ error_log("Could not fetch free API key from Wordfence: " . $e->getMessage());
186
+ return;
187
  }
188
  }
189
  wp_clear_scheduled_hook('wordfence_daily_cron');
240
  add_action('wp_logout','wordfence::logoutAction');
241
  add_action('profile_update', 'wordfence::profileUpdateAction', '99', 2);
242
  add_action('lostpassword_post', 'wordfence::lostPasswordPost', '1');
243
+ /* For testing cron jobs
244
+ add_filter('cron_schedules', 'wordfence::moreCronReccurences');
245
+ */
246
  add_filter('pre_comment_approved', 'wordfence::preCommentApprovedFilter', '99', 2);
247
  add_filter('authenticate', 'wordfence::authenticateFilter', 99, 3);
248
  //html|xhtml|atom|rss2|rdf|comment|export
493
  return array('errorMsg' => "An invalid type was specified to get file.");
494
  }
495
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
496
+ try {
497
+ $contResult = $api->binCall('get_wp_file_content', array(
498
+ 'v' => wfUtils::getWPVersion(),
499
+ 'file' => $file,
500
+ 'cType' => $cType,
501
+ 'cName' => $cName,
502
+ 'cVersion' => $cVersion
503
+ ));
504
+ if($contResult['data']){
505
+ return array('fileContent' => $contResult['data']);
506
+ } else {
507
+ throw new Exception("We could not fetch a core WordPress file from the Wordfence API.");
508
+ }
509
+ } catch (Exception $e){
510
+ return array('errorMsg' => $e->getMessage());
511
  }
512
  }
513
  public static function ajax_sendActivityLog_callback(){
621
  $paidKeyMsg = false;
622
  if($opts['apiKey'] != wfConfig::get('apiKey')){
623
  $api = new wfAPI($opts['apiKey'], wfUtils::getWPVersion());
624
+ try {
625
+ $res = $api->call('check_api_key', array(), array());
626
+ if($res['ok'] && isset($res['isPaid'])){
627
+ wfConfig::set('apiKey', $opts['apiKey']);
628
+ $reload = 'reload';
629
+ wfConfig::set('isPaid', $res['isPaid']);
630
+ if($res['isPaid']){
631
+ $paidKeyMsg = true;
632
+ }
633
+ } else {
634
+ throw new Exception("We could not understand the Wordfence API server reply when updating your API key.");
635
  }
636
+ } catch (Exception $e){
637
+ return array('errorMsg' => $e->getMessage());
 
 
638
  }
639
  }
640
 
882
  'file' => $localFile
883
  );
884
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  public static function ajax_scan_callback(){
886
  self::status(4, 'info', "Ajax request received to start scan.");
887
  $err = wfScanEngine::startScan();
1137
 
1138
  }
1139
  }
1140
+ public static function noKeyError(){
1141
+ echo '<div id="wordfenceConfigWarning" class="fade error"><p><strong>Wordfence is not configured correctly.</strong> Go to your plugins menu and disable and re-enable Wordfence and this should fix the problem.</p></div>';
1142
+ }
1143
+
1144
  public static function admin_menus(){
1145
  if(! wfUtils::isAdmin()){ return; }
1146
  if(! wfConfig::get('alertEmails')){
1150
  add_action('admin_notices', 'wordfence::configure_warning');
1151
  }
1152
  }
1153
+ if(! wfConfig::get('apiKey')){
1154
+ if(wfUtils::isAdminPageMU()){
1155
+ add_action('network_admin_notices', 'wordfence::noKeyError');
1156
+ } else {
1157
+ add_action('admin_notices', 'wordfence::noKeyError');
1158
+ }
1159
+ }
1160
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1161
  add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', wfUtils::getBaseURL() . 'images/wordfence-logo-16x16.png');
1162
  if(wfConfig::get('liveTrafficEnabled')){
1171
  public static function menu_blockedIPs(){
1172
  require 'menu_blockedIPs.php';
1173
  }
 
 
 
1174
  public static function menu_activity(){
1175
  require 'menu_activity.php';
1176
  }
1199
  }
1200
  }
1201
  public static function genFilter($gen, $type){
1202
+ if(wfConfig::get('other_hideWPVersion')){
1203
  return '';
1204
  } else {
1205
  return $gen;
1268
  return;
1269
  }
1270
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
1271
+ try {
1272
+ $result = $api->call('get_next_scan_time', array(), array());
1273
+ $secsToGo = 3600 * 6; //In case we can't contact the API, schedule next scan 6 hours from now.
1274
+ if(is_array($result) && $result['secsToGo'] > 1800){
1275
+ $secsToGo = $result['secsToGo'];
1276
+ }
1277
+ wp_clear_scheduled_hook('wordfence_scheduled_scan');
1278
+ wp_schedule_single_event(time() + $secsToGo, 'wordfence_scheduled_scan');
1279
+ } catch (Exception $e){
1280
+ return $e->getMessage();
1281
  }
 
 
1282
  } else {
1283
  wp_clear_scheduled_hook('wordfence_scheduled_scan');
1284
  }
1343
  }
1344
  return self::$debugOn;
1345
  }
1346
+ /* For testing cron jobs
1347
+ public static function moreCronReccurences(){
1348
+ return array(
1349
+ Feveryminute' => array('interval' => 60, 'display' => 'Once Every Minute'),
1350
+ );
1351
+ }
1352
+ */
1353
  }
1354
  ?>
lib/wordfenceHash.php CHANGED
@@ -22,7 +22,7 @@ class wordfenceHash {
22
  private $lastStatusTime = false;
23
  public function __sleep(){ //same order as above
24
  if(sizeof($this->fileQ) > 0){
25
- wordfence::status(1, 'error', "Sanity fail. fileQ is not empty. Has: " . sizeof($this->fileQ));
26
  }
27
  return array('whitespace', 'totalData', 'totalFiles', 'totalDirs', 'linesOfPHP', 'linesOfJCH', 'striplen', 'hashPacket', 'hashStorageID', 'hashingStartTime', 'lastStatusTime');
28
  }
@@ -47,7 +47,7 @@ class wordfenceHash {
47
  $path .= '/';
48
  }
49
  if(! is_readable($path)){
50
- wordfence::status(1, 'error', "Could not read directory $path to do scan.");
51
  exit();
52
  }
53
  $files = scandir($path);
@@ -127,11 +127,15 @@ class wordfenceHash {
127
  }
128
  private function writeFileQueue(){
129
  $sql = "insert into " . $this->table . " (filename) values ";
 
130
  foreach($this->fileQ as $val){
 
131
  $sql .= "('" . mysql_real_escape_string($val) . "'),";
132
  }
133
- $sql = rtrim($sql, ',');
134
- $this->db->query($sql);
 
 
135
  $this->fileQ = array();
136
  }
137
  private function processFile($file){
@@ -176,23 +180,19 @@ class wordfenceHash {
176
  }
177
  if($this->hashStorageID){
178
  $dataArr = $this->api->binCall('add_hash_chunk', "WFID:" . pack('N', $this->hashStorageID) . $this->hashPacket);
179
- if($this->api->errorMsg){ wordfence::status(1, 'error', $this->api->errorMsg); exit(); }
180
  $this->hashPacket = "";
181
  if(is_array($dataArr) && isset($dataArr['data']) && $dataArr['data'] == $this->hashStorageID){
182
  //keep going
183
  } else {
184
- wordfence::status(1, 'error', "Could not store an additional chunk of hash data on Wordfence servers with ID: " . $this->hashStorageID);
185
- return false;
186
  }
187
  } else {
188
  $dataArr = $this->api->binCall('add_hash_chunk', "WFST:" . $this->hashPacket);
189
- if($this->api->errorMsg){ wordfence::status(1, 'error', $this->api->errorMsg); exit(); }
190
  $this->hashPacket = "";
191
  if(is_array($dataArr) && isset($dataArr['data']) && preg_match('/^\d+$/', $dataArr['data'])){
192
  $this->hashStorageID = $dataArr['data'];
193
  } else {
194
- wordfence::status(1, 'error', "Could not store hash data on Wordfence servers. Got response: " . var_export($dataArr, true));
195
- return false;
196
  }
197
  }
198
  }
22
  private $lastStatusTime = false;
23
  public function __sleep(){ //same order as above
24
  if(sizeof($this->fileQ) > 0){
25
+ throw new Exception("Sanity fail. fileQ is not empty. Has: " . sizeof($this->fileQ));
26
  }
27
  return array('whitespace', 'totalData', 'totalFiles', 'totalDirs', 'linesOfPHP', 'linesOfJCH', 'striplen', 'hashPacket', 'hashStorageID', 'hashingStartTime', 'lastStatusTime');
28
  }
47
  $path .= '/';
48
  }
49
  if(! is_readable($path)){
50
+ throw new Exception("Could not read directory $path to do scan.");
51
  exit();
52
  }
53
  $files = scandir($path);
127
  }
128
  private function writeFileQueue(){
129
  $sql = "insert into " . $this->table . " (filename) values ";
130
+ $added = false;
131
  foreach($this->fileQ as $val){
132
+ $added = true;
133
  $sql .= "('" . mysql_real_escape_string($val) . "'),";
134
  }
135
+ if($added){
136
+ $sql = rtrim($sql, ',');
137
+ $this->db->query($sql);
138
+ }
139
  $this->fileQ = array();
140
  }
141
  private function processFile($file){
180
  }
181
  if($this->hashStorageID){
182
  $dataArr = $this->api->binCall('add_hash_chunk', "WFID:" . pack('N', $this->hashStorageID) . $this->hashPacket);
 
183
  $this->hashPacket = "";
184
  if(is_array($dataArr) && isset($dataArr['data']) && $dataArr['data'] == $this->hashStorageID){
185
  //keep going
186
  } else {
187
+ throw new Exception("Could not store an additional chunk of hash data on Wordfence servers with ID: " . $this->hashStorageID);
 
188
  }
189
  } else {
190
  $dataArr = $this->api->binCall('add_hash_chunk', "WFST:" . $this->hashPacket);
 
191
  $this->hashPacket = "";
192
  if(is_array($dataArr) && isset($dataArr['data']) && preg_match('/^\d+$/', $dataArr['data'])){
193
  $this->hashStorageID = $dataArr['data'];
194
  } else {
195
+ throw new Exception("Could not store hash data on Wordfence servers. Got response: " . var_export($dataArr, true));
 
196
  }
197
  }
198
  }
lib/wordfenceURLHoover.php CHANGED
@@ -121,10 +121,6 @@ class wordfenceURLHoover {
121
  $this->dbg("Checking " . sizeof($allHostKeys) . " hostkeys");
122
  $resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
123
  $this->dbg("Done hostkey check");
124
- if($this->api->errorMsg){
125
- $this->errorMsg = $this->api->errorMsg;
126
- return false;
127
- }
128
 
129
  $badHostKeys = array();
130
  if($resp['code'] == 200){
@@ -170,10 +166,6 @@ class wordfenceURLHoover {
170
  $this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
171
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
172
  $this->dbg("Done URL check");
173
- if($this->api->errorMsg){
174
- $this->errorMsg = $this->api->errorMsg;
175
- return false;
176
- }
177
  if(is_array($badURLs) && sizeof($badURLs) > 0){
178
  $finalResults = array();
179
  foreach($badURLs as $file => $badSiteList){
121
  $this->dbg("Checking " . sizeof($allHostKeys) . " hostkeys");
122
  $resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
123
  $this->dbg("Done hostkey check");
 
 
 
 
124
 
125
  $badHostKeys = array();
126
  if($resp['code'] == 200){
166
  $this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
167
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
168
  $this->dbg("Done URL check");
 
 
 
 
169
  if(is_array($badURLs) && sizeof($badURLs) > 0){
170
  $finalResults = array();
171
  foreach($badURLs as $file => $badSiteList){
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
  Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
  Requires at least: 3.3.1
5
  Tested up to: 3.4
6
- Stable tag: 3.0.2
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
@@ -152,6 +152,13 @@ or a theme, because often these have been updated to fix a security hole.
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
 
 
 
 
 
 
 
155
 
156
  = 3.0.2 =
157
  * Overall this release is a very important upgrade. It drastically reduces memory usage on systems with large files from hundreds of megs to around 8 megs max memory used per scan.
@@ -411,6 +418,10 @@ or a theme, because often these have been updated to fix a security hole.
411
  * Initial public release of Wordfence.
412
 
413
  == Upgrade Notice ==
 
 
 
 
414
 
415
  = 3.0.2 =
416
  Upgrade immediately. This release drastically reduces memory, reduces new DB connections created by
3
  Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
  Requires at least: 3.3.1
5
  Tested up to: 3.4
6
+ Stable tag: 3.0.3
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
155
+ = 3.0.3 =
156
+ * Issue that caused all core files to show as missing has been fixed.
157
+ * We now handle all API server errors gracefully using exceptions.
158
+ * If your installation didn't activate correctly you now get a friendly message.
159
+ * Removed unused menu_config.php code.
160
+ * The 503 message now tells you why your access to the site has been limited so that admin's can tune firewall rules better.
161
+ * We no longer reuse the WordPress wpdb handle because we get better stability with our own connection.
162
 
163
  = 3.0.2 =
164
  * Overall this release is a very important upgrade. It drastically reduces memory usage on systems with large files from hundreds of megs to around 8 megs max memory used per scan.
418
  * Initial public release of Wordfence.
419
 
420
  == Upgrade Notice ==
421
+ = 3.0.3 =
422
+ Upgrade immediately. This release fixes an issue that caused Wordfence to show all your core files
423
+ missing under certain conditions. It was usually caused by high load on our scanning server and the
424
+ plugin not handling an error condition halfway through the scan correctly.
425
 
426
  = 3.0.2 =
427
  Upgrade immediately. This release drastically reduces memory, reduces new DB connections created by
wfscan.php CHANGED
@@ -106,7 +106,14 @@ class wfScan {
106
  wordfence::statusPrep(); //Re-initializes all status counters
107
  $scan = new wfScanEngine();
108
  }
109
- $scan->go();
 
 
 
 
 
 
 
110
  wfUtils::clearScanLock();
111
  self::logPeakMemory();
112
  wordfence::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
106
  wordfence::statusPrep(); //Re-initializes all status counters
107
  $scan = new wfScanEngine();
108
  }
109
+ try {
110
+ $scan->go();
111
+ } catch (Exception $e){
112
+ wfUtils::clearScanLock();
113
+ wordfence::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
114
+ wordfence::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
115
+ exit();
116
+ }
117
  wfUtils::clearScanLock();
118
  self::logPeakMemory();
119
  wordfence::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
- Version: 3.0.2
8
  Author URI: http://wordfence.com/
9
  */
10
- define('WORDFENCE_VERSION', '3.0.2');
11
  if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
  require_once('lib/wordfenceConstants.php');
13
  require_once('lib/wordfenceClass.php');
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
+ Version: 3.0.3
8
  Author URI: http://wordfence.com/
9
  */
10
+ define('WORDFENCE_VERSION', '3.0.3');
11
  if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
  require_once('lib/wordfenceConstants.php');
13
  require_once('lib/wordfenceClass.php');