Wordfence Security – Firewall & Malware Scan - Version 2.1.1

Version Description

  • Added ability to permanently block IP's
  • Added ability to manually block IP's
  • Made Wordfence more memory efficient, particularly the forking process.
  • Fixed issue that caused WF to not work on databases with blank passwords.
  • Wordfence now stops execution of a DB connection error is encountered.
  • Clear cron jobs if Wordfence is uninstalled.
  • Enabled hourly cron for Wordfence security network.
  • Wordfence now works if your server doesn't have openssl installed
  • Wordfence now works even if you don't have CURL
  • Fixed visitor logging so it works with HTTPS websites.
  • Alert emails now contain filenames in each alert description.
  • Users with weak passwords alerts now contain the username in the email.
  • Upgraded API to 1.7.
  • Fixed issue that caused DISALLOW_FILE_MODS to make WF menu disappear.
  • Modified wfDB to deal with very large queries without exceeding max_allowed_packet
  • Fixed issue that broke ability to see file changes and repair files.
Download this release

Release Info

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

Code changes from version 2.1.0 to 2.1.1

js/admin.js CHANGED
@@ -177,7 +177,7 @@ window['wordfenceAdmin'] = {
177
summaryUpdated = true;
178
} else if(item.msg.indexOf('SUM_ENDERR') != -1){
179
var msg = item.msg.replace('SUM_ENDERR:', '');
180
- jQuery('div.wfSummaryMsg:contains("' + msg + '")').next().addClass('wfSummaryErr').html('An error occured.');
181
summaryUpdated = true;
182
} else if(item.msg.indexOf('SUM_DISABLED:') != -1){
183
var msg = item.msg.replace('SUM_DISABLED:', '');
@@ -337,7 +337,7 @@ window['wordfenceAdmin'] = {
337
return;
338
} else if(res.errorAlert){
339
jQuery.colorbox({ width: '400px', html:
340
- "<h3>An error occured:</h3><p>" + res.errorAlert + "</p>"
341
});
342
}
343
@@ -476,7 +476,7 @@ window['wordfenceAdmin'] = {
476
self.nonce = json.nonce;
477
}
478
if(json && json.errorMsg){
479
- self.colorbox('400px', 'An error occured', json.errorMsg);
480
}
481
cb(json);
482
},
@@ -498,7 +498,7 @@ window['wordfenceAdmin'] = {
498
jQuery.colorbox({ width: width, html: "<h3>" + heading + "</h3><p>" + body + "</p>"});
499
},
500
scanRunningMsg: function(){ this.colorbox('400px', "A scan is running", "A scan is currently in progress. Please wait until it finishes before starting another scan."); },
501
- errorMsg: function(msg){ this.colorbox('400px', "An error occured:", msg); },
502
deleteFile: function(issueID){
503
var self = this;
504
this.ajax('wordfence_deleteFile', {
@@ -511,7 +511,7 @@ window['wordfenceAdmin'] = {
511
if(res.ok){
512
this.loadIssues(function(){ self.colorbox('400px', "Success deleting file", "The file " + res.file + " containing " + res.filesize + " bytes was successfully deleted."); });
513
} else if(res.cerrorMsg){
514
- this.loadIssues(function(){ self.colorbox('400px', 'An error occured', res.cerrorMsg); });
515
}
516
},
517
restoreFile: function(issueID){
@@ -525,7 +525,7 @@ window['wordfenceAdmin'] = {
525
if(res.ok){
526
this.loadIssues(function(){ self.colorbox("400px", "File restored OK", "The file " + res.file + " was restored succesfully."); });
527
} else if(res.cerrorMsg){
528
- this.loadIssues(function(){ self.colorbox('400px', 'An error occured', res.cerrorMsg); });
529
}
530
},
531
deleteIssue: function(id){
@@ -761,6 +761,19 @@ window['wordfenceAdmin'] = {
761
}
762
});
763
},
764
unlockOutIP: function(IP){
765
var self = this;
766
this.ajax('wordfence_unlockOutIP', {
@@ -773,6 +786,12 @@ window['wordfenceAdmin'] = {
773
IP: IP
774
}, function(res){ self.staticTabChanged(); });
775
},
776
makeElemID: function(){
777
return 'wfElemGen' + this.elementGeneratorIter++;
778
},
@@ -801,7 +820,7 @@ window['wordfenceAdmin'] = {
801
} else if(res.errorMsg){
802
return;
803
} else {
804
- self.colorbox('400px', 'An error occured', 'We encountered an error trying to save your changes.');
805
}
806
});
807
},
177
summaryUpdated = true;
178
} else if(item.msg.indexOf('SUM_ENDERR') != -1){
179
var msg = item.msg.replace('SUM_ENDERR:', '');
180
+ jQuery('div.wfSummaryMsg:contains("' + msg + '")').next().addClass('wfSummaryErr').html('An error occurred.');
181
summaryUpdated = true;
182
} else if(item.msg.indexOf('SUM_DISABLED:') != -1){
183
var msg = item.msg.replace('SUM_DISABLED:', '');
337
return;
338
} else if(res.errorAlert){
339
jQuery.colorbox({ width: '400px', html:
340
+ "<h3>An error occurred:</h3><p>" + res.errorAlert + "</p>"
341
});
342
}
343
476
self.nonce = json.nonce;
477
}
478
if(json && json.errorMsg){
479
+ self.colorbox('400px', 'An error occurred', json.errorMsg);
480
}
481
cb(json);
482
},
498
jQuery.colorbox({ width: width, html: "<h3>" + heading + "</h3><p>" + body + "</p>"});
499
},
500
scanRunningMsg: function(){ this.colorbox('400px', "A scan is running", "A scan is currently in progress. Please wait until it finishes before starting another scan."); },
501
+ errorMsg: function(msg){ this.colorbox('400px', "An error occurred:", msg); },
502
deleteFile: function(issueID){
503
var self = this;
504
this.ajax('wordfence_deleteFile', {
511
if(res.ok){
512
this.loadIssues(function(){ self.colorbox('400px', "Success deleting file", "The file " + res.file + " containing " + res.filesize + " bytes was successfully deleted."); });
513
} else if(res.cerrorMsg){
514
+ this.loadIssues(function(){ self.colorbox('400px', 'An error occurred', res.cerrorMsg); });
515
}
516
},
517
restoreFile: function(issueID){
525
if(res.ok){
526
this.loadIssues(function(){ self.colorbox("400px", "File restored OK", "The file " + res.file + " was restored succesfully."); });
527
} else if(res.cerrorMsg){
528
+ this.loadIssues(function(){ self.colorbox('400px', 'An error occurred', res.cerrorMsg); });
529
}
530
},
531
deleteIssue: function(id){
761
}
762
});
763
},
764
+ blockIPTwo: function(IP, reason){
765
+ var self = this;
766
+ this.ajax('wordfence_blockIP', {
767
+ IP: IP,
768
+ reason: reason
769
+ }, function(res){
770
+ if(res.errorMsg){
771
+ return;
772
+ } else {
773
+ self.staticTabChanged();
774
+ }
775
+ });
776
+ },
777
unlockOutIP: function(IP){
778
var self = this;
779
this.ajax('wordfence_unlockOutIP', {
786
IP: IP
787
}, function(res){ self.staticTabChanged(); });
788
},
789
+ permBlockIP: function(IP){
790
+ var self = this;
791
+ this.ajax('wordfence_permBlockIP', {
792
+ IP: IP
793
+ }, function(res){ self.staticTabChanged(); });
794
+ },
795
makeElemID: function(){
796
return 'wfElemGen' + this.elementGeneratorIter++;
797
},
820
} else if(res.errorMsg){
821
return;
822
} else {
823
+ self.colorbox('400px', 'An error occurred', 'We encountered an error trying to save your changes.');
824
}
825
});
826
},
lib/menu_blockedIPs.php CHANGED
@@ -7,7 +7,8 @@
7
</table>
8
</div>
9
<div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
10
- <a href="#" onclick="WFAD.clearAllBlocked('blocked'); return false;">Clear all blocked IP addresses</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#" onclick="WFAD.clearAllBlocked('locked'); return false;">Clear all locked out IP addresses</a>
11
</div>
12
<div class="wordfenceWrap">
13
<div>
@@ -118,6 +119,9 @@
118
</div>
119
<div>
120
<strong>IP:</strong>&nbsp;<a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a> [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">unblock</a>]
121
</div>
122
<div>
123
<strong>Reason:</strong>&nbsp;${reason}
@@ -142,7 +146,10 @@
142
<ul>
143
<li>${totalHits} hits before blocked</li>
144
<li>${blockedHits} blocked hits</li>
145
- <li>Will be unblocked in ${blockedForAgo}</li>
146
</ul>
147
</td></tr>
148
{{/each}}
7
</table>
8
</div>
9
<div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
10
+ <a href="#" onclick="WFAD.clearAllBlocked('blocked'); return false;">Clear all blocked IP addresses</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#" onclick="WFAD.clearAllBlocked('locked'); return false;">Clear all locked out IP addresses</a><br />
11
+ You can manually block an IP by entering the address here: <input type="text" id="wfManualBlock" size="20" maxlength="40" value="" />&nbsp;<input type="button" name="but1" value="Manually block IP" onclick="WFAD.blockIPTwo(jQuery('#wfManualBlock').val(), 'Manual block by administrator'); return false;" />
12
</div>
13
<div class="wordfenceWrap">
14
<div>
119
</div>
120
<div>
121
<strong>IP:</strong>&nbsp;<a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a> [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">unblock</a>]
122
+ {{if permanent == '1'}}
123
+ [<span style="color: #F00;">permanently blocked</span>]
124
+ {{else}}&nbsp;&nbsp;[<a href="#" onclick="WFAD.permBlockIP('${IP}'); return false;">make permanent</a>]{{/if}}
125
</div>
126
<div>
127
<strong>Reason:</strong>&nbsp;${reason}
146
<ul>
147
<li>${totalHits} hits before blocked</li>
148
<li>${blockedHits} blocked hits</li>
149
+ <li>
150
+ {{if permanent == '1'}}Permanently blocked{{else}}
151
+ Will be unblocked in ${blockedForAgo}{{/if}}
152
+ </li>
153
</ul>
154
</td></tr>
155
{{/each}}
lib/wfAPI.php CHANGED
@@ -16,7 +16,7 @@ class wfAPI {
16
}
17
public function call($action, $getParams = array(), $postParams = array()){
18
$this->errorMsg = false;
19
- $json = $this->getURL(WORDFENCE_API_URL . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&' . http_build_query(
20
array_merge(
21
array('action' => $action),
22
$getParams
@@ -76,7 +76,7 @@ class wfAPI {
76
return false;
77
}
78
} else {
79
- $data = @file_get_contents ($url);
80
if($data === false){
81
$err = error_get_last();
82
$this->lastURLError = $err;
@@ -86,24 +86,55 @@ class wfAPI {
86
}
87
88
}
89
public function binCall($func, $postData){
90
$this->errorMsg = false;
91
- $url = WORDFENCE_API_URL . '/v' . WORDFENCE_API_VERSION . '/?' . $this->makeAPIQueryString() . '&action=' . $func;
92
- $curl = curl_init($url);
93
- curl_setopt ($curl, CURLOPT_TIMEOUT, 300);
94
- //curl_setopt($curl, CURLOPT_VERBOSE, true);
95
- curl_setopt ($curl, CURLOPT_USERAGENT, "Wordfence");
96
- curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
97
- curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, false);
98
- curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, false);
99
- curl_setopt($curl, CURLOPT_POST, true);
100
- if($postData){
101
- curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
102
- } else {
103
- curl_setopt($curl, CURLOPT_POSTFIELDS, array());
104
- }
105
- $data = curl_exec($curl);
106
- $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
107
if(preg_match('/\{.*errorMsg/', $data)){
108
$jdat = @json_decode($data, true);
109
if(is_array($jdat) && $jdat['errorMsg']){
@@ -124,6 +155,14 @@ class wfAPI {
124
'k' => $this->APIKey
125
));
126
}
127
}
128
129
?>
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
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;
86
}
87
88
}
89
+ private function fileGet($url, $postParams){
90
+ $body = "";
91
+ if(is_array($postParams)){
92
+ $bodyArr = array();
93
+ foreach($postParams as $key => $val){
94
+ $bodyArr[] = urlencode($key) . '=' . urlencode($val);
95
+ }
96
+ $body = implode('&', $bodyArr);
97
+ } else {
98
+ $body = $postParams;
99
+ }
100
+ $opts = array('http' =>
101
+ array(
102
+ 'method' => 'POST',
103
+ 'content' => $body,
104
+ 'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
105
+ 'timeout' => 60
106
+ )
107
+ );
108
+ $context = stream_context_create($opts);
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);
116
+ curl_setopt ($curl, CURLOPT_TIMEOUT, 300);
117
+ //curl_setopt($curl, CURLOPT_VERBOSE, true);
118
+ curl_setopt ($curl, CURLOPT_USERAGENT, "Wordfence");
119
+ curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
120
+ curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, false);
121
+ curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, false);
122
+ curl_setopt($curl, CURLOPT_POST, true);
123
+ if($postData){
124
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
125
+ } else {
126
+ curl_setopt($curl, CURLOPT_POSTFIELDS, array());
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']){
155
'k' => $this->APIKey
156
));
157
}
158
+ private function getAPIURL(){
159
+ $ssl_supported = false;
160
+ if(defined('CURL_VERSION_SSL') && function_exists('curl_version')){
161
+ $version = curl_version();
162
+ $ssl_supported = ($version['features'] & CURL_VERSION_SSL);
163
+ }
164
+ return $ssl_supported ? WORDFENCE_API_URL_SEC : WORDFENCE_API_URL_NONSEC;
165
+ }
166
}
167
168
?>
lib/wfConfig.php CHANGED
@@ -366,6 +366,27 @@ class wfConfig {
366
public static function clearCache(){
367
self::$cache = array();
368
}
369
public static function set($key, $val){
370
if(is_array($val)){
371
$trace=debug_backtrace(); $caller=array_shift($trace); error_log("wfConfig::set() got array as second param. Please use set_ser(). " . $caller['file'] . " line " . $caller['line']);
@@ -389,14 +410,22 @@ class wfConfig {
389
return self::$cache[$key];
390
}
391
public static function get_ser($key, $default){
392
- $val = self::get($key, $default);
393
- if($val){
394
- $val = unserialize($val);
395
}
396
- return $val;
397
- }
398
- public static function set_ser($key, $val){
399
- return self::set($key, serialize($val));
400
}
401
public static function f($key){
402
echo esc_attr(self::get($key));
366
public static function clearCache(){
367
self::$cache = array();
368
}
369
+ public static function set_ser($key, $val){
370
+ //We serialize some very big values so this is ultra-memory efficient. We don't make any copies of $val and don't use ON DUPLICATE KEY UPDATE
371
+ // because we would have to concatenate $val twice into the query which could also exceed max packet for the mysql server
372
+ $dbh = self::getDB()->getDBH();
373
+ $exists = self::getDB()->querySingle("select name from " . self::table() . " where name='%s'", $key);
374
+ if($exists){
375
+ wordfence::status(4, 'info', "set_ser() updating key $key with value to be serialized that has type: " . gettype($val));
376
+ $res = mysql_query("update " . self::table() . " set val='" . mysql_real_escape_string(serialize($val)) . "' where name='" . mysql_real_escape_string($key) . "'", $dbh);
377
+ } else {
378
+ wordfence::status(4, 'info', "set_ser() inserting key $key with value to be serialized that has type: " . gettype($val));
379
+ $res = mysql_query("insert IGNORE into " . self::table() . " (name, val) values ('" . mysql_real_escape_string($key) . "', '" . mysql_real_escape_string(serialize($val)) . "')", $dbh);
380
+ }
381
+ $err = mysql_error();
382
+ if($err){
383
+ $trace=debug_backtrace();
384
+ $caller=array_shift($trace);
385
+ wordfence::status(2, 'error', "Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
386
+ return false;
387
+ }
388
+ return true;
389
+ }
390
public static function set($key, $val){
391
if(is_array($val)){
392
$trace=debug_backtrace(); $caller=array_shift($trace); error_log("wfConfig::set() got array as second param. Please use set_ser(). " . $caller['file'] . " line " . $caller['line']);
410
return self::$cache[$key];
411
}
412
public static function get_ser($key, $default){
413
+ $dbh = self::getDB()->getDBH();
414
+ $res = mysql_query("select val from " . self::table() . " where name='" . mysql_real_escape_string($key) . "'", $dbh);
415
+ $err = mysql_error();
416
+ if($err){
417
+ $trace=debug_backtrace();
418
+ $caller=array_shift($trace);
419
+ wordfence::status(2, 'error', "Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
420
+ return false;
421
}
422
+
423
+ if(mysql_num_rows($res) > 0){
424
+ $row = mysql_fetch_row($res);
425
+ wordfence::status(4, 'info', "get_ser() Unserializing key $key with data length " . strlen($row[0]));
426
+ return unserialize($row[0]);
427
+ }
428
+ return $default;
429
}
430
public static function f($key){
431
echo esc_attr(self::get($key));
lib/wfDB.php CHANGED
@@ -16,14 +16,12 @@ class wfDB {
16
} else {
17
global $wpdb;
18
if(! $wpdb){
19
- $this->errorMsg = "The WordPress variable wpdb is not defined.";
20
- return;
21
}
22
- if(! $wpdb->dbhost ){ $this->errorMsg = "The WordPress variable from wpdb dbhost is not defined."; }
23
- if(! $wpdb->dbuser ){ $this->errorMsg = "The WordPress variable from wpdb dbuser is not defined."; }
24
- if(! $wpdb->dbpassword ){ $this->errorMsg = "The WordPress variable from wpdb dbpassword is not defined."; }
25
- if(! $wpdb->dbname ){ $this->errorMsg = "The WordPress variable from wpdb dbname is not defined."; }
26
- if($this->errorMsg){ return; }
27
$this->dbhost = $wpdb->dbhost;
28
$this->dbuser = $wpdb->dbuser;
29
$this->dbpassword = $wpdb->dbpassword;
@@ -32,12 +30,13 @@ class wfDB {
32
if($createNewHandle){
33
$dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
34
if($dbh === false){
35
- $this->errorMsg = "Could not connect to database on " . $this->dbhost . " with user " . $this->dbuser;
36
- return;
37
}
38
mysql_select_db($this->dbname, $dbh);
39
$this->dbh = $dbh;
40
$this->query("SET NAMES 'utf8'");
41
} else {
42
$handleKey = md5($dbhost . $dbuser . $dbpassword . $dbname);
43
if(isset(self::$dbhCache[$handleKey])){
@@ -45,14 +44,15 @@ class wfDB {
45
} else {
46
$dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
47
if($dbh === false){
48
- $this->errorMsg = "Could not connect to database on " . $this->dbhost . " with user " . $this->dbuser;
49
- return;
50
}
51
52
mysql_select_db($this->dbname, $dbh);
53
self::$dbhCache[$handleKey] = $dbh;
54
$this->dbh = self::$dbhCache[$handleKey];
55
$this->query("SET NAMES 'utf8'");
56
}
57
}
58
}
@@ -68,9 +68,9 @@ class wfDB {
68
}
69
$res = mysql_query($query, $this->dbh);
70
$err = mysql_error();
71
- if($err){
72
$this->errorMsg = $err;
73
- $trace=debug_backtrace(); $caller=array_shift($trace); error_log("Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
74
}
75
return mysql_fetch_assoc($res); //returns false if no rows found
76
}
@@ -89,9 +89,9 @@ class wfDB {
89
}
90
$res = mysql_query($query, $this->dbh);
91
$err = mysql_error();
92
- if($err){
93
$this->errorMsg = $err;
94
- $trace=debug_backtrace(); $caller=array_shift($trace); error_log("Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
95
}
96
if(! $res){
97
return false;
@@ -103,26 +103,48 @@ class wfDB {
103
public function query(){ //sprintfString, arguments
104
$this->errorMsg = false;
105
$args = func_get_args();
106
if(sizeof($args) == 1){
107
- $query = $args[0];
108
} else if(sizeof($args) > 1){
109
for($i = 1; $i < sizeof($args); $i++){
110
$args[$i] = mysql_real_escape_string($args[$i]);
111
}
112
- $query = call_user_func_array('sprintf', $args);
113
} else {
114
wfdie("No arguments passed to query()");
115
}
116
- $res = mysql_query($query, $this->dbh);
117
$err = mysql_error();
118
- if($err){
119
$this->errorMsg = $err;
120
- $trace=debug_backtrace(); $caller=array_shift($trace); error_log("Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
121
}
122
return $res;
123
}
124
private function wfdie($msg){
125
- error_log($msg);
126
exit(1);
127
}
128
public function createKeyIfNotExists($table, $col, $keyName){
@@ -144,6 +166,7 @@ class wfDB {
144
$this->query("alter table $table add KEY $keyName($col)");
145
}
146
}
147
}
148
149
?>
16
} else {
17
global $wpdb;
18
if(! $wpdb){
19
+ self::wfdie("The WordPress variable wpdb is not defined.");
20
}
21
+ if(! $wpdb->dbhost ){ self::wfdie("The WordPress variable from wpdb dbhost is not defined."); }
22
+ if(! $wpdb->dbuser ){ self::wfdie("The WordPress variable from wpdb dbuser is not defined."); }
23
+ if(! isset($wpdb->dbpassword) ){ self::wfdie("The WordPress variable from wpdb dbpassword is not defined."); }
24
+ if(! $wpdb->dbname ){ self::wfdie("The WordPress variable from wpdb dbname is not defined."); }
25
$this->dbhost = $wpdb->dbhost;
26
$this->dbuser = $wpdb->dbuser;
27
$this->dbpassword = $wpdb->dbpassword;
30
if($createNewHandle){
31
$dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
32
if($dbh === false){
33
+ self::wfdie("Could not connect to database on " . $this->dbhost . " with user " . $this->dbuser . ' : ' . mysql_error());
34
}
35
mysql_select_db($this->dbname, $dbh);
36
$this->dbh = $dbh;
37
$this->query("SET NAMES 'utf8'");
38
+ //Set big packets for set_ser when it serializes a scan in between forks
39
+ $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
40
} else {
41
$handleKey = md5($dbhost . $dbuser . $dbpassword . $dbname);
42
if(isset(self::$dbhCache[$handleKey])){
44
} else {
45
$dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
46
if($dbh === false){
47
+ self::wfdie("Could not connect to database on " . $this->dbhost . " with user " . $this->dbuser . ' : ' . mysql_error());
48
}
49
50
mysql_select_db($this->dbname, $dbh);
51
self::$dbhCache[$handleKey] = $dbh;
52
$this->dbh = self::$dbhCache[$handleKey];
53
$this->query("SET NAMES 'utf8'");
54
+ //Set big packets for set_ser when it serializes a scan in between forks
55
+ $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
56
}
57
}
58
}
68
}
69
$res = mysql_query($query, $this->dbh);
70
$err = mysql_error();
71
+ if( (! preg_match('/Wordfence DB error/i', $query)) && $err){ //prevent loops
72
$this->errorMsg = $err;
73
+ $trace=debug_backtrace(); $caller=array_shift($trace); wordfence::status(2, 'error', "Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
74
}
75
return mysql_fetch_assoc($res); //returns false if no rows found
76
}
89
}
90
$res = mysql_query($query, $this->dbh);
91
$err = mysql_error();
92
+ if( (! preg_match('/Wordfence DB error/i', $query)) && $err){
93
$this->errorMsg = $err;
94
+ $trace=debug_backtrace(); $caller=array_shift($trace); wordfence::status(2, 'error', "Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
95
}
96
if(! $res){
97
return false;
103
public function query(){ //sprintfString, arguments
104
$this->errorMsg = false;
105
$args = func_get_args();
106
+ $isStatusQuery = false;
107
if(sizeof($args) == 1){
108
+ if(preg_match('/Wordfence DB error/i', $args[0])){
109
+ $isStatusQuery = true;
110
+ }
111
+ $res = mysql_query($args[0], $this->dbh);
112
} else if(sizeof($args) > 1){
113
for($i = 1; $i < sizeof($args); $i++){
114
+ if(preg_match('/Wordfence DB error/i', $args[$i])){
115
+ $isStatusQuery = true;
116
+ }
117
$args[$i] = mysql_real_escape_string($args[$i]);
118
}
119
+ $res = mysql_query(call_user_func_array('sprintf', $args), $this->dbh);
120
} else {
121
wfdie("No arguments passed to query()");
122
}
123
$err = mysql_error();
124
+ if( (! $isStatusQuery) && $err){ //isStatusQuery prevents loops if status itself is causing error
125
$this->errorMsg = $err;
126
+ $trace=debug_backtrace(); $caller=array_shift($trace); wordfence::status(2, 'error', "Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
127
+ }
128
+ return $res;
129
+ }
130
+ public function queryIgnoreError(){ //sprintfString, arguments
131
+ $this->errorMsg = false;
132
+ $args = func_get_args();
133
+ if(sizeof($args) == 1){
134
+ $res = mysql_query($args[0], $this->dbh);
135
+ } else if(sizeof($args) > 1){
136
+ for($i = 1; $i < sizeof($args); $i++){
137
+ $args[$i] = mysql_real_escape_string($args[$i]);
138
+ }
139
+ $res = mysql_query(call_user_func_array('sprintf', $args), $this->dbh);
140
+ } else {
141
+ wfdie("No arguments passed to query()");
142
}
143
return $res;
144
}
145
+
146
private function wfdie($msg){
147
+ error_log("Wordfence critical database error: $msg");
148
exit(1);
149
}
150
public function createKeyIfNotExists($table, $col, $keyName){
166
$this->query("alter table $table add KEY $keyName($col)");
167
}
168
}
169
+ public function getDBH(){ return $this->dbh; }
170
}
171
172
?>
lib/wfLog.php CHANGED
@@ -156,15 +156,18 @@ class wfLog {
156
public function unblockIP($IP){
157
$this->getDB()->query("delete from " . $this->blocksTable . " where IP=%s", wfUtils::inet_aton($IP));
158
}
159
- public function blockIP($IP, $reason, $wfsn = false){
160
if($this->isWhitelisted($IP)){ return false; }
161
$wfsn = $wfsn ? 1 : 0;
162
- $this->getDB()->query("insert into " . $this->blocksTable . " (IP, blockedTime, reason, wfsn) values (%s, unix_timestamp(), '%s', %d) ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s', wfsn=%d",
163
wfUtils::inet_aton($IP),
164
$reason,
165
$wfsn,
166
$reason,
167
- $wfsn
168
);
169
return true;
170
}
@@ -217,7 +220,7 @@ class wfLog {
217
return $results;
218
}
219
public function getBlockedIPs(){
220
- $res = $this->getDB()->query("select IP, unix_timestamp() - blockedTime as createdAgo, reason, unix_timestamp() - lastAttempt as lastAttemptAgo, lastAttempt, blockedHits, (blockedTime + %s) - unix_timestamp() as blockedFor from " . $this->blocksTable . " where blockedTime + %s > unix_timestamp() order by blockedTime desc", wfConfig::get('blockedTime'), wfConfig::get('blockedTime'));
221
$results = array();
222
while($elem = mysql_fetch_assoc($res)){
223
$lastHitAgo = 0;
@@ -468,7 +471,7 @@ class wfLog {
468
}
469
public function firewallBadIPs(){
470
$IP = wfUtils::inet_aton(wfUtils::getIP());
471
- if($secsToGo = $this->getDB()->querySingle("select (blockedTime + %s) - unix_timestamp() as secsToGo from " . $this->blocksTable . " where IP=%s and blockedTime + %s > unix_timestamp()", wfConfig::get('blockedTime'), $IP, wfConfig::get('blockedTime'))){
472
$this->getDB()->query("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
473
$this->do503($secsToGo);
474
}
@@ -500,10 +503,12 @@ class wfLog {
500
return;
501
}
502
}
503
- private function do503($secsToGo){
504
header('HTTP/1.1 503 Service Temporarily Unavailable');
505
header('Status: 503 Service Temporarily Unavailable');
506
- header('Retry-After: ' . $secsToGo);
507
require_once('wf503.php');
508
exit();
509
}
156
public function unblockIP($IP){
157
$this->getDB()->query("delete from " . $this->blocksTable . " where IP=%s", wfUtils::inet_aton($IP));
158
}
159
+ public function blockIP($IP, $reason, $wfsn = false, $permanent = false){ //wfsn indicates it comes from Wordfence secure network
160
if($this->isWhitelisted($IP)){ return false; }
161
$wfsn = $wfsn ? 1 : 0;
162
+ $permanent = $permanent ? 1 : 0;
163
+ $this->getDB()->query("insert into " . $this->blocksTable . " (IP, blockedTime, reason, wfsn, permanent) values (%s, unix_timestamp(), '%s', %d, %d) ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s', wfsn=%d, permanent=%d",
164
wfUtils::inet_aton($IP),
165
$reason,
166
$wfsn,
167
+ $permanent,
168
$reason,
169
+ $wfsn,
170
+ $permanent
171
);
172
return true;
173
}
220
return $results;
221
}
222
public function getBlockedIPs(){
223
+ $res = $this->getDB()->query("select IP, unix_timestamp() - blockedTime as createdAgo, reason, unix_timestamp() - lastAttempt as lastAttemptAgo, lastAttempt, blockedHits, (blockedTime + %s) - unix_timestamp() as blockedFor, permanent from " . $this->blocksTable . " where blockedTime + %s > unix_timestamp() order by blockedTime desc", wfConfig::get('blockedTime'), wfConfig::get('blockedTime'));
224
$results = array();
225
while($elem = mysql_fetch_assoc($res)){
226
$lastHitAgo = 0;
471
}
472
public function firewallBadIPs(){
473
$IP = wfUtils::inet_aton(wfUtils::getIP());
474
+ 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'))){
475
$this->getDB()->query("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
476
$this->do503($secsToGo);
477
}
503
return;
504
}
505
}
506
+ private function do503($secsToGo = false){
507
header('HTTP/1.1 503 Service Temporarily Unavailable');
508
header('Status: 503 Service Temporarily Unavailable');
509
+ if($secsToGo){
510
+ header('Retry-After: ' . $secsToGo);
511
+ }
512
require_once('wf503.php');
513
exit();
514
}
lib/wfScanEngine.php CHANGED
@@ -359,10 +359,10 @@ class wfScanEngine {
359
$type = $this->scanData[$idString]['type'];
360
foreach($hresults as $result){
361
if($result['badList'] == 'goog-malware-shavar'){
362
- $shortMsg = "$uctype contains a suspected malware URL.";
363
$longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
364
} else if($result['badList'] == 'googpub-phish-shavar'){
365
- $shortMsg = "$uctype contains a suspected phishing site URL.";
366
$longMsg = "This $type contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'];
367
} else {
368
//A list type that may be new and the plugin has not been upgraded yet.
@@ -456,7 +456,7 @@ class wfScanEngine {
456
$type = $this->scanData[$idString]['type'];
457
foreach($hresults as $result){
458
if($result['badList'] == 'goog-malware-shavar'){
459
- $shortMsg = "$uctype contains a suspected malware URL.";
460
$longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
461
} else if($result['badList'] == 'googpub-phish-shavar'){
462
$shortMsg = "$uctype contains a suspected phishing site URL.";
@@ -593,12 +593,12 @@ class wfScanEngine {
593
$level = 1;
594
$highCap = $this->highestCap($userDat->wp_capabilities);
595
if($this->isEditor($userDat->wp_capabilities)){
596
- $shortMsg = "A user with '" . $highCap . "' access has an easy password.";
597
$longMsg = "A user with the a role of '" . $highCap . "' has a password that is easy to guess. Please change this password yourself or ask the user to change it.";
598
$level = 1;
599
$words = $this->dictWords;
600
} else {
601
- $shortMsg = "A user with 'subscriber' access has a very easy password.";
602
$longMsg = "A user with 'subscriber' access has a password that is very easy to guess. Please either change it or ask the user to change their password.";
603
$level = 2;
604
$words = array($userDat->user_login);
359
$type = $this->scanData[$idString]['type'];
360
foreach($hresults as $result){
361
if($result['badList'] == 'goog-malware-shavar'){
362
+ $shortMsg = "$uctype contains a suspected malware URL: " . $this->scanData[$idString]['title'];
363
$longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
364
} else if($result['badList'] == 'googpub-phish-shavar'){
365
+ $shortMsg = "$uctype contains a suspected phishing site URL: " . $this->scanData[$idString]['title'];
366
$longMsg = "This $type contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'];
367
} else {
368
//A list type that may be new and the plugin has not been upgraded yet.
456
$type = $this->scanData[$idString]['type'];
457
foreach($hresults as $result){
458
if($result['badList'] == 'goog-malware-shavar'){
459
+ $shortMsg = "$uctype with author " . $this->scanData[$idString]['author'] . " contains a suspected malware URL.";
460
$longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
461
} else if($result['badList'] == 'googpub-phish-shavar'){
462
$shortMsg = "$uctype contains a suspected phishing site URL.";
593
$level = 1;
594
$highCap = $this->highestCap($userDat->wp_capabilities);
595
if($this->isEditor($userDat->wp_capabilities)){
596
+ $shortMsg = "User \"" . $userDat->user_login . "\" with \"" . $highCap . "\" access has an easy password.";
597
$longMsg = "A user with the a role of '" . $highCap . "' has a password that is easy to guess. Please change this password yourself or ask the user to change it.";
598
$level = 1;
599
$words = $this->dictWords;
600
} else {
601
+ $shortMsg = "User \"" . $userDat->user_login . "\" with 'subscriber' access has a very easy password.";
602
$longMsg = "A user with 'subscriber' access has a password that is very easy to guess. Please either change it or ask the user to change their password.";
603
$level = 2;
604
$words = array($userDat->user_login);
lib/wfSchema.php CHANGED
@@ -20,11 +20,12 @@ class wfSchema {
20
lastAttempt int UNSIGNED default 0,
21
blockedHits int UNSIGNED default 0,
22
wfsn tinyint UNSIGNED default 0,
23
KEY k1(wfsn)
24
) default charset=utf8",
25
"wfConfig" => "(
26
name varchar(100) PRIMARY KEY NOT NULL,
27
- val text
28
) default charset=utf8",
29
"wfCrawlers" => "(
30
IP INT UNSIGNED NOT NULL,
20
lastAttempt int UNSIGNED default 0,
21
blockedHits int UNSIGNED default 0,
22
wfsn tinyint UNSIGNED default 0,
23
+ permanent tinyint UNSIGNED default 0,
24
KEY k1(wfsn)
25
) default charset=utf8",
26
"wfConfig" => "(
27
name varchar(100) PRIMARY KEY NOT NULL,
28
+ val longblob
29
) default charset=utf8",
30
"wfCrawlers" => "(
31
IP INT UNSIGNED NOT NULL,
lib/wfUtils.php CHANGED
@@ -181,7 +181,7 @@ class wfUtils {
181
return true;
182
}
183
} else {
184
- if(current_user_can('update_core')){
185
return true;
186
}
187
}
181
return true;
182
}
183
} else {
184
+ if(current_user_can('manage_options')){
185
return true;
186
}
187
}
lib/wordfenceClass.php CHANGED
@@ -31,6 +31,9 @@ class wordfence {
31
public static function uninstallPlugin(){
32
//Used by MU code below
33
update_option('wordfenceActivated', 0);
34
}
35
public static function hourlyCron(){
36
global $wpdb; $p = $wpdb->base_prefix;
@@ -44,38 +47,41 @@ class wordfence {
44
45
if(wfConfig::get('other_WFNet')){
46
$wfdb = new wfDB();
47
- $q1 = $wfdb->query("select URI from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600 limit 1000");
48
$URIs = array();
49
while($rec = mysql_fetch_assoc($q1)){
50
array_push($URIs, $rec['URI']);
51
}
52
- $wfdb->query("truncate table $p"."wfVulnScanners");
53
- $this->api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) ));
54
55
$q2 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
56
- $wfdb->query("truncate table $p"."wfVulnScanners");
57
$scanCont = "";
58
while($rec = mysql_fetch_assoc($q2)){
59
$scanCont .= pack('N', ip2long($rec['IP']));
60
}
61
62
$q3 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfLockedOut where blockedTime > unix_timestamp() - 3600");
63
$lockCont = "";
64
while($rec = mysql_fetch_assoc($q3)){
65
$lockCont .= pack('N', ip2long($rec['IP']));
66
}
67
- $cont = pack('N', strlen($lockCont) / 4) . $lockCont . pack('N', strlen($scanCont) / 4) . $scanCont;
68
-
69
- $resp = $this->api->binCall('get_net_bad_ips', $cont);
70
- if($resp['code'] == 200){
71
- $len = strlen($resp['data']);
72
- $reason = "WFSN: Blocked by Wordfence Security Network";
73
- $wfdb->query("delete from $p"."wfBlocks where wfsn=1");
74
- if($len > 0 && $len % 4 == 0){
75
- for($i = 0; $i < $len; $i += 4){
76
- list($ipLong) = array_values(unpack('N', substr($resp['data'], $i, 4)));
77
- $IPStr = long2ip($ipLong);
78
- self::getLog()->blockIP($IPStr, $reason, true);
79
}
80
}
81
}
@@ -86,7 +92,7 @@ class wordfence {
86
global $wpdb; $p = $wpdb->base_prefix;
87
$wfdb->query("delete from $p"."wfLocs where ctime < unix_timestamp() - %d", WORDFENCE_MAX_IPLOC_AGE);
88
$wfdb->query("truncate table $p"."wfBadLeechers"); //only uses date that's less than 1 minute old
89
- $wfdb->query("delete from $p"."wfBlocks where blockedTime + %s < unix_timestamp()", wfConfig::get('blockedTime'));
90
$wfdb->query("delete from $p"."wfCrawlers where lastUpdate < unix_timestamp() - (86400 * 7)");
91
92
if(wfConfig::get('liveTraf_hitsMaxSize') && wfConfig::get('liveTraf_hitsMaxSize') > 0){
@@ -139,7 +145,6 @@ class wordfence {
139
}
140
public static function runInstall(){
141
//EVERYTHING HERE MUST BE IDEMPOTENT
142
-
143
$schema = new wfSchema();
144
$schema->createAll(); //if not exists
145
wfConfig::setDefaults(); //If not set
@@ -156,14 +161,10 @@ class wordfence {
156
die("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
157
}
158
}
159
-
160
-
161
- if( !wp_next_scheduled( 'wordfence_daily_cron' )){
162
- wp_schedule_event(time(), 'daily', 'wordfence_daily_cron');
163
- }
164
- if( !wp_next_scheduled( 'wordfence_hourly_cron' )){
165
- wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron');
166
- }
167
$db = new wfDB();
168
169
//Upgrading from 1.5.6 or earlier needs:
@@ -183,7 +184,8 @@ class wordfence {
183
184
global $wpdb;
185
$prefix = $wpdb->base_prefix;
186
- $db->query("alter table $prefix"."wfConfig modify column val longblob");
187
188
//Must be the final line
189
update_option('wordfence_version', WORDFENCE_VERSION);
@@ -469,6 +471,7 @@ class wordfence {
469
}
470
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
471
$contResult = $api->binCall('get_wp_file_content', array(
472
'file' => $file,
473
'cType' => $cType,
474
'cName' => $cName,
@@ -652,6 +655,11 @@ class wordfence {
652
self::getLog()->unblockIP($IP);
653
return array('ok' => 1);
654
}
655
public static function ajax_loadStaticPanel_callback(){
656
$mode = $_POST['mode'];
657
$wfLog = self::getLog();
@@ -667,7 +675,10 @@ class wordfence {
667
return array('ok' => 1, 'results' => $results);
668
}
669
public static function ajax_blockIP_callback(){
670
- $IP = $_POST['IP'];
671
if($IP == wfUtils::getIP()){
672
return array('err' => 1, 'errorMsg' => "You can't block your own IP address.");
673
}
@@ -864,7 +875,7 @@ class wordfence {
864
return array("ok" => 1);
865
}
866
} else {
867
- return array('errorAlert' => "An unknown error occured trying to activate Wordfence. Please try again in a few minutes." );
868
}
869
}
870
public static function ajax_scan_callback(){
@@ -984,7 +995,7 @@ class wordfence {
984
exit();
985
}
986
public static function wp_head(){
987
- echo '<script type="text/javascript">var wfHTImg = new Image(); wfHTImg.src="' . wfUtils::getBaseURL() . 'visitor.php?hid=' . wfUtils::encrypt(self::$hitID) . '";</script>';
988
}
989
public static function shutdownAction(){
990
}
@@ -1087,7 +1098,7 @@ class wordfence {
1087
}
1088
public static function admin_init(){
1089
if(! wfUtils::isAdmin()){ return; }
1090
- foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){
1091
add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1092
}
1093
@@ -1237,7 +1248,7 @@ class wordfence {
1237
public static function scheduleNextScan($force = false){
1238
if(wfConfig::get('scheduledScansEnabled')){
1239
$nextScan = wp_next_scheduled('wordfence_scheduled_scan');
1240
- if((! $force) && $nextScan && $nextScan - time() > 0){
1241
//scan is already scheduled for the future
1242
return;
1243
}
@@ -1251,7 +1262,7 @@ class wordfence {
1251
$secsToGo = $result['secsToGo'];
1252
}
1253
wp_clear_scheduled_hook('wordfence_scheduled_scan');
1254
- wp_schedule_single_event(time() + $secsToGo, 'wordfence_scheduled_scan');
1255
} else {
1256
wp_clear_scheduled_hook('wordfence_scheduled_scan');
1257
}
31
public static function uninstallPlugin(){
32
//Used by MU code below
33
update_option('wordfenceActivated', 0);
34
+ wp_clear_scheduled_hook('wordfence_daily_cron');
35
+ wp_clear_scheduled_hook('wordfence_hourly_cron');
36
+ wp_clear_scheduled_hook('wordfence_scheduled_scan');
37
}
38
public static function hourlyCron(){
39
global $wpdb; $p = $wpdb->base_prefix;
47
48
if(wfConfig::get('other_WFNet')){
49
$wfdb = new wfDB();
50
+ $q1 = $wfdb->query("select URI from $p"."wfNet404s where ctime > unix_timestamp() - 3600 limit 1000");
51
$URIs = array();
52
while($rec = mysql_fetch_assoc($q1)){
53
array_push($URIs, $rec['URI']);
54
}
55
+ $wfdb->query("truncate table $p"."wfNet404s");
56
+ if(sizeof($URIs) > 0){
57
+ $api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) ));
58
+ }
59
60
$q2 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600");
61
$scanCont = "";
62
while($rec = mysql_fetch_assoc($q2)){
63
$scanCont .= pack('N', ip2long($rec['IP']));
64
}
65
+ $wfdb->query("truncate table $p"."wfVulnScanners");
66
67
$q3 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfLockedOut where blockedTime > unix_timestamp() - 3600");
68
$lockCont = "";
69
while($rec = mysql_fetch_assoc($q3)){
70
$lockCont .= pack('N', ip2long($rec['IP']));
71
}
72
+ if(strlen($lockCont) > 0 || strlen($scanCont) > 0){
73
+ $cont = pack('N', strlen($lockCont) / 4) . $lockCont . pack('N', strlen($scanCont) / 4) . $scanCont;
74
+ $resp = $api->binCall('get_net_bad_ips', $cont);
75
+ if($resp['code'] == 200){
76
+ $len = strlen($resp['data']);
77
+ $reason = "WFSN: Blocked by Wordfence Security Network";
78
+ $wfdb->query("delete from $p"."wfBlocks where wfsn=1");
79
+ if($len > 0 && $len % 4 == 0){
80
+ for($i = 0; $i < $len; $i += 4){
81
+ list($ipLong) = array_values(unpack('N', substr($resp['data'], $i, 4)));
82
+ $IPStr = long2ip($ipLong);
83
+ self::getLog()->blockIP($IPStr, $reason, true);
84
+ }
85
}
86
}
87
}
92
global $wpdb; $p = $wpdb->base_prefix;
93
$wfdb->query("delete from $p"."wfLocs where ctime < unix_timestamp() - %d", WORDFENCE_MAX_IPLOC_AGE);
94
$wfdb->query("truncate table $p"."wfBadLeechers"); //only uses date that's less than 1 minute old
95
+ $wfdb->query("delete from $p"."wfBlocks where blockedTime + %s < unix_timestamp() and permanent=0", wfConfig::get('blockedTime'));
96
$wfdb->query("delete from $p"."wfCrawlers where lastUpdate < unix_timestamp() - (86400 * 7)");
97
98
if(wfConfig::get('liveTraf_hitsMaxSize') && wfConfig::get('liveTraf_hitsMaxSize') > 0){
145
}
146
public static function runInstall(){
147
//EVERYTHING HERE MUST BE IDEMPOTENT
148
$schema = new wfSchema();
149
$schema->createAll(); //if not exists
150
wfConfig::setDefaults(); //If not set
161
die("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
162
}
163
}
164
+ wp_clear_scheduled_hook('wordfence_daily_cron');
165
+ wp_clear_scheduled_hook('wordfence_hourly_cron');
166
+ wp_schedule_event(current_time('timestamp'), 'daily', 'wordfence_daily_cron');
167
+ wp_schedule_event(current_time('timestamp'), 'hourly', 'wordfence_hourly_cron');
168
$db = new wfDB();
169
170
//Upgrading from 1.5.6 or earlier needs:
184
185
global $wpdb;
186
$prefix = $wpdb->base_prefix;
187
+ $db->queryIgnoreError("alter table $prefix"."wfConfig modify column val longblob");
188
+ $db->queryIgnoreError("alter table $prefix"."wfBlocks add column permanent tinyint UNSIGNED default 0");
189
190
//Must be the final line
191
update_option('wordfence_version', WORDFENCE_VERSION);
471
}
472
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
473
$contResult = $api->binCall('get_wp_file_content', array(
474
+ 'v' => wfUtils::getWPVersion(),
475
'file' => $file,
476
'cType' => $cType,
477
'cName' => $cName,
655
self::getLog()->unblockIP($IP);
656
return array('ok' => 1);
657
}
658
+ public static function ajax_permBlockIP_callback(){
659
+ $IP = $_POST['IP'];
660
+ self::getLog()->blockIP($IP, "Manual permanent block by admin", false, true);
661
+ return array('ok' => 1);
662
+ }
663
public static function ajax_loadStaticPanel_callback(){
664
$mode = $_POST['mode'];
665
$wfLog = self::getLog();
675
return array('ok' => 1, 'results' => $results);
676
}
677
public static function ajax_blockIP_callback(){
678
+ $IP = trim($_POST['IP']);
679
+ if(! preg_match('/^\d+\.\d+\.\d+\.\d+#x2F;', $IP)){
680
+ return array('err' => 1, 'errorMsg' => "Please enter a valid IP address to block.");
681
+ }
682
if($IP == wfUtils::getIP()){
683
return array('err' => 1, 'errorMsg' => "You can't block your own IP address.");
684
}
875
return array("ok" => 1);
876
}
877
} else {
878
+ return array('errorAlert' => "An unknown error occurred trying to activate Wordfence. Please try again in a few minutes." );
879
}
880
}
881
public static function ajax_scan_callback(){
995
exit();
996
}
997
public static function wp_head(){
998
+ echo '<script type="text/javascript">var src="' . wfUtils::getBaseURL() . 'visitor.php?hid=' . wfUtils::encrypt(self::$hitID) . '"; if(window.location.protocol == "https:"){ src = src.replace("http:", "https:"); } var wfHTImg = new Image(); wfHTImg.src=src;</script>';
999
}
1000
public static function shutdownAction(){
1001
}
1098
}
1099
public static function admin_init(){
1100
if(! wfUtils::isAdmin()){ return; }
1101
+ foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'permBlockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){
1102
add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1103
}
1104
1248
public static function scheduleNextScan($force = false){
1249
if(wfConfig::get('scheduledScansEnabled')){
1250
$nextScan = wp_next_scheduled('wordfence_scheduled_scan');
1251
+ if((! $force) && $nextScan && $nextScan - current_time('timestamp') > 0){
1252
//scan is already scheduled for the future
1253
return;
1254
}
1262
$secsToGo = $result['secsToGo'];
1263
}
1264
wp_clear_scheduled_hook('wordfence_scheduled_scan');
1265
+ wp_schedule_single_event(current_time('timestamp') + $secsToGo, 'wordfence_scheduled_scan');
1266
} else {
1267
wp_clear_scheduled_hook('wordfence_scheduled_scan');
1268
}
lib/wordfenceConstants.php CHANGED
@@ -1,6 +1,7 @@
1
<?php
2
- define('WORDFENCE_API_VERSION', 1.6);
3
- define('WORDFENCE_API_URL', 'https://noc1.wordfence.com/');
4
define('WORDFENCE_MAX_SCAN_TIME', 600);
5
define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
6
define('WORDFENCE_MAX_IPLOC_AGE', 604800); //1 week
1
<?php
2
+ define('WORDFENCE_API_VERSION', 1.7);
3
+ define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
+ define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
define('WORDFENCE_MAX_SCAN_TIME', 600);
6
define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
7
define('WORDFENCE_MAX_IPLOC_AGE', 604800); //1 week
lib/wordfenceScanner.php CHANGED
@@ -139,7 +139,7 @@ class wordfenceScanner {
139
'severity' => 1,
140
'ignoreP' => $this->path . $file,
141
'ignoreC' => md5_file($this->path . $file),
142
- 'shortMsg' => "File contains suspected malware URL.",
143
'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes base64 when scanning files so the URL may not be visible if you view this file. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
144
'data' => array(
145
'file' => $file,
@@ -155,7 +155,7 @@ class wordfenceScanner {
155
'severity' => 1,
156
'ignoreP' => $this->path . $file,
157
'ignoreC' => md5_file($this->path . $file),
158
- 'shortMsg' => "File contains suspected phishing URL.",
159
'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'],
160
'data' => array(
161
'file' => $file,
@@ -177,7 +177,7 @@ class wordfenceScanner {
177
'severity' => 1,
178
'ignoreP' => $ignoreP,
179
'ignoreC' => $ignoreC,
180
- 'shortMsg' => "File contains $encoding encoded programming language.",
181
'longMsg' => "This file contains programming language code that has been encoded using $encoding. This is often used by hackers to hide their tracks.",
182
'data' => array(
183
'file' => $file,
139
'severity' => 1,
140
'ignoreP' => $this->path . $file,
141
'ignoreC' => md5_file($this->path . $file),
142
+ 'shortMsg' => "File contains suspected malware URL: " . $this->path . $file,
143
'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes base64 when scanning files so the URL may not be visible if you view this file. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
144
'data' => array(
145
'file' => $file,
155
'severity' => 1,
156
'ignoreP' => $this->path . $file,
157
'ignoreC' => md5_file($this->path . $file),
158
+ 'shortMsg' => "File contains suspected phishing URL: " . $this->path . $file,
159
'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'],
160
'data' => array(
161
'file' => $file,
177
'severity' => 1,
178
'ignoreP' => $ignoreP,
179
'ignoreC' => $ignoreC,
180
+ 'shortMsg' => "File contains $encoding encoded programming language: " . $file,
181
'longMsg' => "This file contains programming language code that has been encoded using $encoding. This is often used by hackers to hide their tracks.",
182
'data' => array(
183
'file' => $file,
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.3.2
6
- Stable tag: 2.1.0
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
@@ -130,7 +130,7 @@ and as long as it does that, it may become face a security vulnerability at some
130
131
= Will Wordfence protect me against the Timthumb security problem? =
132
133
- The timthumb security exploit occured in 2011 and all good plugins and themes now use an updated
134
version of timthumb (which the creator of Wordfence wrote and donated to the timthumb author) which closes the security hole that
135
caused the problem. However we do scan for old version of timthumb for good measure to make sure they don't
136
cause a security hole on your site.
@@ -152,6 +152,24 @@ 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
= 2.1.0 =
156
* Fixed scans hanging on Dreamhost and other hosts.
157
* Made Wordfence more memory efficient.
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.3.2
6
+ Stable tag: 2.1.1
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
130
131
= Will Wordfence protect me against the Timthumb security problem? =
132
133
+ The timthumb security exploit occurred in 2011 and all good plugins and themes now use an updated
134
version of timthumb (which the creator of Wordfence wrote and donated to the timthumb author) which closes the security hole that
135
caused the problem. However we do scan for old version of timthumb for good measure to make sure they don't
136
cause a security hole on your site.
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
+ = 2.1.1 =
156
+ * Added ability to permanently block IP's
157
+ * Added ability to manually block IP's
158
+ * Made Wordfence more memory efficient, particularly the forking process.
159
+ * Fixed issue that caused WF to not work on databases with blank passwords.
160
+ * Wordfence now stops execution of a DB connection error is encountered.
161
+ * Clear cron jobs if Wordfence is uninstalled.
162
+ * Enabled hourly cron for Wordfence security network.
163
+ * Wordfence now works if your server doesn't have openssl installed
164
+ * Wordfence now works even if you don't have CURL
165
+ * Fixed visitor logging so it works with HTTPS websites.
166
+ * Alert emails now contain filenames in each alert description.
167
+ * Users with weak passwords alerts now contain the username in the email.
168
+ * Upgraded API to 1.7.
169
+ * Fixed issue that caused DISALLOW_FILE_MODS to make WF menu disappear.
170
+ * Modified wfDB to deal with very large queries without exceeding max_allowed_packet
171
+ * Fixed issue that broke ability to see file changes and repair files.
172
+
173
= 2.1.0 =
174
* Fixed scans hanging on Dreamhost and other hosts.
175
* Made Wordfence more memory efficient.
wfscan.php CHANGED
@@ -86,11 +86,12 @@ class wfScan {
86
$scan = wfConfig::get_ser('wfsd_engine', false);
87
if($scan){
88
//Set false so that we don't get stuck in a loop where we're repeating scan stages.
89
wfConfig::set('wfsd_engine', '');
90
} else {
91
if($isFork){ //We encountered an error so blank scan and exit
92
- wordfence::status(2, 'error', "Scan could not continue because the stored data could not be retrieved after a fork.");
93
- //wfConfig::set('wfsd_engine', '');
94
exit();
95
} else {
96
wordfence::statusPrep(); //Re-initializes all status counters
86
$scan = wfConfig::get_ser('wfsd_engine', false);
87
if($scan){
88
//Set false so that we don't get stuck in a loop where we're repeating scan stages.
89
+ wordfence::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
90
wfConfig::set('wfsd_engine', '');
91
} else {
92
if($isFork){ //We encountered an error so blank scan and exit
93
+ wordfence::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
94
+ wfConfig::set('wfsd_engine', '');
95
exit();
96
} else {
97
wordfence::statusPrep(); //Re-initializes all status counters
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: 2.1.0
8
Author URI: http://wordfence.com/
9
*/
10
- define('WORDFENCE_VERSION', '2.1.0');
11
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: 2.1.1
8
Author URI: http://wordfence.com/
9
*/
10
+ define('WORDFENCE_VERSION', '2.1.1');
11
12
require_once('lib/wordfenceConstants.php');
13
require_once('lib/wordfenceClass.php');