Wordfence Security – Firewall & Malware Scan - Version 2.0.3

Version Description

  • Removed unbuffered queries and switched to conventional queries that are memory efficient for better stability.
  • Made scanning large numbers of URL's contained in things like awstats log files extremely memory efficient and way faster.
  • Removed alerts about unknown files in core directory if they belong to an older wordpress version and are unchanged.
  • Other performance improvements like using strpos instead of strstr.
  • Moved "scan files outside base dir" option to be in correct place on config page.
Download this release

Release Info

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

Code changes from version 2.0.2 to 2.0.3

lib/menu_options.php CHANGED
@@ -75,6 +75,7 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
75
  <tr><th>Check the strength of passwords</th><td><input type="checkbox" id="scansEnabled_passwds" class="wfConfigElem" name="scansEnabled_passwds" value="1" <?php $w->cb('scansEnabled_passwds'); ?>/></td></tr>
76
  <tr><th>Monitor disk space</th><td><input type="checkbox" id="scansEnabled_diskSpace" class="wfConfigElem" name="scansEnabled_diskSpace" value="1" <?php $w->cb('scansEnabled_diskSpace'); ?>/></td></tr>
77
  <tr><th>Scan for unauthorized DNS changes</th><td><input type="checkbox" id="scansEnabled_dns" class="wfConfigElem" name="scansEnabled_dns" value="1" <?php $w->cb('scansEnabled_dns'); ?>/></td></tr>
 
78
  <tr><td colspan="2">
79
  <h3 class="wfConfigHeading">Firewall Rules</h3>
80
  </td></tr>
@@ -185,7 +186,6 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
185
  <tr><th>Scan comments for malware and phishing URL's</th><td><input type="checkbox" id="other_scanComments" class="wfConfigElem" name="other_scanComments" value="1" <?php $w->cb('other_scanComments'); ?> /></td></tr>
186
  <tr><th>Check password strength on profile update</th><td><input type="checkbox" id="other_pwStrengthOnUpdate" class="wfConfigElem" name="other_pwStrengthOnUpdate" value="1" <?php $w->cb('other_pwStrengthOnUpdate'); ?> /></td></tr>
187
  <tr><th>Participate in the Wordfence Security Network</th><td><input type="checkbox" id="other_WFNet" class="wfConfigElem" name="other_WFNet" value="1" <?php $w->cb('other_WFNet'); ?> /></td></tr>
188
- <tr><th>Scan files outside your WordPress installation</th><td><input type="checkbox" id="other_scanOutside" class="wfConfigElem" name="other_scanOutside" value="1" <?php $w->cb('other_scanOutside'); ?> /></td></tr>
189
  <tr><th>Your Wordfence API Key</th><td><input type="text" id="apiKey" name="apiKey" value="<?php $w->f('apiKey'); ?>" size="20" /></td></tr>
190
  <tr><th>Maximum memory Wordfence can use</th><td><input type="text" id="maxMem" name="maxMem" value="<?php $w->f('maxMem'); ?>" size="4" />Megabytes</td></tr>
191
  <tr><th colspan="2"><a href="/?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Click to view your system's configuration in a new window</a></th></tr>
75
  <tr><th>Check the strength of passwords</th><td><input type="checkbox" id="scansEnabled_passwds" class="wfConfigElem" name="scansEnabled_passwds" value="1" <?php $w->cb('scansEnabled_passwds'); ?>/></td></tr>
76
  <tr><th>Monitor disk space</th><td><input type="checkbox" id="scansEnabled_diskSpace" class="wfConfigElem" name="scansEnabled_diskSpace" value="1" <?php $w->cb('scansEnabled_diskSpace'); ?>/></td></tr>
77
  <tr><th>Scan for unauthorized DNS changes</th><td><input type="checkbox" id="scansEnabled_dns" class="wfConfigElem" name="scansEnabled_dns" value="1" <?php $w->cb('scansEnabled_dns'); ?>/></td></tr>
78
+ <tr><th>Scan files outside your WordPress installation</th><td><input type="checkbox" id="other_scanOutside" class="wfConfigElem" name="other_scanOutside" value="1" <?php $w->cb('other_scanOutside'); ?> /></td></tr>
79
  <tr><td colspan="2">
80
  <h3 class="wfConfigHeading">Firewall Rules</h3>
81
  </td></tr>
186
  <tr><th>Scan comments for malware and phishing URL's</th><td><input type="checkbox" id="other_scanComments" class="wfConfigElem" name="other_scanComments" value="1" <?php $w->cb('other_scanComments'); ?> /></td></tr>
187
  <tr><th>Check password strength on profile update</th><td><input type="checkbox" id="other_pwStrengthOnUpdate" class="wfConfigElem" name="other_pwStrengthOnUpdate" value="1" <?php $w->cb('other_pwStrengthOnUpdate'); ?> /></td></tr>
188
  <tr><th>Participate in the Wordfence Security Network</th><td><input type="checkbox" id="other_WFNet" class="wfConfigElem" name="other_WFNet" value="1" <?php $w->cb('other_WFNet'); ?> /></td></tr>
 
189
  <tr><th>Your Wordfence API Key</th><td><input type="text" id="apiKey" name="apiKey" value="<?php $w->f('apiKey'); ?>" size="20" /></td></tr>
190
  <tr><th>Maximum memory Wordfence can use</th><td><input type="text" id="maxMem" name="maxMem" value="<?php $w->f('maxMem'); ?>" size="4" />Megabytes</td></tr>
191
  <tr><th colspan="2"><a href="/?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Click to view your system's configuration in a new window</a></th></tr>
lib/wfAPI.php CHANGED
@@ -27,7 +27,6 @@ class wfAPI {
27
  } else {
28
  $this->errorMsg = "We could not fetch data from the API when calling '$action': " . $this->lastURLError;
29
  }
30
- wordfence::status(3, 'error', "API Error: " . $this->errorMsg);
31
  return false;
32
  }
33
 
@@ -38,11 +37,6 @@ class wfAPI {
38
  if(empty($dat['errorMsg']) === false){
39
  $this->errorMsg = $dat['errorMsg'];
40
  }
41
- if($this->errorMsg){
42
- wordfence::status(3, 'error', "API Error: " . $this->errorMsg);
43
- } else {
44
- //wordfence::status(3, 'info', "Completed API call: $action");
45
- }
46
  return $dat;
47
  }
48
  public function curlWrite($h, $d){
@@ -114,7 +108,6 @@ class wfAPI {
114
  $jdat = @json_decode($data, true);
115
  if(is_array($jdat) && $jdat['errorMsg']){
116
  $this->errorMsg = $jdat['errorMsg'];
117
- wordfence::status(3, 'error', "Error in binary API call $func: " . $this->errorMsg);
118
  return false;
119
  }
120
  }
27
  } else {
28
  $this->errorMsg = "We could not fetch data from the API when calling '$action': " . $this->lastURLError;
29
  }
 
30
  return false;
31
  }
32
 
37
  if(empty($dat['errorMsg']) === false){
38
  $this->errorMsg = $dat['errorMsg'];
39
  }
 
 
 
 
 
40
  return $dat;
41
  }
42
  public function curlWrite($h, $d){
108
  $jdat = @json_decode($data, true);
109
  if(is_array($jdat) && $jdat['errorMsg']){
110
  $this->errorMsg = $jdat['errorMsg'];
 
111
  return false;
112
  }
113
  }
lib/wfDB.php CHANGED
@@ -14,7 +14,7 @@ class wfDB {
14
  $this->dbname = $dbname;
15
  } else {
16
  global $wpdb;
17
- if(! $wpdb){ die("Not running under wordpress. Please supply db creditials to constructor."); }
18
  $this->dbhost = $wpdb->dbhost;
19
  $this->dbuser = $wpdb->dbuser;
20
  $this->dbpassword = $wpdb->dbpassword;
@@ -94,25 +94,6 @@ class wfDB {
94
  }
95
  return $res;
96
  }
97
- public function uQuery(){ //sprintfString, arguments NOTE: Very important that there is no other DB activity between uQuery and when you call mysql_free_result on the return value of uQuery.
98
- $args = func_get_args();
99
- if(sizeof($args) == 1){
100
- $query = $args[0];
101
- } else if(sizeof($args) > 1){
102
- for($i = 1; $i < sizeof($args); $i++){
103
- $args[$i] = mysql_real_escape_string($args[$i]);
104
- }
105
- $query = call_user_func_array('sprintf', $args);
106
- } else {
107
- wfdie("No arguments passed to query()");
108
- }
109
- $res = mysql_unbuffered_query($query, $this->dbh);
110
- $err = mysql_error();
111
- if($err){
112
- $trace=debug_backtrace(); $caller=array_shift($trace); error_log("Wordfence DB error in " . $caller['file'] . " line " . $caller['line'] . ": $err");
113
- }
114
- return $res;
115
- }
116
  private function wfdie($msg){
117
  error_log($msg);
118
  exit(1);
14
  $this->dbname = $dbname;
15
  } else {
16
  global $wpdb;
17
+ if(! $wpdb){ die("Not running under wordpress. Please supply db credentials to constructor."); }
18
  $this->dbhost = $wpdb->dbhost;
19
  $this->dbuser = $wpdb->dbuser;
20
  $this->dbpassword = $wpdb->dbpassword;
94
  }
95
  return $res;
96
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  private function wfdie($msg){
98
  error_log($msg);
99
  exit(1);
lib/wfScanEngine.php CHANGED
@@ -274,11 +274,11 @@ class wfScanEngine {
274
  $statusIDX = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
275
  global $wpdb;
276
  $wfdb = new wfDB();
277
- //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
278
- $q1 = $wfdb->uQuery("select ID, post_title, post_type, post_date, post_content from $wpdb->posts where post_type IN ('page', 'post') and post_status = 'publish'");
279
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
280
  $postDat = array();
281
- while($row = mysql_fetch_assoc($q1)){
 
282
  $h->hoover($row['ID'], $row['post_title'] . ' ' . $row['post_content']);
283
  $postDat[$row['ID']] = array(
284
  'contentMD5' => md5($row['post_content']),
@@ -288,7 +288,6 @@ class wfScanEngine {
288
  );
289
 
290
  }
291
- mysql_free_result($q1);
292
  $this->status(2, 'info', "Examining URLs found in posts we scanned for dangerous websites");
293
  $hooverResults = $h->getBaddies();
294
  $this->status(2, 'info', "Done examining URls");
@@ -370,17 +369,20 @@ class wfScanEngine {
370
  $statusIDX = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
371
  global $wpdb;
372
  $wfdb = new wfDB();
373
- //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
374
- $q1 = $wfdb->uQuery("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from $wpdb->comments where comment_approved=1");
375
  if( ! $q1){
376
  wordfence::statusEndErr();
377
  return;
378
  }
 
 
 
 
 
379
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
380
  $commentDat = array();
381
- $gotRow = false;
382
- while($row = mysql_fetch_assoc($q1)){
383
- $gotRow = true; //because we can't use mysql_num_rows on unbuffered queries
384
  $h->hoover($row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content']);
385
  $commentDat[$row['comment_ID']] = array(
386
  'contentMD5' => md5($row['comment_content'] . $row['comment_author'] . $row['comment_author_url']),
@@ -389,11 +391,6 @@ class wfScanEngine {
389
  'date' => $row['comment_date']
390
  );
391
  }
392
- mysql_free_result($q1);
393
- if(! $gotRow){
394
- wordfence::statusEnd($statusIDX, false);
395
- return;
396
- }
397
  $hooverResults = $h->getBaddies();
398
  if($h->errorMsg){
399
  $this->errorStop($h->errorMsg);
274
  $statusIDX = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
275
  global $wpdb;
276
  $wfdb = new wfDB();
277
+ $q1 = $wfdb->query("select ID from $wpdb->posts where post_type IN ('page', 'post') and post_status = 'publish'");
 
278
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
279
  $postDat = array();
280
+ while($idRow = mysql_fetch_assoc($q1)){
281
+ $row = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from $wpdb->posts where ID=%d", $idRow['ID']);
282
  $h->hoover($row['ID'], $row['post_title'] . ' ' . $row['post_content']);
283
  $postDat[$row['ID']] = array(
284
  'contentMD5' => md5($row['post_content']),
288
  );
289
 
290
  }
 
291
  $this->status(2, 'info', "Examining URLs found in posts we scanned for dangerous websites");
292
  $hooverResults = $h->getBaddies();
293
  $this->status(2, 'info', "Done examining URls");
369
  $statusIDX = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
370
  global $wpdb;
371
  $wfdb = new wfDB();
372
+ $q1 = $wfdb->query("select comment_ID from $wpdb->comments where comment_approved=1");
 
373
  if( ! $q1){
374
  wordfence::statusEndErr();
375
  return;
376
  }
377
+ if(! (mysql_num_rows($q1) > 0)){
378
+ wordfence::statusEnd($statusIDX, false);
379
+ return;
380
+ }
381
+
382
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
383
  $commentDat = array();
384
+ while($idRow = mysql_fetch_assoc($q1)){
385
+ $row = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from $wpdb->comments where comment_ID=%d", $idRow['comment_ID']);
 
386
  $h->hoover($row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content']);
387
  $commentDat[$row['comment_ID']] = array(
388
  'contentMD5' => md5($row['comment_content'] . $row['comment_author'] . $row['comment_author_url']),
391
  'date' => $row['comment_date']
392
  );
393
  }
 
 
 
 
 
394
  $hooverResults = $h->getBaddies();
395
  if($h->errorMsg){
396
  $this->errorStop($h->errorMsg);
lib/wfSchema.php CHANGED
@@ -134,6 +134,14 @@ class wfSchema {
134
  ctime int UNSIGNED NOT NULL,
135
  URI varchar(1000) NOT NULL,
136
  KEY k1(ctime)
 
 
 
 
 
 
 
 
137
  ) default charset=utf8"
138
  );
139
  private $db = false;
134
  ctime int UNSIGNED NOT NULL,
135
  URI varchar(1000) NOT NULL,
136
  KEY k1(ctime)
137
+ ) default charset=utf8",
138
+ 'wfHoover' => "(
139
+ id int UNSIGNED auto_increment PRIMARY KEY,
140
+ owner text,
141
+ host text,
142
+ path text,
143
+ hostKey binary(4),
144
+ KEY k2(hostKey)
145
  ) default charset=utf8"
146
  );
147
  private $db = false;
lib/wordfenceConstants.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
- define('WORDFENCE_VERSION', 1.3);
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
1
  <?php
2
+ define('WORDFENCE_VERSION', 1.4);
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
lib/wordfenceURLHoover.php CHANGED
@@ -3,16 +3,20 @@ require_once('wfAPI.php');
3
  class wordfenceURLHoover {
4
  private $debug = false;
5
  private $URLsByID = array();
6
- private $hostKeysByID = array();
7
  public $errorMsg = false;
8
  private $hostKeyCache = array();
9
  private $api = false;
 
10
  private $dRegex = 'aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|xn--lgbbat1ad8j|xn--fiqs8s|xn--fiqz9s|xn--wgbh1c|xn--j6w193g|xn--h2brj9c|xn--mgbbh1a71e|xn--fpcrj9c3d|xn--gecrj9c|xn--s9brj9c|xn--xkc2dl3a5ee0h|xn--45brj9c|xn--mgba3a4f16a|xn--mgbayh7gpa|xn--mgbc0a9azcg|xn--ygbi2ammx|xn--wgbl6a|xn--p1ai|xn--mgberp4a5d4ar|xn--90a3ac|xn--yfro4i67o|xn--clchc0ea0b2g2a9gcd|xn--3e0b707e|xn--fzc2c9e2c|xn--xkc2al3hye2a|xn--mgbtf8fl|xn--kprw13d|xn--kpry57d|xn--o3cw4h|xn--pgbs0dh|xn--mgbaam7a8h|xn--54b7fta0cc|xn--90ae|xn--node|xn--4dbrk0ce|xn--80ao21a|xn--mgb9awbf|xn--mgbai9azgqp6j|xn--j1amh|xn--mgb2ddes|xn--kgbechtv|xn--hgbk6aj7f53bba|xn--0zwm56d|xn--g6w251d|xn--80akhbyknj4f|xn--11b5bs3a9aj6g|xn--jxalpdlp|xn--9t4b11yi5a|xn--deba0ad|xn--zckzah|xn--hlcj6aya9esc7a';
11
  public function __construct($apiKey, $wordpressVersion){
12
  $this->api = new wfAPI($apiKey, $wordpressVersion);
 
 
 
 
13
  }
14
  public function hoover($id, $data){
15
- if(! strstr($data, '.') ){
16
  return;
17
  }
18
  if(! preg_match('/[a-zA-Z0-9\-]+\.(?:' . $this->dRegex . ')/i', $data)){
@@ -25,13 +29,11 @@ class wordfenceURLHoover {
25
  }
26
  private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
27
  public function addHost($id, $host, $path){
28
- $this->dbg("Adding host with ID $id and $host $path");
29
  $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
30
  $host = strtolower($host);
31
  $this->intAddHost($id, $host, $path);
32
  }
33
  public function addIP($id, $ipdata, $path){
34
- $this->dbg("Adding IP with ID $id and $ipdata $path");
35
  $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
36
  if(strstr($ipdata, '.') === false && $ipdata >= 16777216 && $ipdata <= 4026531840){
37
  $ipdata = long2ip($ipdata);
@@ -59,19 +61,11 @@ class wordfenceURLHoover {
59
  if(strpos($path, '/') !== 0){
60
  $path = '/';
61
  }
62
- foreach($this->URLsByID as $elem){
63
- if($elem[0] == $host && $elem[1] == $path){
64
- $this->dbg("Host/Path $host $path already in URLsByID");
65
- return false;
66
- }
67
- }
68
- $this->dbg("Adding Host/Path $host $path to URLsByID");
69
- $this->URLsByID[$id][] = array($host, $path);
70
  return true;
71
  }
72
  private function makeHostKey($host){
73
  if(isset($this->hostKeyCache[$host])){
74
- $this->dbg("Returning cached hostkey for $host");
75
  return $this->hostKeyCache[$host];
76
  }
77
  $hostParts = explode('.', $host);
@@ -82,25 +76,15 @@ class wordfenceURLHoover {
82
  $hostKey = substr(hash('sha256', $hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
83
  }
84
  $this->hostKeyCache[$host] = $hostKey;
85
- $this->dbg("Returning uncached hostkey for $host");
86
  return $hostKey;
87
  }
88
  public function getBaddies(){
89
  $allHostKeys = array();
90
- foreach($this->URLsByID as $id => $hostPathList){
91
- foreach($hostPathList as $elem){
92
- $host = $elem[0];
93
- $path = $elem[1];
94
- $hostKey = $this->makeHostKey($host);
95
- if(! in_array($hostKey, $allHostKeys)){
96
- array_push($allHostKeys, $hostKey);
97
- $this->dbg("Adding hostkey for $host");
98
- }
99
- if(! isset($this->hostKeysByID[$id])){
100
- $this->hostKeysByID[$id] = array();
101
- }
102
- $this->hostKeysByID[$id] = $hostKey;
103
- }
104
  }
105
  //Now call API and check if any hostkeys are bad.
106
  //This is a shortcut, because if no hostkeys are bad it saves us having to check URLs
@@ -142,22 +126,19 @@ class wordfenceURLHoover {
142
  $urlsToCheck = array();
143
  //need to figure out which id's have bad hostkeys
144
  //need to feed in all URL's from those id's where the hostkey matches a URL
145
- foreach($this->URLsByID as $id => $hostPathList){
146
- foreach($hostPathList as $elem){
147
- $host = $elem[0];
148
- $path = $elem[1];
149
- $url = 'http://' . $host . $path;
150
- $hostKey = $this->makeHostKey($host);
151
- if(in_array($hostKey, $badHostKeys)){
152
- if(! isset($urlsToCheck[$id])){
153
- $urlsToCheck[$id] = array();
154
- }
155
- if(! in_array($url, $urlsToCheck[$id])){
156
- $urlsToCheck[$id][] = $url;
157
- }
158
  }
159
  }
160
  }
 
161
  if(sizeof($urlsToCheck) > 0){
162
  $this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
163
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
3
  class wordfenceURLHoover {
4
  private $debug = false;
5
  private $URLsByID = array();
 
6
  public $errorMsg = false;
7
  private $hostKeyCache = array();
8
  private $api = false;
9
+ private $table = '';
10
  private $dRegex = 'aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|xn--lgbbat1ad8j|xn--fiqs8s|xn--fiqz9s|xn--wgbh1c|xn--j6w193g|xn--h2brj9c|xn--mgbbh1a71e|xn--fpcrj9c3d|xn--gecrj9c|xn--s9brj9c|xn--xkc2dl3a5ee0h|xn--45brj9c|xn--mgba3a4f16a|xn--mgbayh7gpa|xn--mgbc0a9azcg|xn--ygbi2ammx|xn--wgbl6a|xn--p1ai|xn--mgberp4a5d4ar|xn--90a3ac|xn--yfro4i67o|xn--clchc0ea0b2g2a9gcd|xn--3e0b707e|xn--fzc2c9e2c|xn--xkc2al3hye2a|xn--mgbtf8fl|xn--kprw13d|xn--kpry57d|xn--o3cw4h|xn--pgbs0dh|xn--mgbaam7a8h|xn--54b7fta0cc|xn--90ae|xn--node|xn--4dbrk0ce|xn--80ao21a|xn--mgb9awbf|xn--mgbai9azgqp6j|xn--j1amh|xn--mgb2ddes|xn--kgbechtv|xn--hgbk6aj7f53bba|xn--0zwm56d|xn--g6w251d|xn--80akhbyknj4f|xn--11b5bs3a9aj6g|xn--jxalpdlp|xn--9t4b11yi5a|xn--deba0ad|xn--zckzah|xn--hlcj6aya9esc7a';
11
  public function __construct($apiKey, $wordpressVersion){
12
  $this->api = new wfAPI($apiKey, $wordpressVersion);
13
+ $this->db = new wfDB();
14
+ global $wpdb;
15
+ $this->table = $wpdb->prefix . 'wfHoover';
16
+ $this->db->query("truncate table $this->table");
17
  }
18
  public function hoover($id, $data){
19
+ if(strpos($data, '.') === false){
20
  return;
21
  }
22
  if(! preg_match('/[a-zA-Z0-9\-]+\.(?:' . $this->dRegex . ')/i', $data)){
29
  }
30
  private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
31
  public function addHost($id, $host, $path){
 
32
  $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
33
  $host = strtolower($host);
34
  $this->intAddHost($id, $host, $path);
35
  }
36
  public function addIP($id, $ipdata, $path){
 
37
  $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
38
  if(strstr($ipdata, '.') === false && $ipdata >= 16777216 && $ipdata <= 4026531840){
39
  $ipdata = long2ip($ipdata);
61
  if(strpos($path, '/') !== 0){
62
  $path = '/';
63
  }
64
+ $this->db->query("insert into $this->table (owner, host, path, hostKey) values ('%s', '%s', '%s', '%s')", $id, $host, $path, $this->makeHostKey($host));
 
 
 
 
 
 
 
65
  return true;
66
  }
67
  private function makeHostKey($host){
68
  if(isset($this->hostKeyCache[$host])){
 
69
  return $this->hostKeyCache[$host];
70
  }
71
  $hostParts = explode('.', $host);
76
  $hostKey = substr(hash('sha256', $hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
77
  }
78
  $this->hostKeyCache[$host] = $hostKey;
 
79
  return $hostKey;
80
  }
81
  public function getBaddies(){
82
  $allHostKeys = array();
83
+ $stime = microtime(true);
84
+ $allHostKeys = array();
85
+ $q1 = $this->db->query("select distinct hostKey as hostKey from $this->table");
86
+ while($hRec = mysql_fetch_assoc($q1)){
87
+ array_push($allHostKeys, $hRec['hostKey']);
 
 
 
 
 
 
 
 
 
88
  }
89
  //Now call API and check if any hostkeys are bad.
90
  //This is a shortcut, because if no hostkeys are bad it saves us having to check URLs
126
  $urlsToCheck = array();
127
  //need to figure out which id's have bad hostkeys
128
  //need to feed in all URL's from those id's where the hostkey matches a URL
129
+ foreach($badHostKeys as $badHostKey){
130
+ $q1 = $this->db->query("select owner, host, path from $this->table where hostKey='%s'", $badHostKey);
131
+ while($rec = mysql_fetch_assoc($q1)){
132
+ $url = 'http://' . $rec['host'] . $rec['path'];
133
+ if(! isset($urlsToCheck[$rec['owner']])){
134
+ $urlsToCheck[$rec['owner']] = array();
135
+ }
136
+ if(! in_array($url, $urlsToCheck[$rec['owner']])){
137
+ $urlsToCheck[$rec['owner']][] = $url;
 
 
 
 
138
  }
139
  }
140
  }
141
+
142
  if(sizeof($urlsToCheck) > 0){
143
  $this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
144
  $badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
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.0.2
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
@@ -152,6 +152,13 @@ or a theme, because often these have been updated to fix a security hole.
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
 
 
 
 
 
 
 
155
  = 2.0.2 =
156
  * Fixed plugin upgrades so that css and scripts are not cached across versions.
157
 
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.0.3
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
155
+ = 2.0.3 =
156
+ * Removed unbuffered queries and switched to conventional queries that are memory efficient for better stability.
157
+ * Made scanning large numbers of URL's contained in things like awstats log files extremely memory efficient and way faster.
158
+ * Removed alerts about unknown files in core directory if they belong to an older wordpress version and are unchanged.
159
+ * Other performance improvements like using strpos instead of strstr.
160
+ * Moved "scan files outside base dir" option to be in correct place on config page.
161
+
162
  = 2.0.2 =
163
  * Fixed plugin upgrades so that css and scripts are not cached across versions.
164
 
wordfence.php CHANGED
@@ -4,7 +4,7 @@ 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.0.2
8
  Author URI: http://wordfence.com/
9
  */
10
  require_once('lib/wordfenceConstants.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.0.3
8
  Author URI: http://wordfence.com/
9
  */
10
  require_once('lib/wordfenceConstants.php');