Wordfence Security – Firewall & Malware Scan - Version 3.6.8

Version Description

  • Fixed bug that caused IP range blocking to not block.
  • Fixed bug that caused unblocking a permanently blocked IP to work, but not refresh the list.
  • Added usernames to the email you receive when a user is locked out.
  • Added a few more status messages for URL malware scanning.
  • Removed the sockets function call from connection testing because some hosts don't allow calls to socket_create()
  • Added detection in the Whois page to check if the server has the fsockopen() function available with helpful message if it's disabled.
  • Whitelisted IP's now override country blocking and range blocking.
  • Removed Bluehost affiliate links for free customers
  • Fixed issue that caused scans to crash when checking URLs for malware.
  • Fixed issue that caused scans with large numbers of posts that contain the same URL to crash.
  • Updated the GeoIP database for country blocking to newest version.
Download this release

Release Info

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

Code changes from version 3.6.7 to 3.6.8

css/main.css CHANGED
@@ -43,20 +43,6 @@ div.wordfenceLive p {
43
  #wfHeading {
44
  white-space: nowrap;
45
  }
46
- a.bluehostBanner {
47
- width: 120px;
48
- height: 60px;
49
- padding: 0;
50
- margin: 0px 0 0px 0px;
51
- display: block;
52
- border-width: 0;
53
- }
54
- a.bluehostBanner1 { background-image: url(../images/bh_120x60_01.gif); }
55
- a.bluehostBanner2 { background-image: url(../images/bh_120x60_02.gif); }
56
- a.bluehostBanner3 { background-image: url(../images/bh_120x60_03.gif); }
57
- a.bluehostBanner4 { background-image: url(../images/bh_120x60_04.gif); }
58
- a.bluehostBanner5 { background-image: url(../images/bh_120x60_05.gif); }
59
-
60
  div.wordfence-lock-icon {
61
  background-image: url(../images/wordfence-logo-32x32.png);
62
  }
43
  #wfHeading {
44
  white-space: nowrap;
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  div.wordfence-lock-icon {
47
  background-image: url(../images/wordfence-logo-32x32.png);
48
  }
js/admin.js CHANGED
@@ -1021,6 +1021,14 @@ window['wordfenceAdmin'] = {
1021
  self.reloadActivities();
1022
  });
1023
  },
 
 
 
 
 
 
 
 
1024
  permBlockIP: function(IP){
1025
  var self = this;
1026
  this.ajax('wordfence_permBlockIP', {
1021
  self.reloadActivities();
1022
  });
1023
  },
1024
+ unblockIPTwo: function(IP){
1025
+ var self = this;
1026
+ this.ajax('wordfence_unblockIP', {
1027
+ IP: IP
1028
+ }, function(res){
1029
+ self.staticTabChanged();
1030
+ });
1031
+ },
1032
  permBlockIP: function(IP){
1033
  var self = this;
1034
  this.ajax('wordfence_permBlockIP', {
lib/GeoIP.dat CHANGED
Binary file
lib/conntest.php CHANGED
@@ -8,31 +8,6 @@
8
  <br /><br />
9
  DNS lookup for noc1.wordfence.com returns: <?php echo gethostbyname('noc1.wordfence.com'); ?><br /><br />
10
  <?php
11
- function testSocket($service_port){
12
- echo "<b>STARTING SOCKET TEST TO PORT $service_port</b><br />\n";
13
- error_reporting(E_ALL);
14
- //$service_port = getservbyname('www', 'tcp');
15
- $address = gethostbyname('noc1.wordfence.com');
16
- $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
17
- if($socket === false){
18
- echo "Could not create socket: " . socket_strerror(socket_last_error()) . "<br />\n";
19
- } else {
20
- echo "Socket created OK<br />\n";
21
- }
22
- echo "Attempting to connect to '$address' on port '$service_port'...";
23
- $result = socket_connect($socket, $address, $service_port);
24
- if($result === false){
25
- echo "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)) . "<br /><br />\n";
26
- } else {
27
- echo "Socket connected OK to port $service_port<br /><br />\n";
28
- }
29
- socket_close($socket);
30
- }
31
- testSocket(80);
32
- testSocket(443);
33
-
34
- ?>
35
- <?php
36
  $curlContent = "";
37
  function curlWrite($h, $d){
38
  global $curlContent;
8
  <br /><br />
9
  DNS lookup for noc1.wordfence.com returns: <?php echo gethostbyname('noc1.wordfence.com'); ?><br /><br />
10
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  $curlContent = "";
12
  function curlWrite($h, $d){
13
  global $curlContent;
lib/menu_blockedIPs.php CHANGED
@@ -118,7 +118,7 @@
118
  {{/if}}
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}}
118
  {{/if}}
119
  </div>
120
  <div>
121
+ <strong>IP:</strong>&nbsp;<a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a> [<a href="#" onclick="WFAD.unblockIPTwo('${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}}
lib/menu_whois.php CHANGED
@@ -5,6 +5,21 @@
5
  <p style="width: 600px;">
6
  Wordfence WHOIS queries the WHOIS servers on the Internet and gets information about domain name or IP address owners. This helps you determine who is hacking your site and helps you report them to the relevant authorities. If you see a malicious IP address, do a WHOIS lookup, find out who is responsible for that IP address and send an email reporting them to the 'abuse' email address provided.<br /><br />
7
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  <p>
9
  <input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" />&nbsp;<input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
10
 
@@ -64,3 +79,7 @@ if(whoisval){
64
  </p>
65
  </div>
66
  </script>
 
 
 
 
5
  <p style="width: 600px;">
6
  Wordfence WHOIS queries the WHOIS servers on the Internet and gets information about domain name or IP address owners. This helps you determine who is hacking your site and helps you report them to the relevant authorities. If you see a malicious IP address, do a WHOIS lookup, find out who is responsible for that IP address and send an email reporting them to the 'abuse' email address provided.<br /><br />
7
  </p>
8
+ <?php
9
+ if(! function_exists('fsockopen')){
10
+ ?>
11
+ <p style="color: #F00; width: 600px;">
12
+ Sorry, but your web hosting provider has disabled the 'fsockopen' function on your WordPress server. That means you can't
13
+ use WHOIS. Please log a support call with them asking them to enable this function. Explain that you need it to be able to
14
+ perform Whois lookups on IP addresses which will allow you to determine who owns the IP's that are attacking your website.
15
+ <br /><br />
16
+ If you are hosting your own site, edit your php.ini config file and make sure 'fsockopen' does not appear
17
+ next to disable_functions in php.ini. You may have to restart your web server for the changes to take effect.
18
+ </p>
19
+ <?php
20
+ } else {
21
+ ?>
22
+ </p>
23
  <p>
24
  <input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" />&nbsp;<input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
25
 
79
  </p>
80
  </div>
81
  </script>
82
+ <?php
83
+ } //closing paren for function_exists('fsockopen')
84
+ ?>
85
+
lib/pageTitle.php CHANGED
@@ -1,18 +1 @@
1
- <?php if(! wfConfig::get('isPaid')){
2
- $affURL = 'http://www.bluehost.com/track/wordfence/wfplghd2?page=/wordpress';
3
- ?>
4
- <table border="0">
5
- <tr>
6
- <td style="padding-right: 50px;">
7
- <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
8
- </td><td style="width: 450px; padding-top: 10px;">
9
- You're using the Free version of Wordfence which you can support by visiting <a href="<?php echo $affURL; ?>" target="_blank">Bluehost.com</a>. We recommend <a href="<?php echo $affURL; ?>" target="_blank">Bluehost for WordPress hosting</a> and use them for our own WordPress websites. &nbsp;&nbsp;&nbsp;<a href="<?php echo $affURL; ?>" target="_blank">&raquo;Visit Bluehost now&raquo;</a>
10
- </td>
11
- <td style="width: 120px; padding-top: 10px;">
12
- <a href="<?php echo $affURL; ?>" target="_blank" class="bluehostBanner bluehostBanner<?php echo rand(1,5); ?>"></a>
13
- </td>
14
- </tr>
15
- </table>
16
- <?php } else { ?>
17
  <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
18
- <?php } ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
 
lib/wfAPI.php CHANGED
@@ -52,8 +52,9 @@ class wfAPI {
52
  curl_setopt ($curl, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
53
  curl_setopt($curl, CURLOPT_POST, true);
54
  curl_setopt($curl, CURLOPT_POSTFIELDS, $postParams);
55
-
56
  $curlResult = curl_exec($curl);
 
57
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
58
  $this->lastCurlErrorNo = curl_errno($curl);
59
  if($httpStatus == 200){
@@ -65,6 +66,7 @@ class wfAPI {
65
  throw new Exception("We received an error response when trying to contact the Wordfence scanning servers. The HTTP status code was [$httpStatus] and the curl error number was [" . $this->lastCurlErrorNo . "] " . ($cerror ? (' and the error from CURL was: ' . $cerror) : ''));
66
  }
67
  } else {
 
68
  $data = $this->fileGet($url, $postParams);
69
  if($data === false){
70
  $err = error_get_last();
@@ -117,6 +119,7 @@ class wfAPI {
117
  curl_setopt($curl, CURLOPT_POSTFIELDS, array());
118
  }
119
  $data = curl_exec($curl);
 
120
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
121
  if($httpStatus != 200){
122
  $cError = curl_error($curl);
52
  curl_setopt ($curl, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
53
  curl_setopt($curl, CURLOPT_POST, true);
54
  curl_setopt($curl, CURLOPT_POSTFIELDS, $postParams);
55
+ wordfence::status(4, 'info', "CURL fetching URL: " . $url);
56
  $curlResult = curl_exec($curl);
57
+
58
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
59
  $this->lastCurlErrorNo = curl_errno($curl);
60
  if($httpStatus == 200){
66
  throw new Exception("We received an error response when trying to contact the Wordfence scanning servers. The HTTP status code was [$httpStatus] and the curl error number was [" . $this->lastCurlErrorNo . "] " . ($cerror ? (' and the error from CURL was: ' . $cerror) : ''));
67
  }
68
  } else {
69
+ wordfence::status(4, 'info', "Fetching URL with file_get: " . $url);
70
  $data = $this->fileGet($url, $postParams);
71
  if($data === false){
72
  $err = error_get_last();
119
  curl_setopt($curl, CURLOPT_POSTFIELDS, array());
120
  }
121
  $data = curl_exec($curl);
122
+
123
  $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
124
  if($httpStatus != 200){
125
  $cError = curl_error($curl);
lib/wfLog.php CHANGED
@@ -233,7 +233,7 @@ class wfLog {
233
  }
234
  public function lockOutIP($IP, $reason){
235
  if($this->isWhitelisted($IP)){ return false; }
236
- $this->getDB()->queryWrite("insert into " . $this->lockOutTable . " (IP, blockedTime, reason) values(%s, unix_timestamp(), '%s') ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s'",
237
  wfUtils::inet_aton($IP),
238
  $reason,
239
  $reason
@@ -518,6 +518,11 @@ class wfLog {
518
  return $this->db;
519
  }
520
  public function firewallBadIPs(){
 
 
 
 
 
521
 
522
  //New range and UA pattern blocking:
523
  $r1 = $this->getDB()->querySelect("select id, blockType, blockString from " . $this->ipRangesTable);
@@ -587,7 +592,7 @@ class wfLog {
587
  } else if(strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false && (! wfConfig::get('cbl_loginFormBlocked', false)) ){ //It's the login form and we're allowing that
588
  //Do nothing
589
  } else {
590
- if($country = wfUtils::IP2Country(wfUtils::getIP()) ){
591
  foreach(explode(',', $blockedCountries) as $blocked){
592
  if(strtoupper($blocked) == strtoupper($country)){ //At this point we know the user has been blocked
593
  if(wfConfig::get('cbl_action') == 'redir'){
@@ -613,9 +618,8 @@ class wfLog {
613
  }
614
  }
615
 
616
- $IP = wfUtils::inet_aton(wfUtils::getIP());
617
- if($rec = $this->getDB()->querySingleRec("select blockedTime, reason from " . $this->blocksTable . " where IP=%s and (permanent=1 OR (blockedTime + %s > unix_timestamp()))", $IP, wfConfig::get('blockedTime'))){
618
- $this->getDB()->queryWrite("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
619
  $now = $this->getDB()->querySingle("select unix_timestamp()");
620
  $secsToGo = ($rec['blockedTime'] + wfConfig::get('blockedTime')) - $now;
621
  $this->do503($secsToGo, $rec['reason']);
233
  }
234
  public function lockOutIP($IP, $reason){
235
  if($this->isWhitelisted($IP)){ return false; }
236
+ $this->getDB()->queryWrite("insert into " . $this->lockOutTable . " (IP, blockedTime, reason) values (%s, unix_timestamp(), '%s') ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s'",
237
  wfUtils::inet_aton($IP),
238
  $reason,
239
  $reason
518
  return $this->db;
519
  }
520
  public function firewallBadIPs(){
521
+ $IP = wfUtils::getIP();
522
+ if($this->isWhitelisted($IP)){
523
+ return;
524
+ }
525
+ $IPnum = wfUtils::inet_aton($IP);
526
 
527
  //New range and UA pattern blocking:
528
  $r1 = $this->getDB()->querySelect("select id, blockType, blockString from " . $this->ipRangesTable);
592
  } else if(strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false && (! wfConfig::get('cbl_loginFormBlocked', false)) ){ //It's the login form and we're allowing that
593
  //Do nothing
594
  } else {
595
+ if($country = wfUtils::IP2Country($IP) ){
596
  foreach(explode(',', $blockedCountries) as $blocked){
597
  if(strtoupper($blocked) == strtoupper($country)){ //At this point we know the user has been blocked
598
  if(wfConfig::get('cbl_action') == 'redir'){
618
  }
619
  }
620
 
621
+ if($rec = $this->getDB()->querySingleRec("select blockedTime, reason from " . $this->blocksTable . " where IP=%s and (permanent=1 OR (blockedTime + %s > unix_timestamp()))", $IPnum, wfConfig::get('blockedTime'))){
622
+ $this->getDB()->queryWrite("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IPnum);
 
623
  $now = $this->getDB()->querySingle("select unix_timestamp()");
624
  $secsToGo = ($rec['blockedTime'] + wfConfig::get('blockedTime')) - $now;
625
  $this->do503($secsToGo, $rec['reason']);
lib/wordfenceClass.php CHANGED
@@ -363,7 +363,7 @@ class wordfence {
363
  $forgotAttempts = 1;
364
  }
365
  if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
366
- self::lockOutIP($IP, "Exceeded the maximum number of tries to recover their password which is set at: " . wfConfig::get('loginSec_maxForgotPasswd'));
367
  require('wfLockedOut.php');
368
  }
369
  set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
@@ -494,7 +494,7 @@ class wordfence {
494
  }
495
  if(wfConfig::get('loginSecurityEnabled')){
496
  if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
497
- self::lockOutIP($IP, "Used an invalid username to try to sign in.");
498
  require('wfLockedOut.php');
499
  }
500
  $tKey = 'wflginfl_' . wfUtils::inet_aton($IP);
@@ -506,7 +506,7 @@ class wordfence {
506
  $tries = 1;
507
  }
508
  if($tries >= wfConfig::get('loginSec_maxFailures')){
509
- self::lockOutIP($IP, "Exceeded the maximum number of login failures which is: " . wfConfig::get('loginSec_maxFailures'));
510
  require('wfLockedOut.php');
511
  }
512
  set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
363
  $forgotAttempts = 1;
364
  }
365
  if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
366
+ self::lockOutIP($IP, "Exceeded the maximum number of tries to recover their password which is set at: " . wfConfig::get('loginSec_maxForgotPasswd') . ". The last username or email they entered before getting locked out was: '" . $_POST['user_login'] . "'");
367
  require('wfLockedOut.php');
368
  }
369
  set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
494
  }
495
  if(wfConfig::get('loginSecurityEnabled')){
496
  if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
497
+ self::lockOutIP($IP, "Used an invalid username '" . $_POST['log'] . "' to try to sign in.");
498
  require('wfLockedOut.php');
499
  }
500
  $tKey = 'wflginfl_' . wfUtils::inet_aton($IP);
506
  $tries = 1;
507
  }
508
  if($tries >= wfConfig::get('loginSec_maxFailures')){
509
+ self::lockOutIP($IP, "Exceeded the maximum number of login failures which is: " . wfConfig::get('loginSec_maxFailures') . ". The last username they tried to sign in with was: '" . $_POST['log'] . "'");
510
  require('wfLockedOut.php');
511
  }
512
  set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
lib/wordfenceScanner.php CHANGED
@@ -50,146 +50,157 @@ class wordfenceScanner {
50
  $this->lastStatusTime = microtime(true);
51
  }
52
  $db = new wfDB();
53
- $res1 = $db->querySelect("select filename, filenameMD5, hex(newMD5) as newMD5 from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0");
54
- foreach($res1 as $rec1){
55
- $db->queryWrite("update " . $db->prefix() . "wfFileMods set oldMD5 = newMD5 where filenameMD5='%s'", $rec1['filenameMD5']); //A way to mark as scanned so that if we come back from a sleep we don't rescan this one.
56
- $file = $rec1['filename'];
57
- $fileSum = $rec1['newMD5'];
58
-
59
- if(! file_exists($this->path . $file)){
60
- continue;
61
- }
62
- $fileExt = '';
63
- if(preg_match('/\.([a-zA-Z\d\-]{1,7})$/', $file, $matches)){
64
- $fileExt = strtolower($matches[1]);
65
- }
66
- $isPHP = false;
67
- if(preg_match('/^(?:php|phtml|php\d+)$/', $fileExt)){
68
- $isPHP = true;
69
  }
70
 
71
- if(preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|gif|png)$/', $fileExt)){
72
- continue;
73
- }
74
- if(wfUtils::fileTooBig($this->path . $file)){ //We can't use filesize on 32 bit systems for files > 2 gigs
75
- //We should not need this check because files > 2 gigs are not hashed and therefore won't be received back as unknowns from the API server
76
- //But we do it anyway to be safe.
77
- wordfence::status(2, 'error', "Encountered file that is too large: $file - Skipping.");
78
- continue;
79
- }
80
- $fsize = filesize($this->path . $file); //Checked if too big above
81
- if($fsize > 1000000){
82
- $fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
83
- } else {
84
- $fsize = $fsize . "B";
85
- }
86
- if(function_exists('memory_get_usage')){
87
- wordfence::status(4, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
88
- } else {
89
- wordfence::status(4, 'info', "Scanning contents: $file (Size: $fsize)");
90
- }
91
 
92
- $stime = microtime(true);
93
- $fh = @fopen($this->path . $file, 'r');
94
- if(! $fh){
95
- continue;
96
- }
97
- $totalRead = 0;
98
- while(! feof($fh)){
99
- $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
100
- $totalRead += strlen($data);
101
- if($totalRead < 1){
102
- break;
103
  }
104
- if($isPHP){
105
- if(strpos($data, '$allowed'.'Sites') !== false && strpos($data, "define ('VER"."SION', '1.") !== false && strpos($data, "TimThum"."b script created by") !== false){
106
- $this->addResult(array(
107
- 'type' => 'file',
108
- 'severity' => 1,
109
- 'ignoreP' => $this->path . $file,
110
- 'ignoreC' => $fileSum,
111
- 'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
112
- 'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
113
- 'data' => array(
114
- 'file' => $file,
115
- 'canDiff' => false,
116
- 'canFix' => false,
117
- 'canDelete' => true
118
- )
119
- ));
120
- break;
121
- } else if(strpos($file, 'lib/wordfenceScanner.php') === false && preg_match($this->patterns['sigPattern'], $data, $matches)){
122
- $this->addResult(array(
123
- 'type' => 'file',
124
- 'severity' => 1,
125
- 'ignoreP' => $this->path . $file,
126
- 'ignoreC' => $fileSum,
127
- 'shortMsg' => "This file appears to be an attack shell",
128
- 'longMsg' => "This file appears to be an executable shell that allows hackers entry to your site via a backdoor. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . $matches[1] . "\"</strong>.",
129
- 'data' => array(
130
- 'file' => $file,
131
- 'canDiff' => false,
132
- 'canFix' => false,
133
- 'canDelete' => true
134
- )
135
- ));
136
- break;
137
 
138
- }
139
- /*
140
- $longestNospace = wfUtils::longestNospace($data);
141
- if($longestNospace > 1000 && (strpos($data, $this->patterns['pat1']) !== false || preg_match('/preg_replace\([^\(]+\/[a-z]*e/', $data)) ){
142
- $this->addResult(array(
143
- 'type' => 'file',
144
- 'severity' => 1,
145
- 'ignoreP' => $this->path . $file,
146
- 'ignoreC' => $fileSum,
147
- 'shortMsg' => "This file may contain malicious executable code",
148
- 'longMsg' => "This file is a PHP executable file and contains a line $longestNospace characters long without spaces that may be encoded data along with functions that may be used to execute that code. If you know about this file you can choose to ignore it to exclude it from future scans.",
149
- 'data' => array(
150
- 'file' => $file,
151
- 'canDiff' => false,
152
- 'canFix' => false,
153
- 'canDelete' => true
154
- )
155
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  break;
157
  }
158
- */
159
- if(preg_match($this->patterns['pat2'], $data)){
160
- $this->addResult(array(
161
- 'type' => 'file',
162
- 'severity' => 1,
163
- 'ignoreP' => $this->path . $file,
164
- 'ignoreC' => $fileSum,
165
- 'shortMsg' => "This file may contain malicious executable code",
166
- 'longMsg' => "This file is a PHP executable file and contains an " . $this->patterns['word1'] . " function and " . $this->patterns['word2'] . " decoding function on the same line. This is a common technique used by hackers to hide and execute code. If you know about this file you can choose to ignore it to exclude it from future scans.",
167
- 'data' => array(
168
- 'file' => $file,
169
- 'canDiff' => false,
170
- 'canFix' => false,
171
- 'canDelete' => true
172
- )
173
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  break;
175
  }
176
- $this->urlHoover->hoover($file, $data);
177
- } else {
178
- $this->urlHoover->hoover($file, $data);
179
  }
180
-
181
- if($totalRead > 2 * 1024 * 1024){
182
- break;
 
 
 
183
  }
 
184
  }
185
- fclose($fh);
186
- $mtime = sprintf("%.5f", microtime(true) - $stime);
187
- $this->totalFilesScanned++;
188
- if(microtime(true) - $this->lastStatusTime > 1){
189
- $this->lastStatusTime = microtime(true);
190
- $this->writeScanningStatus();
191
- }
192
- $forkObj->forkIfNeeded();
193
  }
194
  $this->writeScanningStatus();
195
  wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
50
  $this->lastStatusTime = microtime(true);
51
  }
52
  $db = new wfDB();
53
+ $keepGoing = true;
54
+ $limitOffset = 0;
55
+ $queryChunkSize = 1000;
56
+ while($keepGoing){
57
+ $keepGoing = false;
58
+ $res1 = $db->querySelect("select filename, filenameMD5, hex(newMD5) as newMD5 from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0 limit $limitOffset , $queryChunkSize");
59
+ if(sizeof($res1) > 0){
60
+ $keepGoing = true;
61
+ $limitOffset += $queryChunkSize;
 
 
 
 
 
 
 
62
  }
63
 
64
+ foreach($res1 as $rec1){
65
+ $db->queryWrite("update " . $db->prefix() . "wfFileMods set oldMD5 = newMD5 where filenameMD5='%s'", $rec1['filenameMD5']); //A way to mark as scanned so that if we come back from a sleep we don't rescan this one.
66
+ $file = $rec1['filename'];
67
+ $fileSum = $rec1['newMD5'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ if(! file_exists($this->path . $file)){
70
+ continue;
71
+ }
72
+ $fileExt = '';
73
+ if(preg_match('/\.([a-zA-Z\d\-]{1,7})$/', $file, $matches)){
74
+ $fileExt = strtolower($matches[1]);
75
+ }
76
+ $isPHP = false;
77
+ if(preg_match('/^(?:php|phtml|php\d+)$/', $fileExt)){
78
+ $isPHP = true;
 
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ if(preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|gif|png)$/', $fileExt)){
82
+ continue;
83
+ }
84
+ if(wfUtils::fileTooBig($this->path . $file)){ //We can't use filesize on 32 bit systems for files > 2 gigs
85
+ //We should not need this check because files > 2 gigs are not hashed and therefore won't be received back as unknowns from the API server
86
+ //But we do it anyway to be safe.
87
+ wordfence::status(2, 'error', "Encountered file that is too large: $file - Skipping.");
88
+ continue;
89
+ }
90
+ $fsize = filesize($this->path . $file); //Checked if too big above
91
+ if($fsize > 1000000){
92
+ $fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
93
+ } else {
94
+ $fsize = $fsize . "B";
95
+ }
96
+ if(function_exists('memory_get_usage')){
97
+ wordfence::status(4, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
98
+ } else {
99
+ wordfence::status(4, 'info', "Scanning contents: $file (Size: $fsize)");
100
+ }
101
+
102
+ $stime = microtime(true);
103
+ $fh = @fopen($this->path . $file, 'r');
104
+ if(! $fh){
105
+ continue;
106
+ }
107
+ $totalRead = 0;
108
+ while(! feof($fh)){
109
+ $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
110
+ $totalRead += strlen($data);
111
+ if($totalRead < 1){
112
  break;
113
  }
114
+ if($isPHP){
115
+ if(strpos($data, '$allowed'.'Sites') !== false && strpos($data, "define ('VER"."SION', '1.") !== false && strpos($data, "TimThum"."b script created by") !== false){
116
+ $this->addResult(array(
117
+ 'type' => 'file',
118
+ 'severity' => 1,
119
+ 'ignoreP' => $this->path . $file,
120
+ 'ignoreC' => $fileSum,
121
+ 'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
122
+ 'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
123
+ 'data' => array(
124
+ 'file' => $file,
125
+ 'canDiff' => false,
126
+ 'canFix' => false,
127
+ 'canDelete' => true
128
+ )
129
+ ));
130
+ break;
131
+ } else if(strpos($file, 'lib/wordfenceScanner.php') === false && preg_match($this->patterns['sigPattern'], $data, $matches)){
132
+ $this->addResult(array(
133
+ 'type' => 'file',
134
+ 'severity' => 1,
135
+ 'ignoreP' => $this->path . $file,
136
+ 'ignoreC' => $fileSum,
137
+ 'shortMsg' => "This file appears to be an attack shell",
138
+ 'longMsg' => "This file appears to be an executable shell that allows hackers entry to your site via a backdoor. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . $matches[1] . "\"</strong>.",
139
+ 'data' => array(
140
+ 'file' => $file,
141
+ 'canDiff' => false,
142
+ 'canFix' => false,
143
+ 'canDelete' => true
144
+ )
145
+ ));
146
+ break;
147
+
148
+ }
149
+ /*
150
+ $longestNospace = wfUtils::longestNospace($data);
151
+ if($longestNospace > 1000 && (strpos($data, $this->patterns['pat1']) !== false || preg_match('/preg_replace\([^\(]+\/[a-z]*e/', $data)) ){
152
+ $this->addResult(array(
153
+ 'type' => 'file',
154
+ 'severity' => 1,
155
+ 'ignoreP' => $this->path . $file,
156
+ 'ignoreC' => $fileSum,
157
+ 'shortMsg' => "This file may contain malicious executable code",
158
+ 'longMsg' => "This file is a PHP executable file and contains a line $longestNospace characters long without spaces that may be encoded data along with functions that may be used to execute that code. If you know about this file you can choose to ignore it to exclude it from future scans.",
159
+ 'data' => array(
160
+ 'file' => $file,
161
+ 'canDiff' => false,
162
+ 'canFix' => false,
163
+ 'canDelete' => true
164
+ )
165
+ ));
166
+ break;
167
+ }
168
+ */
169
+ if(preg_match($this->patterns['pat2'], $data)){
170
+ $this->addResult(array(
171
+ 'type' => 'file',
172
+ 'severity' => 1,
173
+ 'ignoreP' => $this->path . $file,
174
+ 'ignoreC' => $fileSum,
175
+ 'shortMsg' => "This file may contain malicious executable code",
176
+ 'longMsg' => "This file is a PHP executable file and contains an " . $this->patterns['word1'] . " function and " . $this->patterns['word2'] . " decoding function on the same line. This is a common technique used by hackers to hide and execute code. If you know about this file you can choose to ignore it to exclude it from future scans.",
177
+ 'data' => array(
178
+ 'file' => $file,
179
+ 'canDiff' => false,
180
+ 'canFix' => false,
181
+ 'canDelete' => true
182
+ )
183
+ ));
184
+ break;
185
+ }
186
+ $this->urlHoover->hoover($file, $data);
187
+ } else {
188
+ $this->urlHoover->hoover($file, $data);
189
+ }
190
+
191
+ if($totalRead > 2 * 1024 * 1024){
192
  break;
193
  }
 
 
 
194
  }
195
+ fclose($fh);
196
+ $mtime = sprintf("%.5f", microtime(true) - $stime);
197
+ $this->totalFilesScanned++;
198
+ if(microtime(true) - $this->lastStatusTime > 1){
199
+ $this->lastStatusTime = microtime(true);
200
+ $this->writeScanningStatus();
201
  }
202
+ $forkObj->forkIfNeeded();
203
  }
 
 
 
 
 
 
 
 
204
  }
205
  $this->writeScanningStatus();
206
  wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
lib/wordfenceURLHoover.php CHANGED
@@ -158,7 +158,9 @@ class wordfenceURLHoover {
158
  $this->dbg("Checking hostkey: " . bin2hex($key));
159
  }
160
  }
 
161
  $resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
 
162
  $this->dbg("Done hostkey check");
163
 
164
  $badHostKeys = array();
@@ -188,11 +190,15 @@ class wordfenceURLHoover {
188
  }
189
  if(sizeof($badHostKeys) > 0){
190
  $urlsToCheck = array();
 
191
  //need to figure out which id's have bad hostkeys
192
  //need to feed in all URL's from those id's where the hostkey matches a URL
193
  foreach($badHostKeys as $badHostKey){
194
  if($this->useDB){
195
- $q1 = $this->db->querySelect("select owner, host, path from $this->table where hostKey='%s'", $badHostKey);
 
 
 
196
  foreach($q1 as $rec){
197
  $url = 'http://' . $rec['host'] . $rec['path'];
198
  if(! isset($urlsToCheck[$rec['owner']])){
@@ -200,6 +206,7 @@ class wordfenceURLHoover {
200
  }
201
  if(! in_array($url, $urlsToCheck[$rec['owner']])){
202
  $urlsToCheck[$rec['owner']][] = $url;
 
203
  }
204
  }
205
  } else {
@@ -211,6 +218,7 @@ class wordfenceURLHoover {
211
  }
212
  if(! in_array($url, $urlsToCheck[$rec['owner']])){
213
  $urlsToCheck[$rec['owner']][] = $url;
 
214
  }
215
  }
216
  }
@@ -218,13 +226,9 @@ class wordfenceURLHoover {
218
  }
219
 
220
  if(sizeof($urlsToCheck) > 0){
221
- $this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
222
- if($this->debug){
223
- foreach($urlsToCheck as $url){
224
- $this->dbg("Checking URL: " . var_export($url, true));
225
- }
226
- }
227
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
 
228
  $this->dbg("Done URL check");
229
  if(is_array($badURLs) && sizeof($badURLs) > 0){
230
  $finalResults = array();
158
  $this->dbg("Checking hostkey: " . bin2hex($key));
159
  }
160
  }
161
+ wordfence::status(2, 'info', "Checking " . sizeof($allHostKeys) . " host keys against Wordfence scanning servers.");
162
  $resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
163
+ wordfence::status(2, 'info', "Done host key check.");
164
  $this->dbg("Done hostkey check");
165
 
166
  $badHostKeys = array();
190
  }
191
  if(sizeof($badHostKeys) > 0){
192
  $urlsToCheck = array();
193
+ $totalURLs = 0;
194
  //need to figure out which id's have bad hostkeys
195
  //need to feed in all URL's from those id's where the hostkey matches a URL
196
  foreach($badHostKeys as $badHostKey){
197
  if($this->useDB){
198
+ //Putting a 10000 limit in here for sites that have a huge number of items with the same URL that repeats.
199
+ // This is an edge case. But if the URLs are malicious then presumably the admin will fix the malicious URLs
200
+ // and on subsequent scans the items (owners) that are above the 10000 limit will appear.
201
+ $q1 = $this->db->querySelect("select owner, host, path from $this->table where hostKey='%s' limit 10000", $badHostKey);
202
  foreach($q1 as $rec){
203
  $url = 'http://' . $rec['host'] . $rec['path'];
204
  if(! isset($urlsToCheck[$rec['owner']])){
206
  }
207
  if(! in_array($url, $urlsToCheck[$rec['owner']])){
208
  $urlsToCheck[$rec['owner']][] = $url;
209
+ $totalURLs++;
210
  }
211
  }
212
  } else {
218
  }
219
  if(! in_array($url, $urlsToCheck[$rec['owner']])){
220
  $urlsToCheck[$rec['owner']][] = $url;
221
+ $totalURLs++;
222
  }
223
  }
224
  }
226
  }
227
 
228
  if(sizeof($urlsToCheck) > 0){
229
+ wordfence::status(2, 'info', "Checking " . $totalURLs . " URLs from " . sizeof($urlsToCheck) . " sources.");
 
 
 
 
 
230
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
231
+ wordfence::status(2, 'info', "Done URL check.");
232
  $this->dbg("Done URL check");
233
  if(is_array($badURLs) && sizeof($badURLs) > 0){
234
  $finalResults = array();
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.5.1
6
- Stable tag: 3.6.7
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
 
@@ -155,6 +155,19 @@ or a theme, because often these have been updated to fix a security hole.
155
 
156
  == Changelog ==
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  = 3.6.7 =
159
  * Improved security for Cloudflare customers to prevent spoofing attacks and protect when a hacker bypasses Cloudflare proxies.
160
  * Added clear explanation of what increasing AJAX polling time does on options page.
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.5.1
6
+ Stable tag: 3.6.8
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
 
155
 
156
  == Changelog ==
157
 
158
+ = 3.6.8 =
159
+ * Fixed bug that caused IP range blocking to not block.
160
+ * Fixed bug that caused unblocking a permanently blocked IP to work, but not refresh the list.
161
+ * Added usernames to the email you receive when a user is locked out.
162
+ * Added a few more status messages for URL malware scanning.
163
+ * Removed the sockets function call from connection testing because some hosts don't allow calls to socket_create()
164
+ * Added detection in the Whois page to check if the server has the fsockopen() function available with helpful message if it's disabled.
165
+ * Whitelisted IP's now override country blocking and range blocking.
166
+ * Removed Bluehost affiliate links for free customers
167
+ * Fixed issue that caused scans to crash when checking URLs for malware.
168
+ * Fixed issue that caused scans with large numbers of posts that contain the same URL to crash.
169
+ * Updated the GeoIP database for country blocking to newest version.
170
+
171
  = 3.6.7 =
172
  * Improved security for Cloudflare customers to prevent spoofing attacks and protect when a hacker bypasses Cloudflare proxies.
173
  * Added clear explanation of what increasing AJAX polling time does on options page.
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
- Version: 3.6.7
8
  Author URI: http://www.wordfence.com/
9
  */
10
- define('WORDFENCE_VERSION', '3.6.7');
11
  if(get_option('wordfenceActivated') != 1){
12
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
13
  }
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
+ Version: 3.6.8
8
  Author URI: http://www.wordfence.com/
9
  */
10
+ define('WORDFENCE_VERSION', '3.6.8');
11
  if(get_option('wordfenceActivated') != 1){
12
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
13
  }