Wordfence Security – Firewall & Malware Scan - Version 2.1.0

Version Description

  • Fixed scans hanging on Dreamhost and other hosts.
  • Made Wordfence more memory efficient.
  • Wordfence scans are now broken into steps so we can scan a huge number of files, posts and comments.
  • Alert emails now include IP address, hostname lookup and geographic location (city if available).
  • Improved scan locking. No longer time based but uses flock() if on unix or time on Windows.
  • Suppressed warnings that WF was generating.
  • Improve handling of non-standard wp-content directories.
  • Fix restored files were still showing as changed if they contained international characters.
  • Improve permission denied message if attempting to repair a file.
  • Fixed problem that caused scans to not start because some hosts take too long to look up their own name.
  • Fixed issue with Wordfence menu that caused it to not appear or conflict with other menus under certain conditions.
  • Upgraded to API version 1.6
  • Improved geo lookup code for IP's.
  • Fixed debug mode output in live status box - coloring was wrong.
  • Added ajax status message to WF admin pages.
  • Fixed colorbox popup so that it doesn't jump around on refresh.
Download this release

Release Info

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

Code changes from version 2.0.7 to 2.1.0

css/main.css CHANGED
@@ -268,4 +268,20 @@ input.wfStartScanButton { width: 160px; text-align: left; padding-left: 20px; }
268
  .wferror {
269
  color: #F00;
270
  }
271
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  .wferror {
269
  color: #F00;
270
  }
271
+ #wordfenceWorking {
272
+ padding: 2px 8px 2px 24px;
273
+ z-index: 100000;
274
+ position: fixed;
275
+ right: 2px;
276
+ bottom: 2px;
277
+ border: 1px solid #000;
278
+ background-color: #F00;
279
+ color: #FFF;
280
+ font-size: 12px;
281
+ font-weight: bold;
282
+ font-family: Arial;
283
+ text-align: center;
284
+ background-image: url('../images/icons/ajaxRed16.gif');
285
+ background-position: 2px 2px;
286
+ background-repeat: no-repeat;
287
+ }
images/icons/ajaxRed16.gif ADDED
Binary file
js/admin.js CHANGED
@@ -63,8 +63,16 @@ window['wordfenceAdmin'] = {
63
  }
64
  jQuery(document).bind('cbox_closed', function(){ self.colorboxIsOpen = false; self.colorboxServiceQueue(); });
65
  }
 
66
 
67
  },
 
 
 
 
 
 
 
68
  startActivityLogUpdates: function(){
69
  var self = this;
70
  setInterval(function(){
@@ -79,7 +87,7 @@ window['wordfenceAdmin'] = {
79
  var self = this;
80
  this.ajax('wordfence_activityLogUpdate', {
81
  lastctime: this.lastALogCtime
82
- }, function(res){ self.doneUpdateActivityLog(res); }, function(){ self.activityLogUpdatePending = false; });
83
 
84
  },
85
  doneUpdateActivityLog: function(res){
@@ -216,7 +224,7 @@ window['wordfenceAdmin'] = {
216
  this.ajax('wordfence_ticker', {
217
  alsoGet: alsoGet,
218
  otherParams: otherParams
219
- }, function(res){ self.handleTickerReturn(res); }, function(){ self.tickerUpdatePending = false; });
220
  },
221
  handleTickerReturn: function(res){
222
  this.tickerUpdatePending = false;
@@ -314,8 +322,7 @@ window['wordfenceAdmin'] = {
314
  }
315
  });
316
  }
317
- }
318
- );
319
  },
320
  activateWF: function(key){
321
  jQuery('.wfAjax24').show();
@@ -441,7 +448,7 @@ window['wordfenceAdmin'] = {
441
  }
442
  return true;
443
  },
444
- ajax: function(action, data, cb, cbErr){
445
  if(typeof(data) == 'string'){
446
  if(data.length > 0){
447
  data += '&';
@@ -455,12 +462,16 @@ window['wordfenceAdmin'] = {
455
  cbErr = function(){};
456
  }
457
  var self = this;
 
 
 
458
  jQuery.ajax({
459
  type: 'POST',
460
  url: WordfenceAdminVars.ajaxURL,
461
  dataType: "json",
462
  data: data,
463
  success: function(json){
 
464
  if(json && json.nonce){
465
  self.nonce = json.nonce;
466
  }
@@ -469,7 +480,7 @@ window['wordfenceAdmin'] = {
469
  }
470
  cb(json);
471
  },
472
- error: cbErr
473
  });
474
  },
475
  colorbox: function(width, heading, body){
@@ -495,11 +506,12 @@ window['wordfenceAdmin'] = {
495
  }, function(res){ self.doneDeleteFile(res); });
496
  },
497
  doneDeleteFile: function(res){
 
 
498
  if(res.ok){
499
- var self = this;
500
  this.loadIssues(function(){ self.colorbox('400px', "Success deleting file", "The file " + res.file + " containing " + res.filesize + " bytes was successfully deleted."); });
501
- } else if(res.errorMsg){
502
- this.loadIssues();
503
  }
504
  },
505
  restoreFile: function(issueID){
@@ -509,26 +521,24 @@ window['wordfenceAdmin'] = {
509
  }, function(res){ self.doneRestoreFile(res); });
510
  },
511
  doneRestoreFile: function(res){
512
- this.loadIssues();
513
  if(res.ok){
514
- this.colorbox("400px", "File restored OK", "The file " + res.file + " was restored succesfully.");
 
 
515
  }
516
  },
517
  deleteIssue: function(id){
518
  var self = this;
519
  this.ajax('wordfence_deleteIssue', { id: id }, function(res){
520
  self.loadIssues();
521
- if(res.errMsg){
522
- self.colorbox('400px', "An error occured", res.errMsg);
523
- }
524
  });
525
  },
526
  updateIssueStatus: function(id, st){
527
  var self = this;
528
  this.ajax('wordfence_updateIssueStatus', { id: id, 'status': st }, function(res){
529
- self.loadIssues();
530
- if(res.errMsg){
531
- self.colorbox('400px', "An error occured", res.errMsg);
532
  }
533
  });
534
  },
63
  }
64
  jQuery(document).bind('cbox_closed', function(){ self.colorboxIsOpen = false; self.colorboxServiceQueue(); });
65
  }
66
+ this.showLoading();
67
 
68
  },
69
+ showLoading: function(){
70
+ this.removeLoading();
71
+ jQuery('<div id="wordfenceWorking">Wordfence is working...</div>').appendTo('body');
72
+ },
73
+ removeLoading: function(){
74
+ jQuery('#wordfenceWorking').remove();
75
+ },
76
  startActivityLogUpdates: function(){
77
  var self = this;
78
  setInterval(function(){
87
  var self = this;
88
  this.ajax('wordfence_activityLogUpdate', {
89
  lastctime: this.lastALogCtime
90
+ }, function(res){ self.doneUpdateActivityLog(res); }, function(){ self.activityLogUpdatePending = false; }, true);
91
 
92
  },
93
  doneUpdateActivityLog: function(res){
224
  this.ajax('wordfence_ticker', {
225
  alsoGet: alsoGet,
226
  otherParams: otherParams
227
+ }, function(res){ self.handleTickerReturn(res); }, function(){ self.tickerUpdatePending = false; }, true);
228
  },
229
  handleTickerReturn: function(res){
230
  this.tickerUpdatePending = false;
322
  }
323
  });
324
  }
325
+ }, false, false);
 
326
  },
327
  activateWF: function(key){
328
  jQuery('.wfAjax24').show();
448
  }
449
  return true;
450
  },
451
+ ajax: function(action, data, cb, cbErr, noLoading){
452
  if(typeof(data) == 'string'){
453
  if(data.length > 0){
454
  data += '&';
462
  cbErr = function(){};
463
  }
464
  var self = this;
465
+ if(! noLoading){
466
+ this.showLoading();
467
+ }
468
  jQuery.ajax({
469
  type: 'POST',
470
  url: WordfenceAdminVars.ajaxURL,
471
  dataType: "json",
472
  data: data,
473
  success: function(json){
474
+ self.removeLoading();
475
  if(json && json.nonce){
476
  self.nonce = json.nonce;
477
  }
480
  }
481
  cb(json);
482
  },
483
+ error: function(){ self.removeLoading(); cbErr(); }
484
  });
485
  },
486
  colorbox: function(width, heading, body){
506
  }, function(res){ self.doneDeleteFile(res); });
507
  },
508
  doneDeleteFile: function(res){
509
+ var cb = false;
510
+ var self = this;
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){
521
  }, function(res){ self.doneRestoreFile(res); });
522
  },
523
  doneRestoreFile: function(res){
524
+ var self = this;
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){
532
  var self = this;
533
  this.ajax('wordfence_deleteIssue', { id: id }, function(res){
534
  self.loadIssues();
 
 
 
535
  });
536
  },
537
  updateIssueStatus: function(id, st){
538
  var self = this;
539
  this.ajax('wordfence_updateIssueStatus', { id: id, 'status': st }, function(res){
540
+ if(res.ok){
541
+ self.loadIssues();
 
542
  }
543
  });
544
  },
lib/Diff.php CHANGED
@@ -173,4 +173,4 @@ class Diff
173
  $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes();
174
  return $this->groupedCodes;
175
  }
176
- }
173
  $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes();
174
  return $this->groupedCodes;
175
  }
176
+ }
lib/Diff/Renderer/Html/Array.php CHANGED
@@ -219,6 +219,6 @@ class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
219
  */
220
  private function htmlSafe($string)
221
  {
222
- return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
223
  }
224
- }
219
  */
220
  private function htmlSafe($string)
221
  {
222
+ return @htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
223
  }
224
+ }
lib/Diff/SequenceMatcher.php CHANGED
@@ -739,4 +739,4 @@ class Diff_SequenceMatcher
739
  return 1;
740
  }
741
  }
742
- }
739
  return 1;
740
  }
741
  }
742
+ }
lib/diffResult.php CHANGED
@@ -30,7 +30,14 @@
30
  ?>
31
  </table>
32
 
33
- <?php echo $diffResult; ?>
 
 
 
 
 
 
 
34
 
35
 
36
  <div class="diffFooter">&copy;&nbsp;2011 Wordfence &mdash; Visit <a href="http://wordfence.com/">Wordfence.com</a> for help, security updates and more.</a>
30
  ?>
31
  </table>
32
 
33
+ <?php
34
+ if($diffResult){
35
+ echo $diffResult;
36
+ } else {
37
+ echo "<br />There are no differences between the original file and the file in the repository.";
38
+ }
39
+
40
+ ?>
41
 
42
 
43
  <div class="diffFooter">&copy;&nbsp;2011 Wordfence &mdash; Visit <a href="http://wordfence.com/">Wordfence.com</a> for help, security updates and more.</a>
lib/email_genericAlert.php CHANGED
@@ -2,7 +2,7 @@ This alert was generated by Wordfence on "<?php echo $blogName; ?>" at <?php ech
2
 
3
 
4
  <?php echo $alertMsg; ?>
5
-
6
 
7
  --
8
  To change your alert options for Wordfence, visit:
2
 
3
 
4
  <?php echo $alertMsg; ?>
5
+ <?php if($IPMsg){ echo "\n$IPMsg\n"; } ?>
6
 
7
  --
8
  To change your alert options for Wordfence, visit:
lib/menu_scan.php CHANGED
@@ -43,7 +43,11 @@
43
  foreach($events as $e){
44
  if(strpos($e['msg'], 'SUM_') !== 0){
45
  if( $debugOn || $e['level'] < 4){
46
- echo '<div class="wfActivityLine wf' . $e['type'] . '">[' . date('M d H:i:s', $e['ctime']) . ']&nbsp;' . $e['msg'] . '</div>';
 
 
 
 
47
  }
48
  }
49
  $newestItem = $e['ctime'];
43
  foreach($events as $e){
44
  if(strpos($e['msg'], 'SUM_') !== 0){
45
  if( $debugOn || $e['level'] < 4){
46
+ $typeClass = '';
47
+ if($debugOn){
48
+ $typeClass = ' wf' . $e['type'];
49
+ }
50
+ echo '<div class="wfActivityLine' . $typeClass . '">[' . date('M d H:i:s', $e['ctime']) . ']&nbsp;' . $e['msg'] . '</div>';
51
  }
52
  }
53
  $newestItem = $e['ctime'];
lib/wfConfig.php CHANGED
@@ -368,7 +368,7 @@ class wfConfig {
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 ser_ser(). " . $caller['file'] . " line " . $caller['line']);
372
  }
373
 
374
  self::getDB()->query("insert into " . self::table() . " (name, val) values ('%s', '%s') ON DUPLICATE KEY UPDATE val='%s'", $key, $val, $val);
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']);
372
  }
373
 
374
  self::getDB()->query("insert into " . self::table() . " (name, val) values ('%s', '%s') ON DUPLICATE KEY UPDATE val='%s'", $key, $val, $val);
lib/wfCrawl.php CHANGED
@@ -22,7 +22,7 @@ class wfCrawl {
22
  }
23
  }
24
  $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
25
- $host = $wfLog->reverseLookup($IP);
26
  if(! $host){
27
  $db->query("insert into $table (IP, patternSig, status, lastUpdate, PTR) values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp(), '%s') ON DUPLICATE KEY UPDATE status='%s', lastUpdate=unix_timestamp(), PTR='%s'", $IPn, $hostPattern, 'noPTR', '', 'noPTR', '');
28
  return false;
22
  }
23
  }
24
  $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
25
+ $host = wfUtils::reverseLookup($IP);
26
  if(! $host){
27
  $db->query("insert into $table (IP, patternSig, status, lastUpdate, PTR) values (%s, UNHEX(MD5('%s')), '%s', unix_timestamp(), '%s') ON DUPLICATE KEY UPDATE status='%s', lastUpdate=unix_timestamp(), PTR='%s'", $IPn, $hostPattern, 'noPTR', '', 'noPTR', '');
28
  return false;
lib/wfDB.php CHANGED
@@ -37,6 +37,7 @@ class wfDB {
37
  }
38
  mysql_select_db($this->dbname, $dbh);
39
  $this->dbh = $dbh;
 
40
  } else {
41
  $handleKey = md5($dbhost . $dbuser . $dbpassword . $dbname);
42
  if(isset(self::$dbhCache[$handleKey])){
@@ -51,6 +52,7 @@ class wfDB {
51
  mysql_select_db($this->dbname, $dbh);
52
  self::$dbhCache[$handleKey] = $dbh;
53
  $this->dbh = self::$dbhCache[$handleKey];
 
54
  }
55
  }
56
  }
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])){
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
  }
lib/wfIssues.php CHANGED
@@ -1,18 +1,25 @@
1
  <?php
2
  require_once('wfUtils.php');
3
  class wfIssues {
 
 
 
4
  private $updateCalled = false;
5
- public $lastError = '';
6
  private $issuesTable = '';
7
  private $newIssues = array();
8
  public $totalIssues = 0;
9
  public $totalCriticalIssues = 0;
10
  public $totalWarningIssues = 0;
11
- private $db = false;
 
 
12
  public function __construct(){
13
  global $wpdb;
14
  $this->issuesTable = $wpdb->base_prefix . 'wfIssues';
15
  }
 
 
 
16
  public function addIssue($type, $severity,
17
 
18
  $ignoreP, /* some piece of data used for md5 for permanent ignores */
@@ -30,12 +37,12 @@ class wfIssues {
30
  if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return false; }
31
  }
32
 
33
- $this->totalIssues++;
34
  if($severity == 1){
35
  $this->totalCriticalIssues++;
36
  } else if($severity == 2){
37
  $this->totalWarningIssues++;
38
  }
 
39
  $this->newIssues[] = array(
40
  'type' => $type,
41
  'severity' => $severity,
@@ -192,7 +199,7 @@ class wfIssues {
192
  }
193
  $arr = wfConfig::get_ser('wf_summaryItems', array());
194
  //$arr['scanTimeAgo'] = wfUtils::makeTimeAgo(sprintf('%.0f', time() - $arr['scanTime']));
195
- $arr['scanRunning'] = wfConfig::get('wf_scanRunning') ? '1' : '0';
196
  $arr['scheduledScansEnabled'] = wfConfig::get('scheduledScansEnabled');
197
  $secsToGo = wp_next_scheduled('wordfence_scheduled_scan') - time();
198
  if($secsToGo < 1){
1
  <?php
2
  require_once('wfUtils.php');
3
  class wfIssues {
4
+ private $db = false;
5
+
6
+ //Properties that are serialized on sleep:
7
  private $updateCalled = false;
 
8
  private $issuesTable = '';
9
  private $newIssues = array();
10
  public $totalIssues = 0;
11
  public $totalCriticalIssues = 0;
12
  public $totalWarningIssues = 0;
13
+ public function __sleep(){ //Same order here as vars above
14
+ return array('updateCalled', 'issuesTable', 'newIssues', 'totalIssues', 'totalCriticalIssues', 'totalWarningIssues');
15
+ }
16
  public function __construct(){
17
  global $wpdb;
18
  $this->issuesTable = $wpdb->base_prefix . 'wfIssues';
19
  }
20
+ public function __wakeup(){
21
+ $this->db = new wfDB();
22
+ }
23
  public function addIssue($type, $severity,
24
 
25
  $ignoreP, /* some piece of data used for md5 for permanent ignores */
37
  if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return false; }
38
  }
39
 
 
40
  if($severity == 1){
41
  $this->totalCriticalIssues++;
42
  } else if($severity == 2){
43
  $this->totalWarningIssues++;
44
  }
45
+ $this->totalIssues++;
46
  $this->newIssues[] = array(
47
  'type' => $type,
48
  'severity' => $severity,
199
  }
200
  $arr = wfConfig::get_ser('wf_summaryItems', array());
201
  //$arr['scanTimeAgo'] = wfUtils::makeTimeAgo(sprintf('%.0f', time() - $arr['scanTime']));
202
+ $arr['scanRunning'] = wfUtils::isScanRunning() ? '1' : '0';
203
  $arr['scheduledScansEnabled'] = wfConfig::get('scheduledScansEnabled');
204
  $secsToGo = wp_next_scheduled('wordfence_scheduled_scan') - time();
205
  if($secsToGo < 1){
lib/wfLog.php CHANGED
@@ -4,7 +4,6 @@ require_once('wfUtils.php');
4
  require_once('wfBrowscap.php');
5
  class wfLog {
6
  private $hitsTable = '';
7
- private $locsTable = '';
8
  private $apiKey = '';
9
  private $wp_version = '';
10
  private $db = false;
@@ -16,13 +15,11 @@ class wfLog {
16
  global $wpdb;
17
  $this->hitsTable = $wpdb->base_prefix . 'wfHits';
18
  $this->loginsTable = $wpdb->base_prefix . 'wfLogins';
19
- $this->locsTable = $wpdb->base_prefix . 'wfLocs';
20
  $this->blocksTable = $wpdb->base_prefix . 'wfBlocks';
21
  $this->lockOutTable = $wpdb->base_prefix . 'wfLockedOut';
22
  $this->leechTable = $wpdb->base_prefix . 'wfLeechers';
23
  $this->badLeechersTable = $wpdb->base_prefix . 'wfBadLeechers';
24
  $this->scanTable = $wpdb->base_prefix . 'wfScanners';
25
- $this->reverseTable = $wpdb->base_prefix . 'wfReverseCache';
26
  $this->throttleTable = $wpdb->base_prefix . 'wfThrottleLog';
27
  $this->statusTable = $wpdb->base_prefix . 'wfStatus';
28
  }
@@ -419,61 +416,8 @@ class wfLog {
419
  $IPs[] = $res['IP'];
420
  }
421
  }
422
- $IPs = array_unique($IPs);
423
- $IPLocs = array();
424
- $toResolve = array();
425
- foreach($IPs as $IP){
426
- $r1 = $this->getDB()->query("select IP, ctime, failed, city, region, countryName, countryCode, lat, lon, unix_timestamp() - ctime as age from " . $this->locsTable . " where IP=%s", $IP);
427
- if($r1){
428
- if($row = mysql_fetch_assoc($r1)){
429
- if($row['age'] > WORDFENCE_MAX_IPLOC_AGE){
430
- $this->getDB()->query("delete from " . $this->locsTable . " where IP=%s", $row['IP']);
431
- } else {
432
- if($row['failed'] == 1){
433
- $IPLocs[$IP] = false;
434
- } else {
435
- $IPLocs[$IP] = $row;
436
- }
437
- }
438
- }
439
- }
440
- if(! isset($IPLocs[$IP])){
441
- $toResolve[] = $IP;
442
- }
443
- }
444
- if(sizeof($toResolve) > 0){
445
- $api = new wfAPI($this->apiKey, $this->wp_version);
446
- $freshIPs = $api->call('resolve_ips', array(), array(
447
- 'ips' => implode(',', $toResolve)
448
- ));
449
- if(is_array($freshIPs)){
450
- foreach($freshIPs as $IP => $value){
451
- if($value == 'failed'){
452
- $this->getDB()->query("insert IGNORE into " . $this->locsTable . " (IP, ctime, failed) values (%s, unix_timestamp(), 1)", $IP);
453
- $IPLocs[$IP] = false;
454
- } else {
455
- $this->getDB()->query("insert IGNORE into " . $this->locsTable . " (IP, ctime, failed, city, region, countryName, countryCode, lat, lon) values (%s, unix_timestamp(), 0, '%s', '%s', '%s', '%s', %s, %s)",
456
- $IP,
457
- $value[3], //city
458
- $value[2], //region
459
- $value[1], //countryName
460
- $value[0],//countryCode
461
- $value[4],//lat
462
- $value[5]//lon
463
- );
464
- $IPLocs[$IP] = array(
465
- 'IP' => $IP,
466
- 'city' => $value[3],
467
- 'region' => $value[2],
468
- 'countryName' => $value[1],
469
- 'countryCode' => $value[0],
470
- 'lat' => $value[4],
471
- 'lon' => $value[5]
472
- );
473
- }
474
- }
475
- }
476
- }
477
  foreach($results as &$res){
478
  if(isset($IPLocs[$res['IP']])){
479
  $res['loc'] = $IPLocs[$res['IP']];
@@ -540,7 +484,7 @@ class wfLog {
540
  if($action == 'block'){
541
  $IP = wfUtils::getIP();
542
  if(wfConfig::get('alertOn_block')){
543
- wordfence::alert("Blocking IP $IP", "Wordfence has blocked IP address $IP.\n The reason is: \"$reason\".\n When we did a reverse lookup on this address it resolved to:\n \"" . $this->reverseLookup($IP) . "\".");
544
  }
545
  wordfence::status(2, 'info', "Blocking IP $IP. $reason");
546
  $this->blockIP($IP, $reason);
@@ -595,25 +539,6 @@ class wfLog {
595
  }
596
  return self::$gbSafeCache[$cacheKey]; //return cached value
597
  }
598
- public function reverseLookup($IP){
599
- $IPn = wfUtils::inet_aton($IP);
600
- $host = $this->getDB()->querySingle("select host from " . $this->reverseTable . " where IP=%s and unix_timestamp() - lastUpdate < %d", $IPn, WORDFENCE_REVERSE_LOOKUP_CACHE_TIME);
601
- if(! $host){
602
- $ptr = implode(".", array_reverse(explode(".",$IP))) . ".in-addr.arpa";
603
- $host = dns_get_record($ptr, DNS_PTR);
604
- if($host == null){
605
- $host = 'NONE';
606
- } else {
607
- $host = $host[0]['target'];
608
- }
609
- $this->getDB()->query("insert into " . $this->reverseTable . " (IP, host, lastUpdate) values (%s, '%s', unix_timestamp()) ON DUPLICATE KEY UPDATE host='%s', lastUpdate=unix_timestamp()", $IPn, $host, $host);
610
- }
611
- if($host == 'NONE'){
612
- return '';
613
- } else {
614
- return $host;
615
- }
616
- }
617
  public function addStatus($level, $type, $msg){
618
  //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
619
  $this->getDB()->query("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", sprintf('%.6f', microtime(true)), $level, $type, $msg);
@@ -636,7 +561,7 @@ class wfLog {
636
  return $results;
637
  }
638
  public function getSummaryEvents(){
639
- $res = $this->getDB()->query("select ctime, level, type, msg from " . $this->statusTable . " where level = 10 order by ctime desc limit 100", $lastCtime);
640
  $results = array();
641
  $lastTime = false;
642
  while($rec = mysql_fetch_assoc($res)){
4
  require_once('wfBrowscap.php');
5
  class wfLog {
6
  private $hitsTable = '';
 
7
  private $apiKey = '';
8
  private $wp_version = '';
9
  private $db = false;
15
  global $wpdb;
16
  $this->hitsTable = $wpdb->base_prefix . 'wfHits';
17
  $this->loginsTable = $wpdb->base_prefix . 'wfLogins';
 
18
  $this->blocksTable = $wpdb->base_prefix . 'wfBlocks';
19
  $this->lockOutTable = $wpdb->base_prefix . 'wfLockedOut';
20
  $this->leechTable = $wpdb->base_prefix . 'wfLeechers';
21
  $this->badLeechersTable = $wpdb->base_prefix . 'wfBadLeechers';
22
  $this->scanTable = $wpdb->base_prefix . 'wfScanners';
 
23
  $this->throttleTable = $wpdb->base_prefix . 'wfThrottleLog';
24
  $this->statusTable = $wpdb->base_prefix . 'wfStatus';
25
  }
416
  $IPs[] = $res['IP'];
417
  }
418
  }
419
+ $IPLocs = wfUtils::getIPsGeo($IPs); //Creates an array with IP as key and data as value
420
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  foreach($results as &$res){
422
  if(isset($IPLocs[$res['IP']])){
423
  $res['loc'] = $IPLocs[$res['IP']];
484
  if($action == 'block'){
485
  $IP = wfUtils::getIP();
486
  if(wfConfig::get('alertOn_block')){
487
+ wordfence::alert("Blocking IP $IP", "Wordfence has blocked IP address $IP.\nThe reason is: \"$reason\".", $IP);
488
  }
489
  wordfence::status(2, 'info', "Blocking IP $IP. $reason");
490
  $this->blockIP($IP, $reason);
539
  }
540
  return self::$gbSafeCache[$cacheKey]; //return cached value
541
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  public function addStatus($level, $type, $msg){
543
  //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
544
  $this->getDB()->query("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", sprintf('%.6f', microtime(true)), $level, $type, $msg);
561
  return $results;
562
  }
563
  public function getSummaryEvents(){
564
+ $res = $this->getDB()->query("select ctime, level, type, msg from " . $this->statusTable . " where level = 10 order by ctime desc limit 100");
565
  $results = array();
566
  $lastTime = false;
567
  while($rec = mysql_fetch_assoc($res)){
lib/wfScanEngine.php CHANGED
@@ -7,27 +7,67 @@ require_once('wfIssues.php');
7
  require_once('wfDB.php');
8
  require_once('wfUtils.php');
9
  class wfScanEngine {
10
- private $i = false;
11
  private $api = false;
12
- private $dbh = false;
 
 
 
 
 
 
13
  private $wp_version = false;
14
  private $apiKey = false;
15
  private $errorStopped = false;
16
- private $dictWords = array();
17
  private $startTime = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  public function __construct(){
19
  $this->startTime = time();
20
  $this->i = new wfIssues();
 
 
21
  $this->wp_version = wfUtils::getWPVersion();
22
  $this->apiKey = wfConfig::get('apiKey');
23
  $this->api = new wfAPI($this->apiKey, $this->wp_version);
24
  include('wfDict.php'); //$dictWords
25
  $this->dictWords = $dictWords;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
  public function go(){
28
- $this->status(1, 'info', "Initializing scan. Memory available: " . @ini_get('memory_limit') );
29
- $this->i->deleteNew();
30
-
31
  try {
32
  $this->doScan();
33
  if(! $this->errorStopped){
@@ -42,79 +82,60 @@ class wfScanEngine {
42
  }
43
  wordfence::scheduleNextScan(true);
44
  }
 
 
 
 
 
 
 
 
 
 
 
 
45
  public function emailNewIssues(){
46
  $this->i->emailNewIssues();
47
  }
48
  private function doScan(){
49
- $this->status(1, 'info', "Contacting Wordfence to initiate scan");
50
- $this->api->call('log_scan', array(), array());
51
- if($this->api->errorMsg){
52
- $this->errorStop($this->api->errorMsg);
53
- return;
54
- }
55
- $unknownFiles = $this->scanKnownFiles();
56
- if($this->errorStopped){
57
- return;
58
- }
59
- if(wfConfig::get('scansEnabled_fileContents')){
60
- $this->scanFileContents($unknownFiles);
61
- if($this->errorStopped){
62
- return;
63
- }
64
- }
65
- if(wfConfig::get('scansEnabled_posts')){
66
- $this->scanPosts();
67
- if($this->errorStopped){
68
- return;
69
  }
70
- }
71
- if(wfConfig::get('scansEnabled_comments')){
72
- $this->scanComments();
73
- if($this->errorStopped){ return; }
74
- }
75
- if(wfConfig::get('scansEnabled_passwds')){
76
- $this->scanAllPasswords();
77
- if($this->errorStopped){ return; }
78
- }
79
- if(wfConfig::get('scansEnabled_diskSpace')){
80
- $this->scanDiskSpace();
81
- if($this->errorStopped){ return; }
82
- }
83
- if(wfConfig::get('scansEnabled_dns')){
84
- $this->scanDNSChanges();
85
- if($this->errorStopped){ return; }
86
- }
87
- if(wfConfig::get('scansEnabled_oldVersions')){
88
- $this->scanOldVersions();
89
- if($this->errorStopped){ return; }
90
  }
91
  $summary = $this->i->getSummaryItems();
92
  $this->status(1, 'info', "Scan Complete. Scanned " . $summary['totalFiles'] . " files, " . $summary['totalPlugins'] . " plugins, " . $summary['totalThemes'] . " themes, " . ($summary['totalPages'] + $summary['totalPosts']) . " pages, " . $summary['totalComments'] . " comments and " . $summary['totalRows'] . " records in " . (time() - $this->startTime) . " seconds.");
93
- if($this->i->totalIssues > 0){
94
  $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
95
  } else {
96
  $this->status(10, 'info', "SUM_FINAL:Scan complete. Congratulations, there were no problems found.");
97
  }
98
  return;
99
  }
100
- private function scanKnownFiles(){
101
- $malwareScanEnabled = $coreScanEnabled = $pluginScanEnabled = $themeScanEnabled = false;
102
- $statusIDX = array(
103
- 'core' => false,
104
- 'plugin' => false,
105
- 'theme' => false,
106
- 'unknown' => false
107
- );
 
 
108
  if(wfConfig::get('scansEnabled_core')){
109
- $coreScanEnabled = true;
110
- $statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
111
  } else {
112
  wordfence::statusDisabled("Skipping core scan");
113
  }
114
  if(wfConfig::get('isPaid')){
115
  if(wfConfig::get('scansEnabled_plugins')){
116
- $pluginScanEnabled = true;
117
- $statusIDX['plugin'] = wordfence::statusStart("Premium: Comparing plugin files against originals in repository");
118
  } else {
119
  wordfence::statusDisabled("Skipping comparing plugin files against originals in repository");
120
  }
@@ -123,8 +144,8 @@ class wfScanEngine {
123
  }
124
  if(wfConfig::get('isPaid')){
125
  if(wfConfig::get('scansEnabled_themes')){
126
- $themeScanEnabled = true;
127
- $statusIDX['theme'] = wordfence::statusStart("Premium: Comparing theme files against originals in repository");
128
  } else {
129
  wordfence::statusDisabled("Skipping comparing theme files against originals in repository");
130
  }
@@ -133,20 +154,19 @@ class wfScanEngine {
133
  }
134
 
135
  if(wfConfig::get('scansEnabled_malware')){
136
- $statusIDX['unknown'] = wordfence::statusStart("Scanning for known malware files");
137
- $malwareScanEnabled = true;
138
  } else {
139
  wordfence::statusDisabled("Skipping malware scan");
140
  $this->status(2, 'info', "Skipping malware scan because it's disabled.");
141
  }
142
- $summaryUpdateRequired = $this->i->summaryUpdateRequired();
143
- if((! $summaryUpdateRequired) && (! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled))){
144
  $this->status(2, 'info', "Finishing this stage because we don't have to do a summary update and we don't need to do a core, plugin, theme or malware scan.");
145
  return array();
146
  }
147
 
148
  //CORE SCAN
149
- $hasher = new wordfenceHash(strlen(ABSPATH));
150
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
151
  $baseContents = scandir(ABSPATH);
152
  $scanOutside = wfConfig::get('other_scanOutside');
@@ -162,13 +182,18 @@ class wfScanEngine {
162
  }
163
  }
164
  $this->status(2, 'info', "Hashing your WordPress files for comparison against originals.");
165
- $hashes = $hasher->hashPaths(ABSPATH, $includeInScan);
 
 
 
 
 
166
  $this->status(2, 'info', "Done hash. Updating summary items.");
167
- $this->i->updateSummaryItem('totalData', wfUtils::formatBytes($hasher->totalData));
168
- $this->i->updateSummaryItem('totalFiles', $hasher->totalFiles);
169
- $this->i->updateSummaryItem('totalDirs', $hasher->totalDirs);
170
- $this->i->updateSummaryItem('linesOfPHP', $hasher->linesOfPHP);
171
- $this->i->updateSummaryItem('linesOfJCH', $hasher->linesOfJCH);
172
 
173
  if(! function_exists( 'get_plugins')){
174
  require_once ABSPATH . '/wp-admin/includes/plugin.php';
@@ -185,7 +210,7 @@ class wfScanEngine {
185
  $this->status(2, 'info', "Found " . sizeof($themes) . " themes");
186
  $this->i->updateSummaryItem('totalThemes', sizeof($themes));
187
  //Return now because we needed to do a summary update but don't have any other work to do.
188
- if(! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled)){
189
  $this->status(2, 'info', "Finishing up because we have done our required summary update and don't need to do a core, plugin, theme or malware scan.");
190
  return array();
191
  }
@@ -201,13 +226,13 @@ class wfScanEngine {
201
  }
202
  $this->status(2, 'info', "Sending request to Wordfence servers to do main scan.");
203
  $scanData = array(
204
- 'pluginScanEnabled' => $pluginScanEnabled,
205
- 'themeScanEnabled' => $themeScanEnabled,
206
- 'coreScanEnabled' => $coreScanEnabled,
207
- 'malwareScanEnabled' => $malwareScanEnabled,
208
  'plugins' => $plugins,
209
  'themes' => $themes,
210
- 'hashes' => wordfenceHash::bin2hex($hashes)
211
  );
212
  $result1 = $this->api->call('main_scan', array(), array(
213
  'data' => json_encode($scanData)
@@ -241,29 +266,35 @@ class wfScanEngine {
241
  }
242
  }
243
  foreach($haveIssues as $type => $have){
244
- if($statusIDX[$type] !== false){
245
- wordfence::statusEnd($statusIDX[$type], $have);
246
  }
247
  }
248
- return $result1['unknownFiles'];
 
249
  }
250
- private function scanFileContents($unknownFiles){
251
- $statusIDX = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
252
- $statusIDX2 = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
253
- if(! is_array($unknownFiles)){
254
- $unknownFiles = array();
255
- }
256
- $this->status(2, 'info', "Getting list of changed files since last scan.");
257
- $scanner = new wordfenceScanner($this->apiKey, $this->wp_version);
258
  $this->status(2, 'info', "Starting scan of file contents");
259
- $result2 = $scanner->scan(ABSPATH, $unknownFiles);
 
 
 
 
260
  $this->status(2, 'info', "Done file contents scan");
261
- if($scanner->errorMsg){
262
- $this->errorStop($scanner->errorMsg);
263
  }
 
264
  $haveIssues = false;
265
  $haveIssuesGSB = false;
266
- foreach($result2 as $issue){
267
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
268
  if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
269
  if(empty($issue['data']['gsb']) === false){
@@ -273,38 +304,48 @@ class wfScanEngine {
273
  }
274
  }
275
  }
276
- wordfence::statusEnd($statusIDX, $haveIssues);
277
- wordfence::statusEnd($statusIDX2, $haveIssuesGSB);
 
278
  }
279
- private function scanPosts(){
280
- $statusIDX = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
281
  $blogsToScan = $this->getBlogsToScan('posts');
282
  $wfdb = new wfDB();
283
- $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
284
- $postDat = array();
285
  foreach($blogsToScan as $blog){
286
  $q1 = $wfdb->query("select ID from " . $blog['table'] . " where post_type IN ('page', 'post') and post_status = 'publish'");
287
  while($idRow = mysql_fetch_assoc($q1)){
288
- $row = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from " . $blog['table'] . " where ID=%d", $idRow['ID']);
289
- $h->hoover($blog['blog_id'] . '-' . $row['ID'], $row['post_title'] . ' ' . $row['post_content']);
290
- $postDat[$blog['blog_id'] . '-' . $row['ID']] = array(
291
- 'contentMD5' => md5($row['post_content']),
292
- 'title' => $row['post_title'],
293
- 'type' => $row['post_type'],
294
- 'postDate' => $row['post_date'],
295
- 'isMultisite' => $blog['isMultisite'],
296
- 'domain' => $blog['domain'],
297
- 'path' => $blog['path'],
298
- 'blog_id' => $blog['blog_id']
299
- );
300
-
301
  }
302
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  $this->status(2, 'info', "Examining URLs found in posts we scanned for dangerous websites");
304
- $hooverResults = $h->getBaddies();
305
  $this->status(2, 'info', "Done examining URls");
306
- if($h->errorMsg){
307
- $this->errorStop($h->errorMsg);
308
  wordfence::statusEndErr();
309
  return;
310
 
@@ -314,8 +355,8 @@ class wfScanEngine {
314
  $arr = explode('-', $idString);
315
  $blogID = $arr[0];
316
  $postID = $arr[1];
317
- $uctype = ucfirst($postDat[$idString]['type']);
318
- $type = $postDat[$idString]['type'];
319
  foreach($hresults as $result){
320
  if($result['badList'] == 'goog-malware-shavar'){
321
  $shortMsg = "$uctype contains a suspected malware URL.";
@@ -332,19 +373,19 @@ class wfScanEngine {
332
  switch_to_blog($blogID);
333
  }
334
  $ignoreP = $idString;
335
- $ignoreC = $idString . $postDat[$idString]['contentMD5'];
336
  if($this->addIssue('postBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
337
  'postID' => $postID,
338
  'badURL' => $result['URL'],
339
- 'postTitle' => $postDat[$idString]['title'],
340
- 'type' => $postDat[$idString]['type'],
341
  'uctype' => $uctype,
342
  'permalink' => get_permalink($postID),
343
  'editPostLink' => get_edit_post_link($postID),
344
- 'postDate' => $postDat[$idString]['postDate'],
345
- 'isMultisite' => $postDat[$idString]['isMultisite'],
346
- 'domain' => $postDat[$idString]['domain'],
347
- 'path' => $postDat[$idString]['path'],
348
  'blog_id' => $blogID
349
  ))){
350
  $haveIssues = true;
@@ -354,51 +395,16 @@ class wfScanEngine {
354
  }
355
  }
356
  }
357
- wordfence::statusEnd($statusIDX, $haveIssues);
358
- }
359
- public function isBadComment($author, $email, $url, $IP, $content){
360
- $content = $author . ' ' . $email . ' ' . $url . ' ' . $IP . ' ' . $content;
361
- $cDesc = '';
362
- if($author){
363
- $cDesc = "Author: $author ";
364
- }
365
- if($email){
366
- $cDesc .= "Email: $email ";
367
- }
368
- $cDesc = "Source IP: $IP ";
369
- $this->status(2, 'info', "Scanning comment with $cDesc");
370
-
371
- $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
372
- $h->hoover(1, $content);
373
- $hooverResults = $h->getBaddies();
374
- if($h->errorMsg){
375
- return false;
376
- }
377
- if(sizeof($hooverResults) > 0 && isset($hooverResults[1])){
378
- $hresults = $hooverResults[1];
379
- foreach($hresults as $result){
380
- if($result['badList'] == 'goog-malware-shavar'){
381
- $this->status(2, 'info', "Marking comment as spam for containing a malware URL. Comment has $cDesc");
382
- return true;
383
- } else if($result['badList'] == 'googpub-phish-shavar'){
384
- $this->status(2, 'info', "Marking comment as spam for containing a phishing URL. Comment has $cDesc");
385
- return true;
386
- } else {
387
- //A list type that may be new and the plugin has not been upgraded yet.
388
- continue;
389
- }
390
- }
391
- }
392
- $this->status(2, 'info', "Scanned comment with $cDesc");
393
- return false;
394
  }
395
- private function scanComments(){
396
- $statusIDX = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
397
- global $wpdb;
398
- $wfdb = new wfDB();
399
- $commentDat = array();
400
- $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
401
  $blogsToScan = $this->getBlogsToScan('comments');
 
402
  foreach($blogsToScan as $blog){
403
  $q1 = $wfdb->query("select comment_ID from " . $blog['table'] . " where comment_approved=1");
404
  if( ! $q1){
@@ -410,23 +416,34 @@ class wfScanEngine {
410
  }
411
 
412
  while($idRow = mysql_fetch_assoc($q1)){
413
- $row = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from " . $blog['table'] . " where comment_ID=%d", $idRow['comment_ID']);
414
- $h->hoover($blog['blog_id'] . '-' . $row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content']);
415
- $commentDat[$blog['blog_id'] . '-' . $row['comment_ID']] = array(
416
- 'contentMD5' => md5($row['comment_content'] . $row['comment_author'] . $row['comment_author_url']),
417
- 'author' => $row['comment_author'],
418
- 'type' => ($row['comment_type'] ? $row['comment_type'] : 'comment'),
419
- 'date' => $row['comment_date'],
420
- 'isMultisite' => $blog['isMultisite'],
421
- 'domain' => $blog['domain'],
422
- 'path' => $blog['path'],
423
- 'blog_id' => $blog['blog_id']
424
- );
425
  }
426
  }
427
- $hooverResults = $h->getBaddies();
428
- if($h->errorMsg){
429
- $this->errorStop($h->errorMsg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  wordfence::statusEndErr();
431
  return;
432
  }
@@ -435,8 +452,8 @@ class wfScanEngine {
435
  $arr = explode('-', $idString);
436
  $blogID = $arr[0];
437
  $commentID = $arr[1];
438
- $uctype = ucfirst($commentDat[$idString]['type']);
439
- $type = $commentDat[$idString]['type'];
440
  foreach($hresults as $result){
441
  if($result['badList'] == 'goog-malware-shavar'){
442
  $shortMsg = "$uctype contains a suspected malware URL.";
@@ -452,18 +469,18 @@ class wfScanEngine {
452
  switch_to_blog($blogID);
453
  }
454
  $ignoreP = $idString;
455
- $ignoreC = $idString . '-' . $commentDat[$idString]['contentMD5'];
456
  if($this->addIssue('commentBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
457
  'commentID' => $commentID,
458
  'badURL' => $result['URL'],
459
- 'author' => $commentDat[$idString]['author'],
460
  'type' => $type,
461
  'uctype' => $uctype,
462
  'editCommentLink' => get_edit_comment_link($commentID),
463
- 'commentDate' => $commentDat[$idString]['date'],
464
- 'isMultisite' => $commentDat[$idString]['isMultisite'],
465
- 'domain' => $commentDat[$idString]['domain'],
466
- 'path' => $commentDat[$idString]['path'],
467
  'blog_id' => $blogID
468
  ))){
469
  $haveIssues = true;
@@ -473,7 +490,43 @@ class wfScanEngine {
473
  }
474
  }
475
  }
476
- wordfence::statusEnd($statusIDX, $haveIssues);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  }
478
  public function getBlogsToScan($table){
479
  $wfdb = new wfDB();
@@ -518,8 +571,8 @@ class wfScanEngine {
518
  }
519
  return false;
520
  }
521
- private function scanAllPasswords(){
522
- $statusIDX = wordfence::statusStart('Scanning for weak passwords');
523
  global $wpdb;
524
  $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
525
  $haveIssues = false;
@@ -528,11 +581,11 @@ class wfScanEngine {
528
  $isWeak = $this->scanUserPassword($user->ID);
529
  if($isWeak){ $haveIssues = true; }
530
  }
531
- wordfence::statusEnd($statusIDX, $haveIssues);
532
  }
533
  public function scanUserPassword($userID){
534
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
535
- $hasher = new PasswordHash(8, TRUE);
536
  $userDat = get_userdata($userID);
537
  $this->status(2, 'info', "Checking password strength of user '" . $userDat->user_login . "'");
538
  $shortMsg = "";
@@ -552,7 +605,7 @@ class wfScanEngine {
552
  }
553
  $haveIssue = false;
554
  for($i = 0; $i < sizeof($words); $i++){
555
- if($hasher->CheckPassword($words[$i], $userDat->user_pass)){
556
  $this->status(2, 'info', "Adding issue " . $shortMsg);
557
  if($this->addIssue('easyPassword', $level, $userDat->ID, $userDat->ID . '-' . $userDat->user_pass, $shortMsg, $longMsg, array(
558
  'ID' => $userDat->ID,
@@ -570,13 +623,13 @@ class wfScanEngine {
570
  $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
571
  return $haveIssue;
572
  }
573
- private function scanDiskSpace(){
574
- $statusIDX = wordfence::statusStart("Scanning to check available disk space");
575
  $total = disk_total_space('.');
576
  $free = disk_free_space('.');
577
  $this->status(2, 'info', "Total space: $total Free space: $free");
578
  if( (! $total) || (! $free )){ //If we get zeros it's probably not reading right. If free is zero then we're out of space and already in trouble.
579
- wordfence::statusEnd($statusIDX, false);
580
  return;
581
  }
582
  $level = false;
@@ -587,22 +640,22 @@ class wfScanEngine {
587
  } else if($spaceLeft < 1.5){
588
  $level = 2;
589
  } else {
590
- wordfence::statusEnd($statusIDX, false);
591
  return;
592
  }
593
  if($this->addIssue('diskSpace', $level, 'diskSpace' . $level, 'diskSpace' . $level, "You have $spaceLeft" . "% disk space remaining", "You only have $spaceLeft" . "% of your disk space remaining. Please free up disk space or your website may stop serving requests.", array(
594
  'spaceLeft' => $spaceLeft ))){
595
- wordfence::statusEnd($statusIDX, true);
596
  } else {
597
- wordfence::statusEnd($statusIDX, false);
598
  }
599
  }
600
- private function scanDNSChanges(){
601
  if(! function_exists('dns_get_record')){
602
  $this->status(1, 'info', "Skipping DNS scan because this system does not support dns_get_record()");
603
  return;
604
  }
605
- $statusIDX = wordfence::statusStart("Scanning DNS for unauthorized changes");
606
  $haveIssues = false;
607
  $home = get_home_url();
608
  if(preg_match('/https?:\/\/([^\/]+)/i', $home, $matches)){
@@ -694,10 +747,10 @@ class wfScanEngine {
694
 
695
  wfConfig::set('wf_dnsLogged', 1);
696
  }
697
- wordfence::statusEnd($statusIDX, $haveIssues);
698
  }
699
- private function scanOldVersions(){
700
- $statusIDX = wordfence::statusStart("Scanning for old themes, plugins and core files");
701
  if(! function_exists( 'get_preferred_from_update_core')){
702
  require_once(ABSPATH . 'wp-admin/includes/update.php');
703
  }
@@ -753,7 +806,7 @@ class wfScanEngine {
753
 
754
  }
755
  }
756
- wordfence::statusEnd($statusIDX, $haveIssues);
757
  }
758
  private function errorStop($msg){
759
  $this->errorStopped = true;
@@ -766,6 +819,125 @@ class wfScanEngine {
766
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
767
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
768
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  }
770
 
771
  ?>
7
  require_once('wfDB.php');
8
  require_once('wfUtils.php');
9
  class wfScanEngine {
 
10
  private $api = false;
11
+ private $dictWords = array();
12
+
13
+ //Beginning of serialized properties on sleep
14
+ private $hasher = false;
15
+ private $hashes = false;
16
+ private $jobList = array();
17
+ private $i = false;
18
  private $wp_version = false;
19
  private $apiKey = false;
20
  private $errorStopped = false;
 
21
  private $startTime = 0;
22
+ private $scanStep = 0;
23
+ private $maxExecTime = 10; //If more than $maxExecTime has elapsed since last check, fork a new scan process and continue
24
+ private $malwareScanEnabled = false;
25
+ private $pluginScanEnabled = false;
26
+ private $coreScanEnabled = false;
27
+ private $themeScanEnabled = false;
28
+ private $unknownFiles = array();
29
+ private $fileContentsResults = false;
30
+ private $scanner = false;
31
+ private $scanQueue = array();
32
+ private $hoover = false;
33
+ private $scanData = array();
34
+ private $statusIDX = array(
35
+ 'core' => false,
36
+ 'plugin' => false,
37
+ 'theme' => false,
38
+ 'unknown' => false
39
+ );
40
+ public function __sleep(){ //Same order here as above for properties that are included in serialization
41
+ return array('hasher', 'hashes', 'jobList', 'i', 'wp_version', 'apiKey', 'errorStopped', 'startTime', 'scanStep', 'maxExecTime', 'malwareScanEnabled', 'pluginScanEnabled', 'coreScanEnabled', 'themeScanEnabled', 'unknownFiles', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX');
42
+ }
43
  public function __construct(){
44
  $this->startTime = time();
45
  $this->i = new wfIssues();
46
+ $this->i->deleteNew();
47
+ $this->cycleStartTime = time();
48
  $this->wp_version = wfUtils::getWPVersion();
49
  $this->apiKey = wfConfig::get('apiKey');
50
  $this->api = new wfAPI($this->apiKey, $this->wp_version);
51
  include('wfDict.php'); //$dictWords
52
  $this->dictWords = $dictWords;
53
+ foreach(array('init', 'main', 'finish') as $op){ $this->jobList[] = 'knownFiles_' . $op; };
54
+ foreach(array('fileContents', 'posts', 'comments', 'passwds', 'dns', 'diskSpace', 'oldVersions') as $scanType){
55
+ if(wfConfig::get('scansEnabled_' . $scanType)){
56
+ if(method_exists($this, 'scan_' . $scanType . '_init')){
57
+ foreach(array('init', 'main', 'finish') as $op){ $this->jobList[] = $scanType . '_' . $op; };
58
+ } else {
59
+ $this->jobList[] = $scanType;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ public function __wakeup(){
65
+ $this->cycleStartTime = time();
66
+ $this->api = new wfAPI($this->apiKey, $this->wp_version);
67
+ include('wfDict.php'); //$dictWords
68
+ $this->dictWords = $dictWords;
69
  }
70
  public function go(){
 
 
 
71
  try {
72
  $this->doScan();
73
  if(! $this->errorStopped){
82
  }
83
  wordfence::scheduleNextScan(true);
84
  }
85
+ public function forkIfNeeded(){
86
+ if(time() - $this->cycleStartTime > $this->maxExecTime){
87
+ wordfence::status(2, 'info', "Forking during hash scan to ensure continuity.");
88
+ $this->fork();
89
+ }
90
+ }
91
+ public function fork(){
92
+ wfConfig::set_ser('wfsd_engine', $this);
93
+ wfUtils::clearScanLock();
94
+ self::startScan(true);
95
+ exit(0);
96
+ }
97
  public function emailNewIssues(){
98
  $this->i->emailNewIssues();
99
  }
100
  private function doScan(){
101
+ while(sizeof($this->jobList) > 0){
102
+ $jobName = $this->jobList[0];
103
+ call_user_func(array($this, 'scan_' . $jobName));
104
+ array_shift($this->jobList); //only shift once we're done because we may pause halfway through a job and need to pick up where we left off
105
+ if($this->errorStopped){
106
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
+ $this->fork();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
  $summary = $this->i->getSummaryItems();
111
  $this->status(1, 'info', "Scan Complete. Scanned " . $summary['totalFiles'] . " files, " . $summary['totalPlugins'] . " plugins, " . $summary['totalThemes'] . " themes, " . ($summary['totalPages'] + $summary['totalPosts']) . " pages, " . $summary['totalComments'] . " comments and " . $summary['totalRows'] . " records in " . (time() - $this->startTime) . " seconds.");
112
+ if($this->i->totalIssues > 0){
113
  $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
114
  } else {
115
  $this->status(10, 'info', "SUM_FINAL:Scan complete. Congratulations, there were no problems found.");
116
  }
117
  return;
118
  }
119
+ public function getCurrentJob(){
120
+ return $this->jobList[0];
121
+ }
122
+ private function scan_knownFiles_init(){
123
+ $this->status(1, 'info', "Contacting Wordfence to initiate scan");
124
+ $this->api->call('log_scan', array(), array());
125
+ if($this->api->errorMsg){
126
+ $this->errorStop($this->api->errorMsg);
127
+ return;
128
+ }
129
  if(wfConfig::get('scansEnabled_core')){
130
+ $this->coreScanEnabled = true;
131
+ $this->statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
132
  } else {
133
  wordfence::statusDisabled("Skipping core scan");
134
  }
135
  if(wfConfig::get('isPaid')){
136
  if(wfConfig::get('scansEnabled_plugins')){
137
+ $this->pluginScanEnabled = true;
138
+ $this->statusIDX['plugin'] = wordfence::statusStart("Premium: Comparing plugin files against originals in repository");
139
  } else {
140
  wordfence::statusDisabled("Skipping comparing plugin files against originals in repository");
141
  }
144
  }
145
  if(wfConfig::get('isPaid')){
146
  if(wfConfig::get('scansEnabled_themes')){
147
+ $this->themeScanEnabled = true;
148
+ $this->statusIDX['theme'] = wordfence::statusStart("Premium: Comparing theme files against originals in repository");
149
  } else {
150
  wordfence::statusDisabled("Skipping comparing theme files against originals in repository");
151
  }
154
  }
155
 
156
  if(wfConfig::get('scansEnabled_malware')){
157
+ $this->statusIDX['unknown'] = wordfence::statusStart("Scanning for known malware files");
158
+ $this->malwareScanEnabled = true;
159
  } else {
160
  wordfence::statusDisabled("Skipping malware scan");
161
  $this->status(2, 'info', "Skipping malware scan because it's disabled.");
162
  }
163
+ if((! $this->i->summaryUpdateRequired()) && (! ($this->coreScanEnabled || $this->pluginScanEnabled || $this->themeScanEnabled || $this->malwareScanEnabled))){
 
164
  $this->status(2, 'info', "Finishing this stage because we don't have to do a summary update and we don't need to do a core, plugin, theme or malware scan.");
165
  return array();
166
  }
167
 
168
  //CORE SCAN
169
+ $this->hasher = new wordfenceHash(strlen(ABSPATH));
170
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
171
  $baseContents = scandir(ABSPATH);
172
  $scanOutside = wfConfig::get('other_scanOutside');
182
  }
183
  }
184
  $this->status(2, 'info', "Hashing your WordPress files for comparison against originals.");
185
+ $this->hasher->buildFileQueue(ABSPATH, $includeInScan);
186
+ }
187
+ private function scan_knownFiles_main(){
188
+ $this->hashes = $this->hasher->genHashes($this);
189
+ }
190
+ private function scan_knownFiles_finish(){
191
  $this->status(2, 'info', "Done hash. Updating summary items.");
192
+ $this->i->updateSummaryItem('totalData', wfUtils::formatBytes($this->hasher->totalData));
193
+ $this->i->updateSummaryItem('totalFiles', $this->hasher->totalFiles);
194
+ $this->i->updateSummaryItem('totalDirs', $this->hasher->totalDirs);
195
+ $this->i->updateSummaryItem('linesOfPHP', $this->hasher->linesOfPHP);
196
+ $this->i->updateSummaryItem('linesOfJCH', $this->hasher->linesOfJCH);
197
 
198
  if(! function_exists( 'get_plugins')){
199
  require_once ABSPATH . '/wp-admin/includes/plugin.php';
210
  $this->status(2, 'info', "Found " . sizeof($themes) . " themes");
211
  $this->i->updateSummaryItem('totalThemes', sizeof($themes));
212
  //Return now because we needed to do a summary update but don't have any other work to do.
213
+ if(! ($this->coreScanEnabled || $this->pluginScanEnabled || $this->themeScanEnabled || $this->malwareScanEnabled)){
214
  $this->status(2, 'info', "Finishing up because we have done our required summary update and don't need to do a core, plugin, theme or malware scan.");
215
  return array();
216
  }
226
  }
227
  $this->status(2, 'info', "Sending request to Wordfence servers to do main scan.");
228
  $scanData = array(
229
+ 'pluginScanEnabled' => $this->pluginScanEnabled,
230
+ 'themeScanEnabled' => $this->themeScanEnabled,
231
+ 'coreScanEnabled' => $this->coreScanEnabled,
232
+ 'malwareScanEnabled' => $this->malwareScanEnabled,
233
  'plugins' => $plugins,
234
  'themes' => $themes,
235
+ 'hashes' => wordfenceHash::bin2hex($this->hashes)
236
  );
237
  $result1 = $this->api->call('main_scan', array(), array(
238
  'data' => json_encode($scanData)
266
  }
267
  }
268
  foreach($haveIssues as $type => $have){
269
+ if($this->statusIDX[$type] !== false){
270
+ wordfence::statusEnd($this->statusIDX[$type], $have);
271
  }
272
  }
273
+
274
+ $this->unknownFiles = $result1['unknownFiles'];
275
  }
276
+ private function scan_fileContents_init(){
277
+ $this->statusIDX['infect'] = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
278
+ $this->statusIDX['GSB'] = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
279
+ if(! is_array($this->unknownFiles)){
280
+ $this->unknownFiles = array();
281
+ }
282
+ $this->scanner = new wordfenceScanner($this->apiKey, $this->wp_version, $this->unknownFiles, ABSPATH);
283
+ $this->unknownFiles = false;
284
  $this->status(2, 'info', "Starting scan of file contents");
285
+ }
286
+ private function scan_fileContents_main(){
287
+ $this->fileContentsResults = $this->scanner->scan($this);
288
+ }
289
+ private function scan_fileContents_finish(){
290
  $this->status(2, 'info', "Done file contents scan");
291
+ if($this->scanner->errorMsg){
292
+ $this->errorStop($this->scanner->errorMsg);
293
  }
294
+ $this->scanner = null;
295
  $haveIssues = false;
296
  $haveIssuesGSB = false;
297
+ foreach($this->fileContentsResults as $issue){
298
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
299
  if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
300
  if(empty($issue['data']['gsb']) === false){
304
  }
305
  }
306
  }
307
+ $this->fileContentsResults = null;
308
+ wordfence::statusEnd($this->statusIDX['infect'], $haveIssues);
309
+ wordfence::statusEnd($this->statusIDX['GSB'], $haveIssuesGSB);
310
  }
311
+ private function scan_posts_init(){
312
+ $this->statusIDX['posts'] = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
313
  $blogsToScan = $this->getBlogsToScan('posts');
314
  $wfdb = new wfDB();
315
+ $this->hoover = new wordfenceURLHoover($this->apiKey, $this->wp_version);
 
316
  foreach($blogsToScan as $blog){
317
  $q1 = $wfdb->query("select ID from " . $blog['table'] . " where post_type IN ('page', 'post') and post_status = 'publish'");
318
  while($idRow = mysql_fetch_assoc($q1)){
319
+ $this->scanQueue[] = array($blog, $idRow['ID']);
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
  }
322
+ }
323
+ private function scan_posts_main(){
324
+ $wfdb = new wfDB();
325
+ while($elem = array_shift($this->scanQueue)){
326
+ $blog = $elem[0];
327
+ $postID = $elem[1];
328
+ $row = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from " . $blog['table'] . " where ID=%d", $postID);
329
+ $this->hoover->hoover($blog['blog_id'] . '-' . $row['ID'], $row['post_title'] . ' ' . $row['post_content']);
330
+ $this->scanData[$blog['blog_id'] . '-' . $row['ID']] = array(
331
+ 'contentMD5' => md5($row['post_content']),
332
+ 'title' => $row['post_title'],
333
+ 'type' => $row['post_type'],
334
+ 'postDate' => $row['post_date'],
335
+ 'isMultisite' => $blog['isMultisite'],
336
+ 'domain' => $blog['domain'],
337
+ 'path' => $blog['path'],
338
+ 'blog_id' => $blog['blog_id']
339
+ );
340
+ $this->forkIfNeeded();
341
+ }
342
+ }
343
+ private function scan_posts_finish(){
344
  $this->status(2, 'info', "Examining URLs found in posts we scanned for dangerous websites");
345
+ $hooverResults = $this->hoover->getBaddies();
346
  $this->status(2, 'info', "Done examining URls");
347
+ if($this->hoover->errorMsg){
348
+ $this->errorStop($this->hoover->errorMsg);
349
  wordfence::statusEndErr();
350
  return;
351
 
355
  $arr = explode('-', $idString);
356
  $blogID = $arr[0];
357
  $postID = $arr[1];
358
+ $uctype = ucfirst($this->scanData[$idString]['type']);
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.";
373
  switch_to_blog($blogID);
374
  }
375
  $ignoreP = $idString;
376
+ $ignoreC = $idString . $this->scanData[$idString]['contentMD5'];
377
  if($this->addIssue('postBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
378
  'postID' => $postID,
379
  'badURL' => $result['URL'],
380
+ 'postTitle' => $this->scanData[$idString]['title'],
381
+ 'type' => $this->scanData[$idString]['type'],
382
  'uctype' => $uctype,
383
  'permalink' => get_permalink($postID),
384
  'editPostLink' => get_edit_post_link($postID),
385
+ 'postDate' => $this->scanData[$idString]['postDate'],
386
+ 'isMultisite' => $this->scanData[$idString]['isMultisite'],
387
+ 'domain' => $this->scanData[$idString]['domain'],
388
+ 'path' => $this->scanData[$idString]['path'],
389
  'blog_id' => $blogID
390
  ))){
391
  $haveIssues = true;
395
  }
396
  }
397
  }
398
+ $this->scanData = array();
399
+ wordfence::statusEnd($this->statusIDX['posts'], $haveIssues);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  }
401
+ private function scan_comments_init(){
402
+ $this->statusIDX['comments'] = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
403
+ $this->scanData = array();
404
+ $this->scanQueue = array();
405
+ $this->hoover = new wordfenceURLHoover($this->apiKey, $this->wp_version);
 
406
  $blogsToScan = $this->getBlogsToScan('comments');
407
+ $wfdb = new wfDB();
408
  foreach($blogsToScan as $blog){
409
  $q1 = $wfdb->query("select comment_ID from " . $blog['table'] . " where comment_approved=1");
410
  if( ! $q1){
416
  }
417
 
418
  while($idRow = mysql_fetch_assoc($q1)){
419
+ $this->scanQueue[] = array($blog, $idRow['comment_ID']);
 
 
 
 
 
 
 
 
 
 
 
420
  }
421
  }
422
+ }
423
+ private function scan_comments_main(){
424
+ $wfdb = new wfDB();
425
+ while($elem = array_shift($this->scanQueue)){
426
+ $blog = $elem[0];
427
+ $commentID = $elem[1];
428
+ $row = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from " . $blog['table'] . " where comment_ID=%d", $commentID);
429
+ $this->hoover->hoover($blog['blog_id'] . '-' . $row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content']);
430
+ $this->scanData[$blog['blog_id'] . '-' . $row['comment_ID']] = array(
431
+ 'contentMD5' => md5($row['comment_content'] . $row['comment_author'] . $row['comment_author_url']),
432
+ 'author' => $row['comment_author'],
433
+ 'type' => ($row['comment_type'] ? $row['comment_type'] : 'comment'),
434
+ 'date' => $row['comment_date'],
435
+ 'isMultisite' => $blog['isMultisite'],
436
+ 'domain' => $blog['domain'],
437
+ 'path' => $blog['path'],
438
+ 'blog_id' => $blog['blog_id']
439
+ );
440
+ $this->forkIfNeeded();
441
+ }
442
+ }
443
+ private function scan_comments_finish(){
444
+ $hooverResults = $this->hoover->getBaddies();
445
+ if($this->hoover->errorMsg){
446
+ $this->errorStop($this->hoover->errorMsg);
447
  wordfence::statusEndErr();
448
  return;
449
  }
452
  $arr = explode('-', $idString);
453
  $blogID = $arr[0];
454
  $commentID = $arr[1];
455
+ $uctype = ucfirst($this->scanData[$idString]['type']);
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.";
469
  switch_to_blog($blogID);
470
  }
471
  $ignoreP = $idString;
472
+ $ignoreC = $idString . '-' . $this->scanData[$idString]['contentMD5'];
473
  if($this->addIssue('commentBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
474
  'commentID' => $commentID,
475
  'badURL' => $result['URL'],
476
+ 'author' => $this->scanData[$idString]['author'],
477
  'type' => $type,
478
  'uctype' => $uctype,
479
  'editCommentLink' => get_edit_comment_link($commentID),
480
+ 'commentDate' => $this->scanData[$idString]['date'],
481
+ 'isMultisite' => $this->scanData[$idString]['isMultisite'],
482
+ 'domain' => $this->scanData[$idString]['domain'],
483
+ 'path' => $this->scanData[$idString]['path'],
484
  'blog_id' => $blogID
485
  ))){
486
  $haveIssues = true;
490
  }
491
  }
492
  }
493
+ wordfence::statusEnd($this->statusIDX['comments'], $haveIssues);
494
+ }
495
+ public function isBadComment($author, $email, $url, $IP, $content){
496
+ $content = $author . ' ' . $email . ' ' . $url . ' ' . $IP . ' ' . $content;
497
+ $cDesc = '';
498
+ if($author){
499
+ $cDesc = "Author: $author ";
500
+ }
501
+ if($email){
502
+ $cDesc .= "Email: $email ";
503
+ }
504
+ $cDesc = "Source IP: $IP ";
505
+ $this->status(2, 'info', "Scanning comment with $cDesc");
506
+
507
+ $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
508
+ $h->hoover(1, $content);
509
+ $hooverResults = $h->getBaddies();
510
+ if($h->errorMsg){
511
+ return false;
512
+ }
513
+ if(sizeof($hooverResults) > 0 && isset($hooverResults[1])){
514
+ $hresults = $hooverResults[1];
515
+ foreach($hresults as $result){
516
+ if($result['badList'] == 'goog-malware-shavar'){
517
+ $this->status(2, 'info', "Marking comment as spam for containing a malware URL. Comment has $cDesc");
518
+ return true;
519
+ } else if($result['badList'] == 'googpub-phish-shavar'){
520
+ $this->status(2, 'info', "Marking comment as spam for containing a phishing URL. Comment has $cDesc");
521
+ return true;
522
+ } else {
523
+ //A list type that may be new and the plugin has not been upgraded yet.
524
+ continue;
525
+ }
526
+ }
527
+ }
528
+ $this->status(2, 'info', "Scanned comment with $cDesc");
529
+ return false;
530
  }
531
  public function getBlogsToScan($table){
532
  $wfdb = new wfDB();
571
  }
572
  return false;
573
  }
574
+ private function scan_passwds(){
575
+ $this->statusIDX['passwds'] = wordfence::statusStart('Scanning for weak passwords');
576
  global $wpdb;
577
  $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
578
  $haveIssues = false;
581
  $isWeak = $this->scanUserPassword($user->ID);
582
  if($isWeak){ $haveIssues = true; }
583
  }
584
+ wordfence::statusEnd($this->statusIDX['passwds'], $haveIssues);
585
  }
586
  public function scanUserPassword($userID){
587
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
588
+ $passwdHasher = new PasswordHash(8, TRUE);
589
  $userDat = get_userdata($userID);
590
  $this->status(2, 'info', "Checking password strength of user '" . $userDat->user_login . "'");
591
  $shortMsg = "";
605
  }
606
  $haveIssue = false;
607
  for($i = 0; $i < sizeof($words); $i++){
608
+ if($passwdHasher->CheckPassword($words[$i], $userDat->user_pass)){
609
  $this->status(2, 'info', "Adding issue " . $shortMsg);
610
  if($this->addIssue('easyPassword', $level, $userDat->ID, $userDat->ID . '-' . $userDat->user_pass, $shortMsg, $longMsg, array(
611
  'ID' => $userDat->ID,
623
  $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
624
  return $haveIssue;
625
  }
626
+ private function scan_diskSpace(){
627
+ $this->statusIDX['diskSpace'] = wordfence::statusStart("Scanning to check available disk space");
628
  $total = disk_total_space('.');
629
  $free = disk_free_space('.');
630
  $this->status(2, 'info', "Total space: $total Free space: $free");
631
  if( (! $total) || (! $free )){ //If we get zeros it's probably not reading right. If free is zero then we're out of space and already in trouble.
632
+ wordfence::statusEnd($this->statusIDX['diskSpace'], false);
633
  return;
634
  }
635
  $level = false;
640
  } else if($spaceLeft < 1.5){
641
  $level = 2;
642
  } else {
643
+ wordfence::statusEnd($this->statusIDX['diskSpace'], false);
644
  return;
645
  }
646
  if($this->addIssue('diskSpace', $level, 'diskSpace' . $level, 'diskSpace' . $level, "You have $spaceLeft" . "% disk space remaining", "You only have $spaceLeft" . "% of your disk space remaining. Please free up disk space or your website may stop serving requests.", array(
647
  'spaceLeft' => $spaceLeft ))){
648
+ wordfence::statusEnd($this->statusIDX['diskSpace'], true);
649
  } else {
650
+ wordfence::statusEnd($this->statusIDX['diskSpace'], false);
651
  }
652
  }
653
+ private function scan_dns(){
654
  if(! function_exists('dns_get_record')){
655
  $this->status(1, 'info', "Skipping DNS scan because this system does not support dns_get_record()");
656
  return;
657
  }
658
+ $this->statusIDX['dns'] = wordfence::statusStart("Scanning DNS for unauthorized changes");
659
  $haveIssues = false;
660
  $home = get_home_url();
661
  if(preg_match('/https?:\/\/([^\/]+)/i', $home, $matches)){
747
 
748
  wfConfig::set('wf_dnsLogged', 1);
749
  }
750
+ wordfence::statusEnd($this->statusIDX['dns'], $haveIssues);
751
  }
752
+ private function scan_oldVersions(){
753
+ $this->statusIDX['oldVersions'] = wordfence::statusStart("Scanning for old themes, plugins and core files");
754
  if(! function_exists( 'get_preferred_from_update_core')){
755
  require_once(ABSPATH . 'wp-admin/includes/update.php');
756
  }
806
 
807
  }
808
  }
809
+ wordfence::statusEnd($this->statusIDX['oldVersions'], $haveIssues);
810
  }
811
  private function errorStop($msg){
812
  $this->errorStopped = true;
819
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
820
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
821
  }
822
+ public static function startScan($isFork = false){
823
+ wordfence::status(4, 'info', "Entering start scan routine");
824
+ if(wfUtils::isScanRunning()){
825
+ return "A scan is already running.";
826
+ }
827
+
828
+ $cron_url = plugins_url('wordfence/wfscan.php?isFork=' . ($isFork ? '1' : '0'));
829
+ wordfence::status(4, 'info', "Cron URL is: " . $cron_url);
830
+ $cronKey = wfUtils::bigRandomHex();
831
+ wordfence::status(4, 'info', "cronKey is: " . $cronKey);
832
+ wfConfig::set('currentCronKey', time() . ',' . $cronKey);
833
+ wordfence::status(4, 'info', "cronKey is set");
834
+ $result = wp_remote_post( $cron_url, array(
835
+ 'timeout' => 0.5,
836
+ 'blocking' => true,
837
+ 'sslverify' => false,
838
+ 'headers' => array(
839
+ 'x-wordfence-cronkey' => $cronKey
840
+ )
841
+ ) );
842
+ $procResp = self::processResponse($result);
843
+ if($procResp){ return $procResp; }
844
+ //If the currentCronKey was eaten, then cron executed so return
845
+ wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){
846
+ wordfence::status(4, 'info', "cronkey is empty so cron executed. Returning.");
847
+ return false;
848
+ }
849
+
850
+
851
+ //This request is for hosts that don't know their own name. i.e. they don't have example.com in their hosts file or DNS pointing to their own IP address or loopback address. So we throw a hail mary to loopback.
852
+ wordfence::status(4, 'info', "cronkey is still set so sleeping for 0.2 seconds and checking again before trying another approach");
853
+ usleep(200000);
854
+ wfConfig::clearCache();
855
+ if(wfConfig::get('currentCronKey')){ //cron key is still set, so cron hasn't executed yet. Maybe the request didn't go through
856
+ wordfence::status(4, 'info', "cronkey is still set so about to manually set host header and try again");
857
+ $cron_url2 = preg_replace('/^(https?):\/\/[^\/]+/', '$1://127.0.0.1', $cron_url);
858
+ wordfence::status(4, 'info', "cron url is: $cron_url2");
859
+ $siteURL = site_url();
860
+ wordfence::status(4, 'info', "siteURL is: $siteURL");
861
+ if(preg_match('/^https?:\/\/([^\/]+)/i', site_url(), $matches)){
862
+ $host = $matches[1];
863
+ wordfence::status(4, 'info', "Extracted host $host from siteURL and trying remote post with manual host header set.");
864
+ $result = wp_remote_post( $cron_url2, array(
865
+ 'timeout' => 0.5,
866
+ 'blocking' => true,
867
+ 'sslverify' => false,
868
+ 'headers' => array(
869
+ 'x-wordfence-cronkey' => $cronKey,
870
+ 'Host' => $host
871
+ )
872
+ ) );
873
+ $procResp = self::processResponse($result);
874
+ if($procResp){ return $procResp; }
875
+ }
876
+ } else {
877
+ return false;
878
+ }
879
+
880
+ //Try again with longer timeout. Dreamhost was giving DNS lookup timeouts
881
+ usleep(200000);
882
+ wfConfig::clearCache();
883
+ if(wfConfig::get('currentCronKey')){
884
+ wordfence::status(4, "info", "cronkey is still set. Trying hostname again with longer timeout.");
885
+ $result = wp_remote_post( $cron_url, array(
886
+ 'timeout' => 10,
887
+ 'blocking' => true,
888
+ 'sslverify' => false,
889
+ 'headers' => array(
890
+ 'x-wordfence-cronkey' => $cronKey
891
+ )
892
+ ) );
893
+ $procResp = self::processResponse($result);
894
+ if($procResp){ return $procResp; }
895
+ //If the currentCronKey was eaten, then cron executed so return
896
+ wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){
897
+ wordfence::status(4, 'info', "cronkey is empty so cron executed. Returning.");
898
+ return false;
899
+ }
900
+ }
901
+ return false;
902
+ }
903
+ public function processResponse($result){
904
+ if(is_wp_error($result)){
905
+ wordfence::status(4, 'info', "Debug output of WP_Error: " . implode('; ', $result->get_error_messages()) );
906
+ } else {
907
+ if(is_array($result)){
908
+ if(is_array($result['response'])){
909
+ wordfence::status(4, 'info', "POST response: " . $result['response']['code'] . ' ' . $result['response']['message']);
910
+ }
911
+ wordfence::status(4, 'info', "POST response body: " . $result['body']);
912
+ }
913
+ }
914
+ if((! is_wp_error($result)) && is_array($result) && empty($result['body']) === false){
915
+ if(strpos($result['body'], 'WFSOURCEVISIBLE') !== false){
916
+ wordfence::status(4, 'info', "wfscan.php source is visible.");
917
+ $msg = "Wordfence can't run because the source code of your WordPress plugin files is visible from the Internet. This is a serious security risk which you need to fix. Please look for .htaccess files in your WordPress root directory and your wp-content/ and wp-content/plugins/ directories that may contain malicious code designed to reveal your site source code to a hacker.";
918
+ $htfiles = array();
919
+ if(file_exists(ABSPATH . 'wp-content/.htaccess')){
920
+ array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/.htaccess" target="_blank">wp-content/.htaccess</a>');
921
+ }
922
+ if(file_exists(ABSPATH . 'wp-content/plugins/.htaccess')){
923
+ array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/plugins/.htaccess" target="_blank">wp-content/plugins/.htaccess</a>');
924
+ }
925
+ if(sizeof($htfiles) > 0){
926
+ $msg .= "<br /><br />Click to view the .htaccess files below that may be the cause of this problem:<br />" . implode('<br />', $htfiles);
927
+ }
928
+ return $msg;
929
+
930
+ } else if(strpos($result['body'], '{') !== false && strpos($result['body'], 'errorMsg') !== false){
931
+ wordfence::status(4, 'info', "Got response from cron containing json");
932
+ $resp = json_decode($result['body'], true);
933
+ if(empty($resp['errorMsg']) === false){
934
+ wordfence::status(4, 'info', "Got an error message from cron: " . $resp['errorMsg']);
935
+ return $resp['errorMsg'];
936
+ }
937
+ }
938
+ }
939
+ return false;
940
+ }
941
  }
942
 
943
  ?>
lib/wfUtils.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
  class wfUtils {
3
- private static $reverseLookupCache = array();
 
4
  public static function makeTimeAgo($secs, $noSeconds = false) {
5
  if($secs < 1){
6
  return "a moment";
@@ -66,7 +67,8 @@ class wfUtils {
66
  return plugins_url() . '/wordfence/';
67
  }
68
  public static function getPluginBaseDir(){
69
- return ABSPATH . 'wp-content/plugins/';
 
70
  }
71
  public static function getIP(){
72
  $ip = 0;
@@ -81,18 +83,7 @@ class wfUtils {
81
  public static function getRequestedURL(){
82
  return ($_SERVER['HTTPS'] ? 'https' : 'http') . '://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
83
  }
84
- public static function reverseLookup($IP){
85
- if(! isset(self::$reverseLookupCache[$IP])){
86
- $ptr = implode(".", array_reverse(explode(".",$IP))) . ".in-addr.arpa";
87
- $host = dns_get_record($ptr, DNS_PTR);
88
- if($host == null){
89
- self::$reverseLookupCache[$IP] = '';
90
- } else {
91
- self::$reverseLookupCache[$IP] = $host[0]['target'];
92
- }
93
- }
94
- return self::$reverseLookupCache[$IP];
95
- }
96
  public static function editUserLink($userID){
97
  return get_admin_url() . 'user-edit.php?user_id=' . $userID;
98
  }
@@ -196,6 +187,153 @@ class wfUtils {
196
  }
197
  return false;
198
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
 
201
 
1
  <?php
2
  class wfUtils {
3
+ private static $isWindows = false;
4
+ public static $scanLockFH = false;
5
  public static function makeTimeAgo($secs, $noSeconds = false) {
6
  if($secs < 1){
7
  return "a moment";
67
  return plugins_url() . '/wordfence/';
68
  }
69
  public static function getPluginBaseDir(){
70
+ return WP_CONTENT_DIR . '/plugins/';
71
+ //return ABSPATH . 'wp-content/plugins/';
72
  }
73
  public static function getIP(){
74
  $ip = 0;
83
  public static function getRequestedURL(){
84
  return ($_SERVER['HTTPS'] ? 'https' : 'http') . '://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
85
  }
86
+
 
 
 
 
 
 
 
 
 
 
 
87
  public static function editUserLink($userID){
88
  return get_admin_url() . 'user-edit.php?user_id=' . $userID;
89
  }
187
  }
188
  return false;
189
  }
190
+ public static function isWindows(){
191
+ if(! self::$isWindows){
192
+ if(preg_match('/^win/', PHP_OS)){
193
+ self::$isWindows = 'yes';
194
+ } else {
195
+ self::$isWindows = 'no';
196
+ }
197
+ }
198
+ return self::$isWindows == 'yes' ? true : false;
199
+ }
200
+ public static function getScanLock(){
201
+ if(self::isWindows()){
202
+ //Windows does not support non-blocking flock, so we use time.
203
+ $scanRunning = wfConfig::get('wf_scanRunning');
204
+ if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
205
+ return false;
206
+ }
207
+ wfConfig::set('wf_scanRunning', time());
208
+ return true;
209
+ } else {
210
+ self::$scanLockFH = fopen(__FILE__, 'r');
211
+ if(flock(self::$scanLockFH, LOCK_EX | LOCK_NB)){
212
+ return true;
213
+ } else {
214
+ return false;
215
+ }
216
+ }
217
+ }
218
+ public static function clearScanLock(){
219
+ if(self::isWindows()){
220
+ wfConfig::set('wf_scanRunning', '');
221
+ } else {
222
+ if(self::$scanLockFH){
223
+ @fclose(self::$scanLockFH);
224
+ self::$scanLockFH = false;
225
+ }
226
+ }
227
+
228
+ }
229
+ public static function isScanRunning(){
230
+ $scanRunning = true;
231
+ if(self::getScanLock()){
232
+ $scanRunning = false;
233
+ }
234
+ self::clearScanLock();
235
+ return $scanRunning;
236
+ }
237
+ public static function getIPGeo($IP){ //Works with int or dotted
238
+
239
+ $locs = self::getIPsGeo(array($IP));
240
+ if(isset($locs[$IP])){
241
+ return $locs[$IP];
242
+ } else {
243
+ return false;
244
+ }
245
+ }
246
+ public static function getIPsGeo($IPs){ //works with int or dotted. Outputs same format it receives.
247
+ $IPs = array_unique($IPs);
248
+ $isInt = false;
249
+ if(strpos($IPs[0], '.') === false){
250
+ $isInt = true;
251
+ }
252
+ $toResolve = array();
253
+ $db = new wfDB();
254
+ global $wp_version;
255
+ global $wpdb;
256
+ $locsTable = $wpdb->base_prefix . 'wfLocs';
257
+ $IPLocs = array();
258
+ foreach($IPs as $IP){
259
+ $r1 = $db->query("select IP, ctime, failed, city, region, countryName, countryCode, lat, lon, unix_timestamp() - ctime as age from " . $locsTable . " where IP=%s", ($isInt ? $IP : self::inet_aton($IP)) );
260
+ if($r1){
261
+ if($row = mysql_fetch_assoc($r1)){
262
+ if($row['age'] > WORDFENCE_MAX_IPLOC_AGE){
263
+ $db->query("delete from " . $locsTable . " where IP=%s", $row['IP']);
264
+ } else {
265
+ if($row['failed'] == 1){
266
+ $IPLocs[$IP] = false;
267
+ } else {
268
+ if(! $isInt){
269
+ $row['IP'] = self::inet_ntoa($row['IP']);
270
+ }
271
+ $IPLocs[$IP] = $row;
272
+ }
273
+ }
274
+ }
275
+ }
276
+ if(! isset($IPLocs[$IP])){
277
+ $toResolve[] = $IP;
278
+ }
279
+ }
280
+ if(sizeof($toResolve) > 0){
281
+ $api = new wfAPI(wfConfig::get('apiKey'), $wp_version);
282
+ $freshIPs = $api->call('resolve_ips', array(), array(
283
+ 'ips' => implode(',', $toResolve)
284
+ ));
285
+ if(is_array($freshIPs)){
286
+ foreach($freshIPs as $IP => $value){
287
+ if($value == 'failed'){
288
+ $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed) values (%s, unix_timestamp(), 1)", ($isInt ? $IP : self::inet_aton($IP)) );
289
+ $IPLocs[$IP] = false;
290
+ } else {
291
+ $db->query("insert IGNORE into " . $locsTable . " (IP, ctime, failed, city, region, countryName, countryCode, lat, lon) values (%s, unix_timestamp(), 0, '%s', '%s', '%s', '%s', %s, %s)",
292
+ ($isInt ? $IP : self::inet_aton($IP)),
293
+ $value[3], //city
294
+ $value[2], //region
295
+ $value[1], //countryName
296
+ $value[0],//countryCode
297
+ $value[4],//lat
298
+ $value[5]//lon
299
+ );
300
+ $IPLocs[$IP] = array(
301
+ 'IP' => $IP,
302
+ 'city' => $value[3],
303
+ 'region' => $value[2],
304
+ 'countryName' => $value[1],
305
+ 'countryCode' => $value[0],
306
+ 'lat' => $value[4],
307
+ 'lon' => $value[5]
308
+ );
309
+ }
310
+ }
311
+ }
312
+ }
313
+ return $IPLocs;
314
+ }
315
+ public function reverseLookup($IP){
316
+ $db = new wfDB();
317
+ global $wpdb;
318
+ $reverseTable = $wpdb->base_prefix . 'wfReverseCache';
319
+ $IPn = wfUtils::inet_aton($IP);
320
+ $host = $db->querySingle("select host from " . $reverseTable . " where IP=%s and unix_timestamp() - lastUpdate < %d", $IPn, WORDFENCE_REVERSE_LOOKUP_CACHE_TIME);
321
+ if(! $host){
322
+ $ptr = implode(".", array_reverse(explode(".",$IP))) . ".in-addr.arpa";
323
+ $host = dns_get_record($ptr, DNS_PTR);
324
+ if($host == null){
325
+ $host = 'NONE';
326
+ } else {
327
+ $host = $host[0]['target'];
328
+ }
329
+ $db->query("insert into " . $reverseTable . " (IP, host, lastUpdate) values (%s, '%s', unix_timestamp()) ON DUPLICATE KEY UPDATE host='%s', lastUpdate=unix_timestamp()", $IPn, $host, $host);
330
+ }
331
+ if($host == 'NONE'){
332
+ return '';
333
+ } else {
334
+ return $host;
335
+ }
336
+ }
337
  }
338
 
339
 
lib/wordfenceClass.php CHANGED
@@ -165,8 +165,9 @@ class wordfence {
165
  wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron');
166
  }
167
  $db = new wfDB();
 
168
  //Upgrading from 1.5.6 or earlier needs:
169
- $db->createKeyIfNotExists($prefix . 'wfStatus', 'level', 'k2');
170
  if(wfConfig::get('isPaid') == 'free'){
171
  wfConfig::set('isPaid', '');
172
  }
@@ -180,6 +181,10 @@ class wordfence {
180
  @chmod(dirname(__FILE__) . '/../wfscan.php', 0755);
181
  @chmod(dirname(__FILE__) . '/../visitor.php', 0755);
182
 
 
 
 
 
183
  //Must be the final line
184
  update_option('wordfence_version', WORDFENCE_VERSION);
185
  }
@@ -269,7 +274,7 @@ class wordfence {
269
  $user = get_user_by('email', $_POST['user_login']);
270
  if($user){
271
  if(wfConfig::get('alertOn_lostPasswdForm')){
272
- wordfence::alert("Password recovery attempted", "Someone tried to recover the password for user with email address: $email\nTheir IP address was: " . wfUtils::getIP() . "\nTheir hostname was: " . self::getLog()->reverseLookup(wfUtils::getIP()));
273
  }
274
  }
275
  if(wfConfig::get('loginSecurityEnabled')){
@@ -289,7 +294,7 @@ class wordfence {
289
  }
290
  public static function lockOutIP($IP, $reason){
291
  if(wfConfig::get('alertOn_loginLockout')){
292
- wordfence::alert("User locked out from signing in", "A user with IP address $IP has been locked out from the signing in or using the password recovery form for the following reason: $reason");
293
  }
294
  self::getLog()->lockOutIP(wfUtils::getIP(), $reason);
295
  }
@@ -297,7 +302,7 @@ class wordfence {
297
  return self::getLog()->isIPLockedOut($IP);
298
  }
299
  public static function veryFirstAction(){
300
- $wfFunc = $_GET['_wfsf'];
301
  if($wfFunc == 'unlockEmail'){
302
  $email = trim($_POST['email']);
303
  global $wpdb;
@@ -381,11 +386,11 @@ class wordfence {
381
  self::getLog()->logLogin('loginOK', 0, $username);
382
  if(user_can($userID, 'update_core')){
383
  if(wfConfig::get('alertOn_adminLogin')){
384
- wordfence::alert("Admin Login", "A user with username \"$username\" who has administrator access signed in to your WordPress site.");
385
  }
386
  } else {
387
  if(wfConfig::get('alertOn_nonAdminLogin')){
388
- wordfence::alert("User login", "A non-admin user with username \"$username\" signed in to your WordPress site.");
389
  }
390
  }
391
  }
@@ -445,26 +450,25 @@ class wordfence {
445
  }
446
  public static function getWPFileContent($file, $cType, $cName, $cVersion){
447
  if($cType == 'plugin'){
448
- $file = realpath(ABSPATH . $file);
449
- $file = substr($file, strlen(realpath(dirname(__FILE__) . '/../../')));
450
- $file = preg_replace('/^\/[^\/]+\//', '', $file);
 
 
 
451
  } else if($cType == 'theme'){
452
- $themeDir = substr(WP_CONTENT_DIR, strlen(ABSPATH)) . get_theme_roots();
453
- $file = preg_replace('#' . $themeDir . '/[^/]+/#', '', $file);
 
 
 
454
  } else if($cType == 'core'){
455
 
456
  } else {
457
  return array('errorMsg' => "An invalid type was specified to get file.");
458
  }
459
-
460
- $transKey = 'wf_wpFileContent_' . $file . '_' . $cType . '_' . $cName . '_' . $cVersion;
461
- $transKey = preg_replace('/[^a-zA-Z0-9\_]+/', '_', $transKey);
462
- $content = get_site_transient($transKey);
463
- if($content){
464
- return array('fileContent' => $content);
465
- }
466
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
467
- $dat = $api->call('get_wp_file_content', array(
468
  'file' => $file,
469
  'cType' => $cType,
470
  'cName' => $cName,
@@ -473,9 +477,8 @@ class wordfence {
473
  if($api->errorMsg){
474
  return array('errorMsg' => $api->errorMsg);
475
  }
476
- if($dat['contents']){
477
- set_site_transient($transKey, $dat['contents'], WORDFENCE_TRANSIENTS_TIMEOUT);
478
- return array('fileContent' => $dat['contents']);
479
  } else {
480
  return array('errorMsg' => "We could not fetch a core WordPress file from the Wordfence API.");
481
  }
@@ -683,7 +686,7 @@ class wordfence {
683
  $ips = explode(',', $_POST['ips']);
684
  $res = array();
685
  foreach($ips as $ip){
686
- $res[$ip] = self::getLog()->reverseLookup($ip);
687
  }
688
  return array('ok' => 1, 'ips' => $res);
689
  }
@@ -795,32 +798,37 @@ class wordfence {
795
  $wfIssues = new wfIssues();
796
  $issue = $wfIssues->getIssueByID($issueID);
797
  if(! $issue){
798
- return array('errorMsg' => "We could not find that issue in our database.");
799
  }
800
  $dat = $issue['data'];
801
  $result = self::getWPFileContent($dat['file'], $dat['cType'], $dat['cName'], $dat['cVersion']);
802
  $file = $dat['file'];
803
- if($result['errorMsg']){
804
  return $result;
805
  } else if(! $result['fileContent']){
806
- return array('errorMsg' => "We could not get the original file to do a repair.");
807
  }
808
 
809
  if(preg_match('/\.\./', $file)){
810
- return array('errorMsg' => "An invalid file was specified for repair.");
811
  }
812
  $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
813
  $fh = fopen($localFile, 'w');
814
  if(! $fh){
815
  $err = error_get_last();
816
- return array('errorMsg' => "We could not write to that file. The error was: " . $err['message']);
 
 
 
 
 
817
  }
818
  flock($fh, LOCK_EX);
819
  $bytes = fwrite($fh, $result['fileContent']);
820
  flock($fh, LOCK_UN);
821
  fclose($fh);
822
  if($bytes < 1){
823
- return array('errorMsg' => "We could not write to that file. ($bytes bytes written) You may not have permission to modify files on your WordPress server.");
824
  }
825
  $wfIssues->updateIssue($issueID, 'delete');
826
  return array(
@@ -849,7 +857,7 @@ class wordfence {
849
  }
850
  if($result['ok'] && isset($result['isPaid'])){
851
  wfConfig::set('isPaid', $result['isPaid']);
852
- $err = self::startScan();
853
  if($err){
854
  return array('errorMsg' => $err);
855
  } else {
@@ -861,7 +869,7 @@ class wordfence {
861
  }
862
  public static function ajax_scan_callback(){
863
  self::status(4, 'info', "Ajax request received to start scan.");
864
- $err = self::startScan();
865
  if($err){
866
  return array('errorMsg' => $err);
867
  } else {
@@ -869,84 +877,7 @@ class wordfence {
869
  }
870
  }
871
  public static function startScan(){
872
- self::status(4, 'info', "Entering start scan routine");
873
- $cron_url = plugins_url('wordfence/wfscan.php');
874
- self::status(4, 'info', "Cron URL is: " . $cron_url);
875
- $cronKey = wfUtils::bigRandomHex();
876
- self::status(4, 'info', "cronKey is: " . $cronKey);
877
- wfConfig::set('currentCronKey', time() . ',' . $cronKey);
878
- self::status(4, 'info', "cronKey is set");
879
- $result = wp_remote_post( $cron_url, array(
880
- 'timeout' => 0.5,
881
- 'blocking' => true,
882
- 'sslverify' => false,
883
- 'headers' => array(
884
- 'x-wordfence-cronkey' => $cronKey
885
- )
886
- ) );
887
- $procResp = self::processResponse($result);
888
- if($procResp){ return $procResp; }
889
- //If the currentCronKey was eaten, then cron executed so return
890
- wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){
891
- self::status(4, 'info', "cronkey is empty so cron executed. Returning.");
892
- return false;
893
- }
894
-
895
- //This second request is for hosts that don't know their own name. i.e. they don't have example.com in their hosts file or DNS pointing to their own IP address or loopback address. So we throw a hail mary to loopback.
896
- self::status(4, 'info', "cronkey is still set so sleeping for 0.2 seconds and checking again before trying another approach");
897
- usleep(200000);
898
- wfConfig::clearCache();
899
- if(wfConfig::get('currentCronKey')){ //cron key is still set, so cron hasn't executed yet. Maybe the request didn't go through
900
- self::status(4, 'info', "cronkey is still set so about to manually set host header and try again");
901
- $cron_url = preg_replace('/^(https?):\/\/[^\/]+/', '$1://127.0.0.1', $cron_url);
902
- self::status(4, 'info', "cron url is: $cron_url");
903
- $siteURL = site_url();
904
- self::status(4, 'info', "siteURL is: $siteURL");
905
- if(preg_match('/^https?:\/\/([^\/]+)/i', site_url(), $matches)){
906
- $host = $matches[1];
907
- self::status(4, 'info', "Extracted host $host from siteURL and trying remote post with manual host header set.");
908
- $result = wp_remote_post( $cron_url, array(
909
- 'timeout' => 0.5,
910
- 'blocking' => true,
911
- 'sslverify' => false,
912
- 'headers' => array(
913
- 'x-wordfence-cronkey' => $cronKey,
914
- 'Host' => $host
915
- )
916
- ) );
917
- $procResp = self::processResponse($result);
918
- if($procResp){ return $procResp; }
919
- }
920
- }
921
- return false;
922
- }
923
- public function processResponse($result){
924
- if((! is_wp_error($result)) && is_array($result) && empty($result['body']) === false){
925
- if(strpos($result['body'], 'WFSOURCEVISIBLE') !== false){
926
- self::status(4, 'info', "wfscan.php source is visible.");
927
- $msg = "Wordfence can't run because the source code of your WordPress plugin files is visible from the Internet. This is a serious security risk which you need to fix. Please look for .htaccess files in your WordPress root directory and your wp-content/ and wp-content/plugins/ directories that may contain malicious code designed to reveal your site source code to a hacker.";
928
- $htfiles = array();
929
- if(file_exists(ABSPATH . 'wp-content/.htaccess')){
930
- array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/.htaccess" target="_blank">wp-content/.htaccess</a>');
931
- }
932
- if(file_exists(ABSPATH . 'wp-content/plugins/.htaccess')){
933
- array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/plugins/.htaccess" target="_blank">wp-content/plugins/.htaccess</a>');
934
- }
935
- if(sizeof($htfiles) > 0){
936
- $msg .= "<br /><br />Click to view the .htaccess files below that may be the cause of this problem:<br />" . implode('<br />', $htfiles);
937
- }
938
- return $msg;
939
-
940
- } else if(strpos($result['body'], '{') !== false && strpos($result['body'], 'errorMsg') !== false){
941
- self::status(4, 'info', "Got response from cron containing json");
942
- $resp = json_decode($result['body'], true);
943
- if(empty($resp['errorMsg']) === false){
944
- self::status(4, 'info', "Got an error message from cron: " . $resp['errorMsg']);
945
- return $resp['errorMsg'];
946
- }
947
- }
948
- }
949
- return false;
950
  }
951
  public static function templateRedir(){
952
  $wfFunc = get_query_var('_wfsf');
@@ -989,7 +920,7 @@ class wordfence {
989
  self::wfFunc_testmem();
990
  } else if($wfFunc == 'testtime'){
991
  self::wfFunc_testtime();
992
- }
993
  exit(0);
994
  }
995
  public static function memtest_error_handler($errno, $errstr, $errfile, $errline){
@@ -1063,7 +994,7 @@ class wordfence {
1063
  }
1064
  public static function wfFunc_IPTraf(){
1065
  $IP = $_GET['IP'];
1066
- $reverseLookup = self::getLog()->reverseLookup($IP);
1067
  if(! preg_match('/^\d+\.\d+\.\d+\.\d+$/', $IP)){
1068
  echo "An invalid IP address was specified.";
1069
  exit(0);
@@ -1125,21 +1056,19 @@ class wordfence {
1125
  }
1126
 
1127
  $localFile = realpath(ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file']));
1128
- if(strpos($localFile, ABSPATH) !== 0){
1129
- echo "An invalid file was requested for comparison.";
1130
- exit(0);
1131
- }
1132
- $diffOptions = array();
1133
  $localContents = file_get_contents($localFile);
1134
-
1135
- $diff = new Diff(
1136
- //Treat DOS and Unix files the same
1137
- preg_split("/(?:\r\n|\n)/", $result['fileContent']),
1138
- preg_split("/(?:\r\n|\n)/", $localContents),
1139
- array()
1140
- );
1141
- $renderer = new Diff_Renderer_Html_SideBySide;
1142
- $diffResult = $diff->Render($renderer);
 
 
 
1143
  require 'diffResult.php';
1144
  exit(0);
1145
  }
@@ -1154,7 +1083,7 @@ class wordfence {
1154
  } else {
1155
  self::$newVisit = true;
1156
  }
1157
- setcookie($cookieName, uniqid(), time() + 1800, '/');
1158
  }
1159
  public static function admin_init(){
1160
  if(! wfUtils::isAdmin()){ return; }
@@ -1162,7 +1091,7 @@ class wordfence {
1162
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1163
  }
1164
 
1165
- if(preg_match('/^Wordfence/', $_GET['page'])){
1166
 
1167
  wp_enqueue_style('wordfence-main-style', WP_PLUGIN_URL . '/wordfence/css/main.css', '', WORDFENCE_VERSION);
1168
  wp_enqueue_style('wordfence-colorbox-style', WP_PLUGIN_URL . '/wordfence/css/colorbox.css', '', WORDFENCE_VERSION);
@@ -1203,7 +1132,7 @@ class wordfence {
1203
  }
1204
  }
1205
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1206
- add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png', 'div');
1207
  if(wfConfig::get('liveTrafficEnabled')){
1208
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1209
  }
@@ -1273,18 +1202,36 @@ class wordfence {
1273
  return admin_url('admin.php?page=WordfenceSecOpt', 'http');
1274
  }
1275
 
1276
- public static function alert($subject, $alertMsg){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1277
  $content = wfUtils::tmpl('email_genericAlert.php', array(
1278
  'subject' => $subject,
1279
  'blogName' => get_bloginfo('name', 'raw'),
1280
  'alertMsg' => $alertMsg,
 
1281
  'date' => date('l jS \of F Y \a\t h:i:s A'),
1282
  'myHomeURL' => self::getMyHomeURL(),
1283
  'myOptionsURL' => self::getMyOptionsURL()
1284
  ));
1285
  $emails = wfConfig::getAlertEmails();
1286
  if(sizeof($emails) < 1){ return; }
1287
- $subject = "[Wordfence Alert] " . $subject;
 
1288
  wp_mail(implode(',', $emails), $subject, $content);
1289
  }
1290
  public static function scheduleNextScan($force = false){
@@ -1316,24 +1263,34 @@ class wordfence {
1316
  }
1317
  return self::$wfLog;
1318
  }
 
 
 
 
 
1319
  public static function statusStart($msg){
1320
- self::$statusStartMsgs[] = $msg;
 
 
1321
  self::status(10, 'info', 'SUM_START:' . $msg);
1322
- return sizeof(self::$statusStartMsgs) - 1;
1323
  }
1324
  public static function statusEnd($idx, $haveIssues){
 
1325
  if($haveIssues){
1326
- self::status(10, 'info', 'SUM_ENDBAD:' . self::$statusStartMsgs[$idx]);
1327
  } else {
1328
- self::status(10, 'info', 'SUM_ENDOK:' . self::$statusStartMsgs[$idx]);
1329
  }
1330
- self::$statusStartMsgs[$idx] = '';
 
1331
  }
1332
  public static function statusEndErr(){
1333
- for($i = 0; $i < sizeof(self::$statusStartMsgs); $i++){
1334
- if(empty(self::$statusStartMsgs[$i]) === false){
1335
- self::status(10, 'info', 'SUM_ENDERR:' . self::$statusStartMsgs[$i]);
1336
- self::$statusStartMsgs[$i] = '';
 
1337
  }
1338
  }
1339
  }
165
  wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron');
166
  }
167
  $db = new wfDB();
168
+
169
  //Upgrading from 1.5.6 or earlier needs:
170
+ $db->createKeyIfNotExists('wfStatus', 'level', 'k2');
171
  if(wfConfig::get('isPaid') == 'free'){
172
  wfConfig::set('isPaid', '');
173
  }
181
  @chmod(dirname(__FILE__) . '/../wfscan.php', 0755);
182
  @chmod(dirname(__FILE__) . '/../visitor.php', 0755);
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);
190
  }
274
  $user = get_user_by('email', $_POST['user_login']);
275
  if($user){
276
  if(wfConfig::get('alertOn_lostPasswdForm')){
277
+ wordfence::alert("Password recovery attempted", "Someone tried to recover the password for user with email address: $email", wfUtils::getIP());
278
  }
279
  }
280
  if(wfConfig::get('loginSecurityEnabled')){
294
  }
295
  public static function lockOutIP($IP, $reason){
296
  if(wfConfig::get('alertOn_loginLockout')){
297
+ wordfence::alert("User locked out from signing in", "A user with IP address $IP has been locked out from the signing in or using the password recovery form for the following reason: $reason", $IP);
298
  }
299
  self::getLog()->lockOutIP(wfUtils::getIP(), $reason);
300
  }
302
  return self::getLog()->isIPLockedOut($IP);
303
  }
304
  public static function veryFirstAction(){
305
+ $wfFunc = @$_GET['_wfsf'];
306
  if($wfFunc == 'unlockEmail'){
307
  $email = trim($_POST['email']);
308
  global $wpdb;
386
  self::getLog()->logLogin('loginOK', 0, $username);
387
  if(user_can($userID, 'update_core')){
388
  if(wfConfig::get('alertOn_adminLogin')){
389
+ wordfence::alert("Admin Login", "A user with username \"$username\" who has administrator access signed in to your WordPress site.", wfUtils::getIP());
390
  }
391
  } else {
392
  if(wfConfig::get('alertOn_nonAdminLogin')){
393
+ wordfence::alert("User login", "A non-admin user with username \"$username\" signed in to your WordPress site.", wfUtils::getIP());
394
  }
395
  }
396
  }
450
  }
451
  public static function getWPFileContent($file, $cType, $cName, $cVersion){
452
  if($cType == 'plugin'){
453
+ if(preg_match('#^/?wp-content/plugins/[^/]+/#', $file)){
454
+ $file = preg_replace('#^/?wp-content/plugins/[^/]+/#', '', $file);
455
+ } else {
456
+ //If user is using non-standard wp-content dir, then use /plugins/ in pattern to figure out what to strip off
457
+ $file = preg_replace('#^.*[^/]+/plugins/[^/]+/#', '', $file);
458
+ }
459
  } else if($cType == 'theme'){
460
+ if(preg_match('#/?wp-content/themes/[^/]+/#', $file)){
461
+ $file = preg_replace('#/?wp-content/themes/[^/]+/#', '', $file);
462
+ } else {
463
+ $file = preg_replace('#^.*[^/]+/themes/[^/]+/#', '', $file);
464
+ }
465
  } else if($cType == 'core'){
466
 
467
  } else {
468
  return array('errorMsg' => "An invalid type was specified to get file.");
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,
477
  if($api->errorMsg){
478
  return array('errorMsg' => $api->errorMsg);
479
  }
480
+ if($contResult['data']){
481
+ return array('fileContent' => $contResult['data']);
 
482
  } else {
483
  return array('errorMsg' => "We could not fetch a core WordPress file from the Wordfence API.");
484
  }
686
  $ips = explode(',', $_POST['ips']);
687
  $res = array();
688
  foreach($ips as $ip){
689
+ $res[$ip] = wfUtils::reverseLookup($ip);
690
  }
691
  return array('ok' => 1, 'ips' => $res);
692
  }
798
  $wfIssues = new wfIssues();
799
  $issue = $wfIssues->getIssueByID($issueID);
800
  if(! $issue){
801
+ return array('cerrorMsg' => "We could not find that issue in our database.");
802
  }
803
  $dat = $issue['data'];
804
  $result = self::getWPFileContent($dat['file'], $dat['cType'], $dat['cName'], $dat['cVersion']);
805
  $file = $dat['file'];
806
+ if($result['cerrorMsg']){
807
  return $result;
808
  } else if(! $result['fileContent']){
809
+ return array('cerrorMsg' => "We could not get the original file to do a repair.");
810
  }
811
 
812
  if(preg_match('/\.\./', $file)){
813
+ return array('cerrorMsg' => "An invalid file was specified for repair.");
814
  }
815
  $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
816
  $fh = fopen($localFile, 'w');
817
  if(! $fh){
818
  $err = error_get_last();
819
+ if(preg_match('/Permission denied/i', $err['message'])){
820
+ $errMsg = "You don't have permission to repair that file. You need to either fix the file manually using FTP or change the file permissions and ownership so that your web server has write access to repair the file.";
821
+ } else {
822
+ $errMsg = "We could not write to that file. The error was: " . $err['message'];
823
+ }
824
+ return array('cerrorMsg' => $errMsg);
825
  }
826
  flock($fh, LOCK_EX);
827
  $bytes = fwrite($fh, $result['fileContent']);
828
  flock($fh, LOCK_UN);
829
  fclose($fh);
830
  if($bytes < 1){
831
+ return array('cerrorMsg' => "We could not write to that file. ($bytes bytes written) You may not have permission to modify files on your WordPress server.");
832
  }
833
  $wfIssues->updateIssue($issueID, 'delete');
834
  return array(
857
  }
858
  if($result['ok'] && isset($result['isPaid'])){
859
  wfConfig::set('isPaid', $result['isPaid']);
860
+ $err = wfScanEngine::startScan();
861
  if($err){
862
  return array('errorMsg' => $err);
863
  } else {
869
  }
870
  public static function ajax_scan_callback(){
871
  self::status(4, 'info', "Ajax request received to start scan.");
872
+ $err = wfScanEngine::startScan();
873
  if($err){
874
  return array('errorMsg' => $err);
875
  } else {
877
  }
878
  }
879
  public static function startScan(){
880
+ wfScanEngine::startScan();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  }
882
  public static function templateRedir(){
883
  $wfFunc = get_query_var('_wfsf');
920
  self::wfFunc_testmem();
921
  } else if($wfFunc == 'testtime'){
922
  self::wfFunc_testtime();
923
+ }
924
  exit(0);
925
  }
926
  public static function memtest_error_handler($errno, $errstr, $errfile, $errline){
994
  }
995
  public static function wfFunc_IPTraf(){
996
  $IP = $_GET['IP'];
997
+ $reverseLookup = wfUtils::reverseLookup($IP);
998
  if(! preg_match('/^\d+\.\d+\.\d+\.\d+$/', $IP)){
999
  echo "An invalid IP address was specified.";
1000
  exit(0);
1056
  }
1057
 
1058
  $localFile = realpath(ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file']));
 
 
 
 
 
1059
  $localContents = file_get_contents($localFile);
1060
+ if($localContents == $result['fileContent']){
1061
+ $diffResult = '';
1062
+ } else {
1063
+ $diff = new Diff(
1064
+ //Treat DOS and Unix files the same
1065
+ preg_split("/(?:\r\n|\n)/", $result['fileContent']),
1066
+ preg_split("/(?:\r\n|\n)/", $localContents),
1067
+ array()
1068
+ );
1069
+ $renderer = new Diff_Renderer_Html_SideBySide;
1070
+ $diffResult = $diff->Render($renderer);
1071
+ }
1072
  require 'diffResult.php';
1073
  exit(0);
1074
  }
1083
  } else {
1084
  self::$newVisit = true;
1085
  }
1086
+ @setcookie($cookieName, uniqid(), time() + 1800, '/');
1087
  }
1088
  public static function admin_init(){
1089
  if(! wfUtils::isAdmin()){ return; }
1091
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1092
  }
1093
 
1094
+ if(preg_match('/^Wordfence/', @$_GET['page'])){
1095
 
1096
  wp_enqueue_style('wordfence-main-style', WP_PLUGIN_URL . '/wordfence/css/main.css', '', WORDFENCE_VERSION);
1097
  wp_enqueue_style('wordfence-colorbox-style', WP_PLUGIN_URL . '/wordfence/css/colorbox.css', '', WORDFENCE_VERSION);
1132
  }
1133
  }
1134
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1135
+ add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png');
1136
  if(wfConfig::get('liveTrafficEnabled')){
1137
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1138
  }
1202
  return admin_url('admin.php?page=WordfenceSecOpt', 'http');
1203
  }
1204
 
1205
+ public static function alert($subject, $alertMsg, $IP){
1206
+ $IPMsg = "";
1207
+ if($IP){
1208
+ $IPMsg = "User IP: $IP\n";
1209
+ $reverse = wfUtils::reverseLookup($IP);
1210
+ if($reverse){
1211
+ $IPMsg .= "User hostname: " . $reverse . "\n";
1212
+ }
1213
+ $userLoc = wfUtils::getIPGeo($IP);
1214
+ if($userLoc){
1215
+ $IPMsg .= "User location: ";
1216
+ if($userLoc['city']){
1217
+ $IPMsg .= $userLoc['city'] . ', ';
1218
+ }
1219
+ $IPMsg .= $userLoc['countryName'] . "\n";
1220
+ }
1221
+ }
1222
  $content = wfUtils::tmpl('email_genericAlert.php', array(
1223
  'subject' => $subject,
1224
  'blogName' => get_bloginfo('name', 'raw'),
1225
  'alertMsg' => $alertMsg,
1226
+ 'IPMsg' => $IPMsg,
1227
  'date' => date('l jS \of F Y \a\t h:i:s A'),
1228
  'myHomeURL' => self::getMyHomeURL(),
1229
  'myOptionsURL' => self::getMyOptionsURL()
1230
  ));
1231
  $emails = wfConfig::getAlertEmails();
1232
  if(sizeof($emails) < 1){ return; }
1233
+ $shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
1234
+ $subject = "[Wordfence Alert] $shortSiteURL " . $subject;
1235
  wp_mail(implode(',', $emails), $subject, $content);
1236
  }
1237
  public static function scheduleNextScan($force = false){
1263
  }
1264
  return self::$wfLog;
1265
  }
1266
+ public static function statusPrep(){
1267
+ wfConfig::set_ser('wfStatusStartMsgs', array());
1268
+ wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
1269
+ }
1270
+ //In the following functions statusStartMsgs is serialized into the DB so it persists between forks
1271
  public static function statusStart($msg){
1272
+ $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
1273
+ $statusStartMsgs[] = $msg;
1274
+ wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
1275
  self::status(10, 'info', 'SUM_START:' . $msg);
1276
+ return sizeof($statusStartMsgs) - 1;
1277
  }
1278
  public static function statusEnd($idx, $haveIssues){
1279
+ $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
1280
  if($haveIssues){
1281
+ self::status(10, 'info', 'SUM_ENDBAD:' . $statusStartMsgs[$idx]);
1282
  } else {
1283
+ self::status(10, 'info', 'SUM_ENDOK:' . $statusStartMsgs[$idx]);
1284
  }
1285
+ $statusStartMsgs[$idx] = '';
1286
+ wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
1287
  }
1288
  public static function statusEndErr(){
1289
+ $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
1290
+ for($i = 0; $i < sizeof($statusStartMsgs); $i++){
1291
+ if(empty($statusStartMsgs[$i]) === false){
1292
+ self::status(10, 'info', 'SUM_ENDERR:' . $statusStartMsgs[$i]);
1293
+ $statusStartMsgs[$i] = '';
1294
  }
1295
  }
1296
  }
lib/wordfenceConstants.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
- define('WORDFENCE_API_VERSION', 1.5);
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_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
lib/wordfenceHash.php CHANGED
@@ -1,7 +1,10 @@
1
  <?php
2
  require_once('wordfenceClass.php');
3
  class wordfenceHash {
 
 
4
  private $whitespace = array("\n","\r","\t"," ");
 
5
  public $totalData = 0; //To do a sanity check, don't use 'du' because it gets sparse files wrong and reports blocks used on disk. Use : find . -type f -ls | awk '{total += $7} END {print total}'
6
  public $totalFiles = 0;
7
  public $totalDirs = 0;
@@ -9,10 +12,16 @@ class wordfenceHash {
9
  public $linesOfJCH = 0; //lines of HTML, CSS and javascript
10
  public $striplen = 0;
11
  private $hashes = array();
 
 
 
12
  public function __construct($striplen){
13
  $this->striplen = $striplen;
14
  }
15
- public function hashPaths($path, $only = array()){ //base path and 'only' is a list of files and dirs in the bast that are the only ones that should be processed. Everything else in base is ignored. If only is empty then everything is processed.
 
 
 
16
  if($path[strlen($path) - 1] != '/'){
17
  $path .= '/';
18
  }
@@ -29,6 +38,13 @@ class wordfenceHash {
29
  wordfence::status(2, 'info', "Hashing item in base dir: $file");
30
  $this->_dirHash($file);
31
  }
 
 
 
 
 
 
 
32
  return $this->hashes;
33
  }
34
  private function _dirHash($path){
@@ -46,17 +62,20 @@ class wordfenceHash {
46
  if($cont[$i] == '.' || $cont[$i] == '..'){ continue; }
47
  $file = $path . $cont[$i];
48
  if(is_file($file)){
49
- $this->processFile($file);
50
  } else if(is_dir($file)) {
51
  $this->_dirHash($file);
52
  }
53
  }
54
  } else {
55
  if(is_file($path)){
56
- $this->processFile($path);
57
  }
58
  }
59
  }
 
 
 
60
  private function processFile($file){
61
  if(@filesize($file) > WORDFENCE_MAX_FILE_SIZE_TO_PROCESS){
62
  wordfence::status(2, 'info', "Skipping file larger than 50 megs: $file");
1
  <?php
2
  require_once('wordfenceClass.php');
3
  class wordfenceHash {
4
+
5
+ //Begin serialized vars
6
  private $whitespace = array("\n","\r","\t"," ");
7
+ private $fileQ = array();
8
  public $totalData = 0; //To do a sanity check, don't use 'du' because it gets sparse files wrong and reports blocks used on disk. Use : find . -type f -ls | awk '{total += $7} END {print total}'
9
  public $totalFiles = 0;
10
  public $totalDirs = 0;
12
  public $linesOfJCH = 0; //lines of HTML, CSS and javascript
13
  public $striplen = 0;
14
  private $hashes = array();
15
+ public function __sleep(){ //same order as above
16
+ return array('whitespace', 'fileQ', 'totalData', 'totalFiles', 'totalDirs', 'linesOfPHP', 'linesOfJCH', 'striplen', 'hashes');
17
+ }
18
  public function __construct($striplen){
19
  $this->striplen = $striplen;
20
  }
21
+ public function __wakeup(){
22
+
23
+ }
24
+ public function buildFileQueue($path, $only = array()){ //base path and 'only' is a list of files and dirs in the bast that are the only ones that should be processed. Everything else in base is ignored. If only is empty then everything is processed.
25
  if($path[strlen($path) - 1] != '/'){
26
  $path .= '/';
27
  }
38
  wordfence::status(2, 'info', "Hashing item in base dir: $file");
39
  $this->_dirHash($file);
40
  }
41
+
42
+ }
43
+ public function genHashes($forkObj){
44
+ while($file = array_shift($this->fileQ)){
45
+ $this->processFile($file);
46
+ $forkObj->forkIfNeeded();
47
+ }
48
  return $this->hashes;
49
  }
50
  private function _dirHash($path){
62
  if($cont[$i] == '.' || $cont[$i] == '..'){ continue; }
63
  $file = $path . $cont[$i];
64
  if(is_file($file)){
65
+ $this->qFile($file);
66
  } else if(is_dir($file)) {
67
  $this->_dirHash($file);
68
  }
69
  }
70
  } else {
71
  if(is_file($path)){
72
+ $this->qFile($path);
73
  }
74
  }
75
  }
76
+ private function qFile($file){
77
+ $this->fileQ[] = $file;
78
+ }
79
  private function processFile($file){
80
  if(@filesize($file) > WORDFENCE_MAX_FILE_SIZE_TO_PROCESS){
81
  wordfence::status(2, 'info', "Skipping file larger than 50 megs: $file");
lib/wordfenceScanner.php CHANGED
@@ -5,28 +5,30 @@ require_once('wordfenceURLHoover.php');
5
  class wordfenceScanner {
6
  protected $path = '';
7
  protected $fileList = array();
8
- protected $hostFileList = array();
9
- protected $urlFileList = array();
10
  protected $results = array();
11
  public $errorMsg = false;
12
  private $apiKey = false;
13
  private $wordpressVersion = '';
14
- public function __construct($apiKey, $wordpressVersion){
 
 
 
 
 
15
  $this->apiKey = $apiKey;
16
  $this->wordpressVersion = $wordpressVersion;
17
- }
18
- public function scan($path, $fileList){
19
- $this->errorMsg = false;
20
  if($path[strlen($path) - 1] != '/'){
21
  $path .= '/';
22
  }
23
- $this->hostFileList = array();
24
  $this->path = $path;
25
- $this->fileList = $fileList;
26
  $this->results = array();
 
27
  //First extract hosts or IP's and their URL's into $this->hostsFound and URL's into $this->urlsFound
28
- $urlHoover = new wordfenceURLHoover($this->apiKey, $this->wordpressVersion);
29
- foreach($this->fileList as $file){
 
 
30
  if(! file_exists($this->path . $file)){
31
  continue;
32
  }
@@ -107,9 +109,9 @@ class wordfenceScanner {
107
  break;
108
  }
109
 
110
- $urlHoover->hoover($file, $data);
111
  } else {
112
- $urlHoover->hoover($file, $data);
113
  }
114
 
115
  if($totalRead > 2 * 1024 * 1024){
@@ -118,14 +120,15 @@ class wordfenceScanner {
118
  }
119
  fclose($fh);
120
  $mtime = sprintf("%.5f", microtime(true) - $stime);
 
121
  }
122
  if(function_exists('memory_get_usage')){
123
  wordfence::status(3, 'info', "Total memory being used: " . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . "MB");
124
  }
125
  wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
126
- $hooverResults = $urlHoover->getBaddies();
127
- if($urlHoover->errorMsg){
128
- $this->errorMsg = $urlHoover->errorMsg;
129
  return false;
130
  }
131
  foreach($hooverResults as $file => $hresults){
5
  class wordfenceScanner {
6
  protected $path = '';
7
  protected $fileList = array();
 
 
8
  protected $results = array();
9
  public $errorMsg = false;
10
  private $apiKey = false;
11
  private $wordpressVersion = '';
12
+ public function __sleep(){
13
+ return array('path', 'fileList', 'results', 'errorMsg', 'apiKey', 'wordpressVersion', 'urlHoover');
14
+ }
15
+ public function __wakeup(){
16
+ }
17
+ public function __construct($apiKey, $wordpressVersion, $fileList, $path){
18
  $this->apiKey = $apiKey;
19
  $this->wordpressVersion = $wordpressVersion;
20
+ $this->fileList = $fileList;
 
 
21
  if($path[strlen($path) - 1] != '/'){
22
  $path .= '/';
23
  }
 
24
  $this->path = $path;
 
25
  $this->results = array();
26
+ $this->errorMsg = false;
27
  //First extract hosts or IP's and their URL's into $this->hostsFound and URL's into $this->urlsFound
28
+ $this->urlHoover = new wordfenceURLHoover($this->apiKey, $this->wordpressVersion);
29
+ }
30
+ public function scan($forkObj){
31
+ while($file = array_shift($this->fileList)){
32
  if(! file_exists($this->path . $file)){
33
  continue;
34
  }
109
  break;
110
  }
111
 
112
+ $this->urlHoover->hoover($file, $data);
113
  } else {
114
+ $this->urlHoover->hoover($file, $data);
115
  }
116
 
117
  if($totalRead > 2 * 1024 * 1024){
120
  }
121
  fclose($fh);
122
  $mtime = sprintf("%.5f", microtime(true) - $stime);
123
+ $forkObj->forkIfNeeded();
124
  }
125
  if(function_exists('memory_get_usage')){
126
  wordfence::status(3, 'info', "Total memory being used: " . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . "MB");
127
  }
128
  wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
129
+ $hooverResults = $this->urlHoover->getBaddies();
130
+ if($this->urlHoover->errorMsg){
131
+ $this->errorMsg = $this->urlHoover->errorMsg;
132
  return false;
133
  }
134
  foreach($hooverResults as $file => $hresults){
lib/wordfenceURLHoover.php CHANGED
@@ -2,13 +2,24 @@
2
  require_once('wfAPI.php');
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;
@@ -65,9 +76,11 @@ class wordfenceURLHoover {
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);
72
  $hostKey = '';
73
  if(sizeof($hostParts) == 2){
@@ -75,7 +88,7 @@ class wordfenceURLHoover {
75
  } else if(sizeof($hostParts) > 2){
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(){
2
  require_once('wfAPI.php');
3
  class wordfenceURLHoover {
4
  private $debug = false;
 
5
  public $errorMsg = false;
6
+ //private $hostKeyCache = array();
 
7
  private $table = '';
8
+ private $apiKey = false;
9
+ private $wordpressVersion = 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
+ private $api = false;
12
+ private $db = false;
13
+ public function __sleep(){
14
+ return array('debug', 'errorMsg', 'table', 'apiKey', 'wordpressVersion', 'dRegex');
15
+ }
16
+ public function __wakeup(){
17
+ $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
18
+ $this->db = new wfDB();
19
+ }
20
  public function __construct($apiKey, $wordpressVersion){
21
+ $this->apiKey = $apiKey;
22
+ $this->wordpressVersion = $wordpressVersion;
23
  $this->api = new wfAPI($apiKey, $wordpressVersion);
24
  $this->db = new wfDB();
25
  global $wpdb;
76
  return true;
77
  }
78
  private function makeHostKey($host){
79
+ /*
80
  if(isset($this->hostKeyCache[$host])){
81
  return $this->hostKeyCache[$host];
82
  }
83
+ */
84
  $hostParts = explode('.', $host);
85
  $hostKey = '';
86
  if(sizeof($hostParts) == 2){
88
  } else if(sizeof($hostParts) > 2){
89
  $hostKey = substr(hash('sha256', $hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
90
  }
91
+ //$this->hostKeyCache[$host] = $hostKey;
92
  return $hostKey;
93
  }
94
  public function getBaddies(){
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.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
 
@@ -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.0.7 =
156
  * Fixed CSS bug that changed plugins page layout in admin area
157
  * Added memory benchmark utility.
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
 
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.
158
+ * Wordfence scans are now broken into steps so we can scan a huge number of files, posts and comments.
159
+ * Alert emails now include IP address, hostname lookup and geographic location (city if available).
160
+ * Improved scan locking. No longer time based but uses flock() if on unix or time on Windows.
161
+ * Suppressed warnings that WF was generating.
162
+ * Improve handling of non-standard wp-content directories.
163
+ * Fix restored files were still showing as changed if they contained international characters.
164
+ * Improve permission denied message if attempting to repair a file.
165
+ * Fixed problem that caused scans to not start because some hosts take too long to look up their own name.
166
+ * Fixed issue with Wordfence menu that caused it to not appear or conflict with other menus under certain conditions.
167
+ * Upgraded to API version 1.6
168
+ * Improved geo lookup code for IP's.
169
+ * Fixed debug mode output in live status box - coloring was wrong.
170
+ * Added ajax status message to WF admin pages.
171
+ * Fixed colorbox popup so that it doesn't jump around on refresh.
172
+
173
  = 2.0.7 =
174
  * Fixed CSS bug that changed plugins page layout in admin area
175
  * Added memory benchmark utility.
wfscan.php CHANGED
@@ -68,11 +68,9 @@ class wfScan {
68
  self::becomeAdmin();
69
 
70
  wordfence::status(4, 'info', "Checking if scan is already running");
71
- $scanRunning = wfConfig::get('wf_scanRunning');
72
- if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
73
  self::errorExit("There is already a scan running.");
74
  }
75
- wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
76
  wordfence::status(4, 'info', "Requesting max memory");
77
  wfUtils::requestMaxMemory();
78
  wordfence::status(4, 'info', "Setting up error handling environment");
@@ -84,11 +82,23 @@ class wfScan {
84
  @error_reporting(E_ALL);
85
  @ini_set('display_errors','On');
86
  wordfence::status(4, 'info', "Setting up scanRunning and starting scan");
87
- wfConfig::set('wf_scanRunning', time());
88
- $scan = new wfScanEngine();
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  $scan->go();
90
-
91
- wfConfig::set('wf_scanRunning', '');
92
  }
93
  public static function obHandler($buf){
94
  if(strlen($buf) > 1000){
@@ -102,7 +112,7 @@ class wfScan {
102
  wordfence::status(1, 'error', "$errstr ($errno) File: $errfile Line: $errline");
103
  }
104
  public static function shutdown(){
105
- wfConfig::set('wf_scanRunning', '');
106
  }
107
  private static function errorExit($msg){
108
  echo json_encode(array('errorMsg' => $msg));
68
  self::becomeAdmin();
69
 
70
  wordfence::status(4, 'info', "Checking if scan is already running");
71
+ if(! wfUtils::getScanLock()){
 
72
  self::errorExit("There is already a scan running.");
73
  }
 
74
  wordfence::status(4, 'info', "Requesting max memory");
75
  wfUtils::requestMaxMemory();
76
  wordfence::status(4, 'info', "Setting up error handling environment");
82
  @error_reporting(E_ALL);
83
  @ini_set('display_errors','On');
84
  wordfence::status(4, 'info', "Setting up scanRunning and starting scan");
85
+ $isFork = ($_GET['isFork'] == '1' ? true : false);
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
97
+ $scan = new wfScanEngine();
98
+ }
99
+ }
100
  $scan->go();
101
+ wfUtils::clearScanLock();
 
102
  }
103
  public static function obHandler($buf){
104
  if(strlen($buf) > 1000){
112
  wordfence::status(1, 'error', "$errstr ($errno) File: $errfile Line: $errline");
113
  }
114
  public static function shutdown(){
115
+ wfUtils::clearScanLock();
116
  }
117
  private static function errorExit($msg){
118
  echo json_encode(array('errorMsg' => $msg));
wordfence.php CHANGED
@@ -4,11 +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.0.7
8
  Author URI: http://wordfence.com/
9
  */
10
- define('WORDFENCE_VERSION', '2.0.7');
11
-
12
 
13
  require_once('lib/wordfenceConstants.php');
14
  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.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');