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+#x2F;', $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+#x2F;', $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');