Wordfence Security – Firewall & Malware Scan - Version 3.0.2

Version Description

  • Overall this release is a very important upgrade. It drastically reduces memory usage on systems with large files from hundreds of megs to around 8 megs max memory used per scan.
  • Moved queue of files that get processed to a new DB table to save memory.
  • Reduced max size of tables before we truncate to avoid long DB queries.
  • Reduced max size of wfStatus table from 100,000 rows to 1,000 rows.
  • Introduced feature to kill hung or crashed scans reliably.
  • Made scan locking much more reliable to avoid multiple concurrent scans hogging resources.
  • Debug status messages are no longer written to the DB in non-debug mode.
  • Modified the list of unknown files we receive back from the WF scanning servers to be a packed string rather than an array which is more memory efficient.
  • Added summary at the end of scans to show the peak memory that Wordfence used along with server peak memory.
  • Hashes are now progressively sent to Wordfence servers during scan to drastically reduce memory usage.
  • Upgraded to Wordfence server API version 1.8
  • List of hosts that Wordfence URL scanner compiles now uses wfArray which is a very memory efficient packed binary structure.
  • Writes that WF URL scanner makes to the DB are now batched into bulk inserts to reduce load on DB.
  • Fixed bug in wfscan.php (scanning script) that could have caused scans to loop or pick up old data.
  • Massively reduced the number of status messages we log, but kept very verbose logging for debug mode with a warning about DB load.
  • Added summary messages instead of individual file scanning status messages which show files scanned and scan rate.
  • Removed bin2hex and hex2bin conversions for scanning data which were slow, memory heavy and unneeded.
  • Wordfence database class will now reuse the WordPress database handle from $wpdb if it can to reduce DB connections.
Download this release

Release Info

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

Code changes from version 2.1.5 to 3.0.2

js/admin.js CHANGED
@@ -193,6 +193,9 @@ window['wordfenceAdmin'] = {
193
  } else if(item.msg.indexOf('SUM_PREP:') != -1){
194
  var msg = item.msg.replace('SUM_PREP:', '');
195
  jQuery('#consoleSummary').empty().html('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult" id="wfStartingScan"><div class="wfSummaryLoading"></div></div><div class="wfClear"></div>');
 
 
 
196
  }
197
  },
198
  processActQueueItem: function(){
@@ -343,6 +346,16 @@ window['wordfenceAdmin'] = {
343
 
344
  });
345
  },
 
 
 
 
 
 
 
 
 
 
346
  startScan: function(){
347
  var scanReqAnimation = setInterval(function(){
348
  var str = jQuery('#wfStartScanButton1').prop('value');
193
  } else if(item.msg.indexOf('SUM_PREP:') != -1){
194
  var msg = item.msg.replace('SUM_PREP:', '');
195
  jQuery('#consoleSummary').empty().html('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult" id="wfStartingScan"><div class="wfSummaryLoading"></div></div><div class="wfClear"></div>');
196
+ } else if(item.msg.indexOf('SUM_KILLED:') != -1){
197
+ var msg = item.msg.replace('SUM_KILLED:', '');
198
+ jQuery('#consoleSummary').empty().html('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult wfSummaryOK">Scan Complete.</div><div class="wfClear"></div>');
199
  }
200
  },
201
  processActQueueItem: function(){
346
 
347
  });
348
  },
349
+ killScan: function(){
350
+ var self = this;
351
+ this.ajax('wordfence_killScan', {}, function(res){
352
+ if(res.ok){
353
+ self.colorbox('400px', "Kill requested", "A termination request has been sent to any running scans.");
354
+ } else {
355
+ self.colorbox('400px', "Kill failed", "We failed to send a termination request.");
356
+ }
357
+ });
358
+ },
359
  startScan: function(){
360
  var scanReqAnimation = setInterval(function(){
361
  var str = jQuery('#wfStartScanButton1').prop('value');
lib/menu_options.php CHANGED
@@ -206,7 +206,8 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
206
  <tr><th>Check password strength on profile update</th><td><input type="checkbox" id="other_pwStrengthOnUpdate" class="wfConfigElem" name="other_pwStrengthOnUpdate" value="1" <?php $w->cb('other_pwStrengthOnUpdate'); ?> /></td></tr>
207
  <tr><th>Participate in the Wordfence Security Network</th><td><input type="checkbox" id="other_WFNet" class="wfConfigElem" name="other_WFNet" value="1" <?php $w->cb('other_WFNet'); ?> /></td></tr>
208
  <tr><th>Maximum memory Wordfence can use</th><td><input type="text" id="maxMem" name="maxMem" value="<?php $w->f('maxMem'); ?>" size="4" />Megabytes</td></tr>
209
- <tr><th>Enable debugging mode</th><td><input type="checkbox" id="debugOn" class="wfConfigElem" name="debugOn" value="1" <?php $w->cb('debugOn'); ?> /></td></tr>
 
210
  <tr><th colspan="2"><a href="/?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Click to view your system's configuration in a new window</a></th></tr>
211
  <tr><th colspan="2"><a href="/?_wfsf=testmem&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Test your WordPress host's available memory</a></th></tr>
212
  </table>
206
  <tr><th>Check password strength on profile update</th><td><input type="checkbox" id="other_pwStrengthOnUpdate" class="wfConfigElem" name="other_pwStrengthOnUpdate" value="1" <?php $w->cb('other_pwStrengthOnUpdate'); ?> /></td></tr>
207
  <tr><th>Participate in the Wordfence Security Network</th><td><input type="checkbox" id="other_WFNet" class="wfConfigElem" name="other_WFNet" value="1" <?php $w->cb('other_WFNet'); ?> /></td></tr>
208
  <tr><th>Maximum memory Wordfence can use</th><td><input type="text" id="maxMem" name="maxMem" value="<?php $w->f('maxMem'); ?>" size="4" />Megabytes</td></tr>
209
+ <tr><th>Enable debugging mode (increases database load)</th><td><input type="checkbox" id="debugOn" class="wfConfigElem" name="debugOn" value="1" <?php $w->cb('debugOn'); ?> /></td></tr>
210
+ <tr><th>Delete Wordfence tables and data on deactivation?</th><td><input type="checkbox" id="deleteTablesOnDeact" class="wfConfigElem" name="deleteTablesOnDeact" value="1" <?php $w->cb('deleteTablesOnDeact'); ?> /></td></tr>
211
  <tr><th colspan="2"><a href="/?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Click to view your system's configuration in a new window</a></th></tr>
212
  <tr><th colspan="2"><a href="/?_wfsf=testmem&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>" target="_blank">Test your WordPress host's available memory</a></th></tr>
213
  </table>
lib/menu_scan.php CHANGED
@@ -4,6 +4,8 @@
4
  <div class="wordfenceWrap">
5
  <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" id="wfStartScanButton1" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();" />
6
  <a target="_blank" href="http://www.wordfence.com/forums/">You can always get help on our support forum.</a>
 
 
7
  </div>
8
  <div>
9
  <div class="consoleHead">
4
  <div class="wordfenceWrap">
5
  <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" id="wfStartScanButton1" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();" />
6
  <a target="_blank" href="http://www.wordfence.com/forums/">You can always get help on our support forum.</a>
7
+ <br />
8
+ &nbsp;&nbsp;&nbsp;&nbsp;<a href="#" onclick="WFAD.killScan(); return false;" style="font-size: 10px; color: #AAA;">Click here to kill a running scan.</a>
9
  </div>
10
  <div>
11
  <div class="consoleHead">
lib/wfArray.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class wfArray {
3
+ private $data = "";
4
+ private $size = 0;
5
+ private $shiftPtr = 0;
6
+ public function __construct($keys){
7
+ $this->keys = $keys;
8
+ }
9
+ public function push($val){ //associative array with keys that match those given to constructor
10
+ foreach($this->keys as $key){
11
+ $this->data .= pack('N', strlen($val[$key])) . $val[$key];
12
+ }
13
+ $this->size++;
14
+ }
15
+ public function shift(){ //If you alternately call push and shift you must periodically call collectGarbage() or ->data will keep growing
16
+ $arr = array();
17
+ if(strlen($this->data) < 1){ return null; }
18
+ if($this->shiftPtr == strlen($this->data)){ return null; }
19
+ foreach($this->keys as $key){
20
+ $len = unpack('N', substr($this->data, $this->shiftPtr, 4));
21
+ $len = $len[1];
22
+ $arr[$key] = substr($this->data, $this->shiftPtr + 4, $len);
23
+ $this->shiftPtr += 4 + $len;
24
+ }
25
+ if($this->shiftPtr == strlen($this->data)){ //garbage collection
26
+ $this->data = ""; //we don't shorten with substr() because the assignment doubles peak mem
27
+ $this->shiftPtr = 0;
28
+ }
29
+ $this->size--;
30
+ return $arr;
31
+ }
32
+ public function collectGarbage(){ //only call collectGarbage if you're alternating between pushes and shifts and never emptying the array.
33
+ //If you don't collect garbage then the data that is shifted is never freed
34
+ $this->data = substr($this->data, $this->shiftPtr); //at this point memory usage doubles because of the = assignment (string copy is made), so try not to call collect garbage unless you have to.
35
+ $this->shiftPtr = 0;
36
+ }
37
+ public function zero(){ //Rather call this instead of collect garbage because it's way more mem efficient.
38
+ $this->data = "";
39
+ $this->shiftPtr = 0;
40
+ $this->size = 0;
41
+ }
42
+ public function size(){
43
+ return $this->size;
44
+ }
45
+ }
46
+ ?>
lib/wfConfig.php CHANGED
@@ -42,6 +42,7 @@ class wfConfig {
42
  "other_pwStrengthOnUpdate" => false,
43
  "other_WFNet" => true,
44
  "other_scanOutside" => false,
 
45
  "debugOn" => false
46
  ),
47
  "otherParams" => array(
@@ -104,6 +105,7 @@ class wfConfig {
104
  "other_pwStrengthOnUpdate" => true,
105
  "other_WFNet" => true,
106
  "other_scanOutside" => false,
 
107
  "debugOn" => false
108
  ),
109
  "otherParams" => array(
@@ -166,6 +168,7 @@ class wfConfig {
166
  "other_pwStrengthOnUpdate" => true,
167
  "other_WFNet" => true,
168
  "other_scanOutside" => false,
 
169
  "debugOn" => false
170
  ),
171
  "otherParams" => array(
@@ -228,6 +231,7 @@ class wfConfig {
228
  "other_pwStrengthOnUpdate" => true,
229
  "other_WFNet" => true,
230
  "other_scanOutside" => false,
 
231
  "debugOn" => false
232
  ),
233
  "otherParams" => array(
@@ -290,6 +294,7 @@ class wfConfig {
290
  "other_pwStrengthOnUpdate" => true,
291
  "other_WFNet" => true,
292
  "other_scanOutside" => false,
 
293
  "debugOn" => false
294
  ),
295
  "otherParams" => array(
42
  "other_pwStrengthOnUpdate" => false,
43
  "other_WFNet" => true,
44
  "other_scanOutside" => false,
45
+ "deleteTablesOnDeact" => false,
46
  "debugOn" => false
47
  ),
48
  "otherParams" => array(
105
  "other_pwStrengthOnUpdate" => true,
106
  "other_WFNet" => true,
107
  "other_scanOutside" => false,
108
+ "deleteTablesOnDeact" => false,
109
  "debugOn" => false
110
  ),
111
  "otherParams" => array(
168
  "other_pwStrengthOnUpdate" => true,
169
  "other_WFNet" => true,
170
  "other_scanOutside" => false,
171
+ "deleteTablesOnDeact" => false,
172
  "debugOn" => false
173
  ),
174
  "otherParams" => array(
231
  "other_pwStrengthOnUpdate" => true,
232
  "other_WFNet" => true,
233
  "other_scanOutside" => false,
234
+ "deleteTablesOnDeact" => false,
235
  "debugOn" => false
236
  ),
237
  "otherParams" => array(
294
  "other_pwStrengthOnUpdate" => true,
295
  "other_WFNet" => true,
296
  "other_scanOutside" => false,
297
+ "deleteTablesOnDeact" => false,
298
  "debugOn" => false
299
  ),
300
  "otherParams" => array(
lib/wfDB.php CHANGED
@@ -54,18 +54,22 @@ class wfDB {
54
  if(isset(self::$dbhCache[$handleKey])){
55
  $this->dbh = self::$dbhCache[$handleKey];
56
  } else {
57
- $dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
58
- if($dbh === false){
59
- self::criticalError("Wordfence could not connect to your database. The error was: " . mysql_error());
60
- return;
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
-
63
- mysql_select_db($this->dbname, $dbh);
64
- self::$dbhCache[$handleKey] = $dbh;
65
- $this->dbh = self::$dbhCache[$handleKey];
66
- $this->query("SET NAMES 'utf8'");
67
-
68
- //Set big packets for set_ser when it serializes a scan in between forks
69
  $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
70
  }
71
  }
@@ -194,6 +198,10 @@ class wfDB {
194
  $rec = $this->querySingleRec("show variables like 'max_allowed_packet'");
195
  return $rec['Value'];
196
  }
 
 
 
 
197
  }
198
 
199
  ?>
54
  if(isset(self::$dbhCache[$handleKey])){
55
  $this->dbh = self::$dbhCache[$handleKey];
56
  } else {
57
+ global $wpdb;
58
+ if(isset($wpdb) && isset($wpdb->dbh) && is_resource($wpdb->dbh)){
59
+ $dbh = $wpdb->dbh;
60
+ self::$dbhCache[$handleKey] = $dbh;
61
+ $this->dbh = self::$dbhCache[$handleKey];
62
+ } else {
63
+ $dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, true );
64
+ if($dbh === false){
65
+ self::criticalError("Wordfence could not connect to your database. The error was: " . mysql_error());
66
+ return;
67
+ }
68
+ mysql_select_db($this->dbname, $dbh);
69
+ self::$dbhCache[$handleKey] = $dbh;
70
+ $this->dbh = self::$dbhCache[$handleKey];
71
+ $this->query("SET NAMES 'utf8'");
72
  }
 
 
 
 
 
 
 
73
  $this->queryIgnoreError("SET GLOBAL max_allowed_packet=256*1024*1024");
74
  }
75
  }
198
  $rec = $this->querySingleRec("show variables like 'max_allowed_packet'");
199
  return $rec['Value'];
200
  }
201
+ public function prefix(){
202
+ global $wpdb;
203
+ return $wpdb->base_prefix;
204
+ }
205
  }
206
 
207
  ?>
lib/wfScanEngine.php CHANGED
@@ -25,7 +25,7 @@ class wfScanEngine {
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();
@@ -69,7 +69,9 @@ class wfScanEngine {
69
  }
70
  public function go(){
71
  try {
 
72
  $this->doScan();
 
73
  if(! $this->errorStopped){
74
  wfConfig::set('lastScanCompleted', 'ok');
75
  }
@@ -83,14 +85,14 @@ class wfScanEngine {
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
  if(wfConfig::set_ser('wfsd_engine', $this, true)){
93
- wfUtils::clearScanLock();
94
  self::startScan(true);
95
  } //Otherwise there was an error so don't start another scan.
96
  exit(0);
@@ -100,15 +102,18 @@ class wfScanEngine {
100
  }
101
  private function doScan(){
102
  while(sizeof($this->jobList) > 0){
 
103
  $jobName = $this->jobList[0];
104
  call_user_func(array($this, 'scan_' . $jobName));
105
  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
106
  if($this->errorStopped){
107
  return;
108
  }
 
109
  $this->fork();
110
  }
111
  $summary = $this->i->getSummaryItems();
 
112
  $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.");
113
  if($this->i->totalIssues > 0){
114
  $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
@@ -165,7 +170,6 @@ class wfScanEngine {
165
  $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.");
166
  return array();
167
  }
168
-
169
  //CORE SCAN
170
  $this->hasher = new wordfenceHash(strlen(ABSPATH));
171
  $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');
@@ -186,7 +190,7 @@ class wfScanEngine {
186
  $this->hasher->buildFileQueue(ABSPATH, $includeInScan);
187
  }
188
  private function scan_knownFiles_main(){
189
- $this->hashes = $this->hasher->genHashes($this);
190
  }
191
  private function scan_knownFiles_finish(){
192
  $this->status(2, 'info', "Done hash. Updating summary items.");
@@ -226,6 +230,7 @@ class wfScanEngine {
226
  }
227
  }
228
  $this->status(2, 'info', "Sending request to Wordfence servers to do main scan.");
 
229
  $scanData = array(
230
  'pluginScanEnabled' => $this->pluginScanEnabled,
231
  'themeScanEnabled' => $this->themeScanEnabled,
@@ -233,26 +238,31 @@ class wfScanEngine {
233
  'malwareScanEnabled' => $this->malwareScanEnabled,
234
  'plugins' => $plugins,
235
  'themes' => $themes,
236
- 'hashes' => wordfenceHash::bin2hex($this->hashes)
237
  );
238
- $result1 = $this->api->call('main_scan', array(), array(
239
- 'data' => json_encode($scanData)
240
- ));
241
  if($this->api->errorMsg){
242
  $this->errorStop($this->api->errorMsg);
243
  wordfence::statusEndErr();
244
  return;
245
  }
246
- if(empty($result1['errorMsg']) === false){
247
- $this->errorStop($result['errorMsg']);
248
  wordfence::statusEndErr();
249
  return;
250
  }
251
- if(! $result1){
252
- $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
 
 
 
 
 
253
  wordfence::statusEndErr();
254
  return;
255
  }
 
256
  $this->status(2, 'info', "Processing scan results");
257
  $haveIssues = array(
258
  'core' => false,
@@ -260,7 +270,7 @@ class wfScanEngine {
260
  'theme' => false,
261
  'unknown' => false
262
  );
263
- foreach($result1['results'] as $issue){
264
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
265
  if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
266
  $haveIssues[$issue['data']['cType']] = true;
@@ -272,14 +282,10 @@ class wfScanEngine {
272
  }
273
  }
274
 
275
- $this->unknownFiles = $result1['unknownFiles'];
276
  }
277
  private function scan_fileContents_init(){
278
  $this->statusIDX['infect'] = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
279
  $this->statusIDX['GSB'] = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
280
- if(! is_array($this->unknownFiles)){
281
- $this->unknownFiles = array();
282
- }
283
  $this->scanner = new wordfenceScanner($this->apiKey, $this->wp_version, $this->unknownFiles, ABSPATH);
284
  $this->unknownFiles = false;
285
  $this->status(2, 'info', "Starting scan of file contents");
@@ -822,10 +828,25 @@ class wfScanEngine {
822
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
823
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
824
  }
 
 
 
 
 
 
 
 
 
 
 
 
825
  public static function startScan($isFork = false){
826
- wordfence::status(4, 'info', "Entering start scan routine");
827
- if(wfUtils::isScanRunning()){
828
- return "A scan is already running.";
 
 
 
829
  }
830
 
831
  $cron_url = plugins_url('wordfence/wfscan.php?isFork=' . ($isFork ? '1' : '0'));
25
  private $pluginScanEnabled = false;
26
  private $coreScanEnabled = false;
27
  private $themeScanEnabled = false;
28
+ private $unknownFiles = "";
29
  private $fileContentsResults = false;
30
  private $scanner = false;
31
  private $scanQueue = array();
69
  }
70
  public function go(){
71
  try {
72
+ self::checkForKill();
73
  $this->doScan();
74
+ self::checkForKill();
75
  if(! $this->errorStopped){
76
  wfConfig::set('lastScanCompleted', 'ok');
77
  }
85
  wordfence::scheduleNextScan(true);
86
  }
87
  public function forkIfNeeded(){
88
+ self::checkForKill();
89
  if(time() - $this->cycleStartTime > $this->maxExecTime){
90
+ wordfence::status(4, 'info', "Forking during hash scan to ensure continuity.");
91
  $this->fork();
92
  }
93
  }
94
  public function fork(){
95
  if(wfConfig::set_ser('wfsd_engine', $this, true)){
 
96
  self::startScan(true);
97
  } //Otherwise there was an error so don't start another scan.
98
  exit(0);
102
  }
103
  private function doScan(){
104
  while(sizeof($this->jobList) > 0){
105
+ self::checkForKill();
106
  $jobName = $this->jobList[0];
107
  call_user_func(array($this, 'scan_' . $jobName));
108
  array_shift($this->jobList); //only shift once we're done because we may pause halfway through a job and need to pick up where we left off
109
  if($this->errorStopped){
110
  return;
111
  }
112
+ self::checkForKill();
113
  $this->fork();
114
  }
115
  $summary = $this->i->getSummaryItems();
116
+ $this->status(1, 'info', '-------------------');
117
  $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.");
118
  if($this->i->totalIssues > 0){
119
  $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
170
  $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.");
171
  return array();
172
  }
 
173
  //CORE SCAN
174
  $this->hasher = new wordfenceHash(strlen(ABSPATH));
175
  $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');
190
  $this->hasher->buildFileQueue(ABSPATH, $includeInScan);
191
  }
192
  private function scan_knownFiles_main(){
193
+ $this->hasher->genHashes($this);
194
  }
195
  private function scan_knownFiles_finish(){
196
  $this->status(2, 'info', "Done hash. Updating summary items.");
230
  }
231
  }
232
  $this->status(2, 'info', "Sending request to Wordfence servers to do main scan.");
233
+
234
  $scanData = array(
235
  'pluginScanEnabled' => $this->pluginScanEnabled,
236
  'themeScanEnabled' => $this->themeScanEnabled,
238
  'malwareScanEnabled' => $this->malwareScanEnabled,
239
  'plugins' => $plugins,
240
  'themes' => $themes,
241
+ 'hashStorageID' => $this->hasher->getHashStorageID()
242
  );
243
+ $content = json_encode($scanData);
244
+ $dataArr = $this->api->binCall('main_scan', $content);
 
245
  if($this->api->errorMsg){
246
  $this->errorStop($this->api->errorMsg);
247
  wordfence::statusEndErr();
248
  return;
249
  }
250
+ if(! is_array($dataArr)){
251
+ $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
252
  wordfence::statusEndErr();
253
  return;
254
  }
255
+ //Data is an encoded string of <4 bytes of total length including these 4 bytes><2 bytes of filename length><filename>
256
+ $totalUStrLen = unpack('N', substr($dataArr['data'], 0, 4));
257
+ $totalUStrLen = $totalUStrLen[1];
258
+ $this->unknownFiles = substr($dataArr['data'], 4, ($totalUStrLen - 4)); //subtruct the first 4 bytes which is an INT that is the total length of unknown string including the 4 bytes
259
+ $resultArr = json_decode(substr($dataArr['data'], $totalUStrLen), true);
260
+ if(! (is_array($resultArr) && isset($resultArr['results'])) ){
261
+ $this->errorStop("We received an incorrect response from the Wordfence server when scanning core, plugin and theme files.");
262
  wordfence::statusEndErr();
263
  return;
264
  }
265
+
266
  $this->status(2, 'info', "Processing scan results");
267
  $haveIssues = array(
268
  'core' => false,
270
  'theme' => false,
271
  'unknown' => false
272
  );
273
+ foreach($resultArr['results'] as $issue){
274
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
275
  if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
276
  $haveIssues[$issue['data']['cType']] = true;
282
  }
283
  }
284
 
 
285
  }
286
  private function scan_fileContents_init(){
287
  $this->statusIDX['infect'] = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
288
  $this->statusIDX['GSB'] = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
 
 
 
289
  $this->scanner = new wordfenceScanner($this->apiKey, $this->wp_version, $this->unknownFiles, ABSPATH);
290
  $this->unknownFiles = false;
291
  $this->status(2, 'info', "Starting scan of file contents");
828
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
829
  return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
830
  }
831
+ public static function requestKill(){
832
+ wfConfig::set('wfKillRequested', time());
833
+ }
834
+ public static function checkForKill(){
835
+ $kill = wfConfig::get('wfKillRequested', 0);
836
+ if($kill && time() - $kill < 600){ //Kill lasts for 10 minutes
837
+ $wfdb = new wfDB();
838
+ wordfence::status(2, 'info', "Killing current scan");
839
+ wordfence::status(10, 'info', "SUM_KILLED:Previous scan was killed successfully.");
840
+ exit(0);
841
+ }
842
+ }
843
  public static function startScan($isFork = false){
844
+ if(! $isFork){ //beginning of scan
845
+ wfConfig::set('wfKillRequested', 0);
846
+ wordfence::status(4, 'info', "Entering start scan routine");
847
+ if(wfUtils::isScanRunning()){
848
+ return "A scan is already running. Use the kill link if you would like to terminate the current scan.";
849
+ }
850
  }
851
 
852
  $cron_url = plugins_url('wordfence/wfscan.php?isFork=' . ($isFork ? '1' : '0'));
lib/wfSchema.php CHANGED
@@ -143,6 +143,10 @@ class wfSchema {
143
  path text,
144
  hostKey binary(4),
145
  KEY k2(hostKey)
 
 
 
 
146
  ) default charset=utf8"
147
  );
148
  private $db = false;
143
  path text,
144
  hostKey binary(4),
145
  KEY k2(hostKey)
146
+ ) default charset=utf8",
147
+ 'wfFileQueue' => "(
148
+ id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
149
+ filename text
150
  ) default charset=utf8"
151
  );
152
  private $db = false;
lib/wfUtils.php CHANGED
@@ -158,6 +158,13 @@ class wfUtils {
158
  $db = new wfDB();
159
  return $db->querySingle("select AES_DECRYPT(UNHEX('%s'), '%s') as val", $str, $key);
160
  }
 
 
 
 
 
 
 
161
  public static function logCaller(){
162
  $trace=debug_backtrace();
163
  $caller=array_shift($trace);
@@ -237,41 +244,24 @@ class wfUtils {
237
  return self::$isWindows == 'yes' ? true : false;
238
  }
239
  public static function getScanLock(){
240
- if(self::isWindows()){
241
- //Windows does not support non-blocking flock, so we use time.
242
- $scanRunning = wfConfig::get('wf_scanRunning');
243
- if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
244
- return false;
245
- }
246
- wfConfig::set('wf_scanRunning', time());
247
- return true;
248
- } else {
249
- self::$scanLockFH = fopen(__FILE__, 'r');
250
- if(flock(self::$scanLockFH, LOCK_EX | LOCK_NB)){
251
- return true;
252
- } else {
253
- return false;
254
- }
255
  }
 
 
256
  }
257
  public static function clearScanLock(){
258
- if(self::isWindows()){
259
- wfConfig::set('wf_scanRunning', '');
260
- } else {
261
- if(self::$scanLockFH){
262
- @fclose(self::$scanLockFH);
263
- self::$scanLockFH = false;
264
- }
265
- }
266
-
267
  }
268
  public static function isScanRunning(){
269
- $scanRunning = true;
270
- if(self::getScanLock()){
271
- $scanRunning = false;
 
 
272
  }
273
- self::clearScanLock();
274
- return $scanRunning;
275
  }
276
  public static function getIPGeo($IP){ //Works with int or dotted
277
 
158
  $db = new wfDB();
159
  return $db->querySingle("select AES_DECRYPT(UNHEX('%s'), '%s') as val", $str, $key);
160
  }
161
+ public static function lcmem(){
162
+ $trace=debug_backtrace();
163
+ $caller=array_shift($trace);
164
+ $c2 = array_shift($trace);
165
+ $mem = memory_get_usage(true);
166
+ error_log("$mem at " . $caller['file'] . " line " . $caller['line']);
167
+ }
168
  public static function logCaller(){
169
  $trace=debug_backtrace();
170
  $caller=array_shift($trace);
244
  return self::$isWindows == 'yes' ? true : false;
245
  }
246
  public static function getScanLock(){
247
+ //Windows does not support non-blocking flock, so we use time.
248
+ $scanRunning = wfConfig::get('wf_scanRunning');
249
+ if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
250
+ return false;
 
 
 
 
 
 
 
 
 
 
 
251
  }
252
+ wfConfig::set('wf_scanRunning', time());
253
+ return true;
254
  }
255
  public static function clearScanLock(){
256
+ wfConfig::set('wf_scanRunning', '');
 
 
 
 
 
 
 
 
257
  }
258
  public static function isScanRunning(){
259
+ $scanRunning = wfConfig::get('wf_scanRunning');
260
+ if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
261
+ return true;
262
+ } else {
263
+ return false;
264
  }
 
 
265
  }
266
  public static function getIPGeo($IP){ //Works with int or dotted
267
 
lib/wordfenceClass.php CHANGED
@@ -23,6 +23,7 @@ class wordfence {
23
  private static $wfLog = false;
24
  private static $hitID = 0;
25
  private static $statusStartMsgs = array();
 
26
  public static function installPlugin(){
27
  self::runInstall();
28
  //Used by MU code below
@@ -34,10 +35,12 @@ class wordfence {
34
  wp_clear_scheduled_hook('wordfence_daily_cron');
35
  wp_clear_scheduled_hook('wordfence_hourly_cron');
36
  wp_clear_scheduled_hook('wordfence_scheduled_scan');
37
- $schema = new wfSchema();
38
- $schema->dropAll();
39
- foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
40
- delete_option($opt);
 
 
41
  }
42
  }
43
  public static function hourlyCron(){
@@ -126,21 +129,23 @@ class wordfence {
126
  }
127
  $wfdb->query("delete from $p"."wfLockedOut where blockedTime + %s < unix_timestamp()", wfConfig::get('loginSec_lockoutMins') * 60);
128
  $count2 = $wfdb->querySingle("select count(*) as cnt from $p"."wfLogins");
129
- if($count2 > 100000){
130
  $wfdb->query("truncate table $p"."wfLogins"); //in case of Dos
131
  } else if($count2 > $maxRows){
132
  $wfdb->query("delete from $p"."wfLogins order by ctime asc limit %d", ($count2 - $maxRows));
133
  }
134
  $wfdb->query("delete from $p"."wfReverseCache where unix_timestamp() - lastUpdate > 86400");
135
  $count3 = $wfdb->querySingle("select count(*) as cnt from $p"."wfThrottleLog");
136
- if($count3 > 100000){
137
  $wfdb->query("truncate table $p"."wfThrottleLog"); //in case of DoS
138
  } else if($count3 > $maxRows){
139
  $wfdb->query("delete from $p"."wfThrottleLog order by endTime asc limit %d", ($count3 - $maxRows));
140
  }
141
  $count4 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus");
142
- if($count4 > 100000){ //max status events we keep. This determines how much gets emailed to us when users sends us a debug report.
143
- $wfdb->query("delete from $p"."wfStatus where level != 10 order by ctime asc limit %d", ($count4 - 100000));
 
 
144
  $count5 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus where level=10");
145
  if($count5 > 100){
146
  $wfdb->query("delete from $p"."wfStatus where level = 10 order by ctime asc limit %d", ($count5 - 100) );
@@ -731,6 +736,15 @@ class wordfence {
731
  $wfIssues->updateIssue($issueID, $status);
732
  return array('ok' => 1);
733
  }
 
 
 
 
 
 
 
 
 
734
  public static function ajax_loadIssues_callback(){
735
  $i = new wfIssues();
736
  $iss = $i->getIssues();
@@ -1098,7 +1112,7 @@ class wordfence {
1098
  }
1099
  public static function admin_init(){
1100
  if(! wfUtils::isAdmin()){ return; }
1101
- foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'permBlockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){
1102
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1103
  }
1104
 
@@ -1166,6 +1180,9 @@ class wordfence {
1166
  require 'menu_scan.php';
1167
  }
1168
  public static function status($level /* 1 has highest visibility */, $type /* info|error */, $msg){
 
 
 
1169
  if($type != 'info' && $type != 'error'){ error_log("Invalid status type: $type"); return; }
1170
  if(self::$printStatus){
1171
  echo "STATUS: $level : $type : $msg\n";
@@ -1317,5 +1334,15 @@ class wordfence {
1317
  $exists = $db->querySingle("show tables like '$prefix"."wfConfig'");
1318
  return $exists ? true : false;
1319
  }
 
 
 
 
 
 
 
 
 
 
1320
  }
1321
  ?>
23
  private static $wfLog = false;
24
  private static $hitID = 0;
25
  private static $statusStartMsgs = array();
26
+ private static $debugOn = null;
27
  public static function installPlugin(){
28
  self::runInstall();
29
  //Used by MU code below
35
  wp_clear_scheduled_hook('wordfence_daily_cron');
36
  wp_clear_scheduled_hook('wordfence_hourly_cron');
37
  wp_clear_scheduled_hook('wordfence_scheduled_scan');
38
+ if(wfConfig::get('deleteTablesOnDeact')){
39
+ $schema = new wfSchema();
40
+ $schema->dropAll();
41
+ foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
42
+ delete_option($opt);
43
+ }
44
  }
45
  }
46
  public static function hourlyCron(){
129
  }
130
  $wfdb->query("delete from $p"."wfLockedOut where blockedTime + %s < unix_timestamp()", wfConfig::get('loginSec_lockoutMins') * 60);
131
  $count2 = $wfdb->querySingle("select count(*) as cnt from $p"."wfLogins");
132
+ if($count2 > 20000){
133
  $wfdb->query("truncate table $p"."wfLogins"); //in case of Dos
134
  } else if($count2 > $maxRows){
135
  $wfdb->query("delete from $p"."wfLogins order by ctime asc limit %d", ($count2 - $maxRows));
136
  }
137
  $wfdb->query("delete from $p"."wfReverseCache where unix_timestamp() - lastUpdate > 86400");
138
  $count3 = $wfdb->querySingle("select count(*) as cnt from $p"."wfThrottleLog");
139
+ if($count3 > 20000){
140
  $wfdb->query("truncate table $p"."wfThrottleLog"); //in case of DoS
141
  } else if($count3 > $maxRows){
142
  $wfdb->query("delete from $p"."wfThrottleLog order by endTime asc limit %d", ($count3 - $maxRows));
143
  }
144
  $count4 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus");
145
+ if($count4 > 100000){
146
+ $wfdb->query("truncate table $p"."wfStatus");
147
+ } else if($count4 > 1000){ //max status events we keep. This determines how much gets emailed to us when users sends us a debug report.
148
+ $wfdb->query("delete from $p"."wfStatus where level != 10 order by ctime asc limit %d", ($count4 - 1000));
149
  $count5 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus where level=10");
150
  if($count5 > 100){
151
  $wfdb->query("delete from $p"."wfStatus where level = 10 order by ctime asc limit %d", ($count5 - 100) );
736
  $wfIssues->updateIssue($issueID, $status);
737
  return array('ok' => 1);
738
  }
739
+ public static function ajax_killScan_callback(){
740
+ wordfence::status(1, 'info', "Scan kill request received.");
741
+ wordfence::status(10, 'info', "SUM_KILLED:A request was received to kill the previous scan.");
742
+ wfUtils::clearScanLock(); //Clear the lock now because there may not be a scan running to pick up the kill request and clear the lock
743
+ wfScanEngine::requestKill();
744
+ return array(
745
+ 'ok' => 1,
746
+ );
747
+ }
748
  public static function ajax_loadIssues_callback(){
749
  $i = new wfIssues();
750
  $iss = $i->getIssues();
1112
  }
1113
  public static function admin_init(){
1114
  if(! wfUtils::isAdmin()){ return; }
1115
+ foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'permBlockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked', 'killScan') as $func){
1116
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1117
  }
1118
 
1180
  require 'menu_scan.php';
1181
  }
1182
  public static function status($level /* 1 has highest visibility */, $type /* info|error */, $msg){
1183
+ if($level > 3 && $level < 10 && (! self::isDebugOn())){ //level 10 and higher is for summary messages
1184
+ return false;
1185
+ }
1186
  if($type != 'info' && $type != 'error'){ error_log("Invalid status type: $type"); return; }
1187
  if(self::$printStatus){
1188
  echo "STATUS: $level : $type : $msg\n";
1334
  $exists = $db->querySingle("show tables like '$prefix"."wfConfig'");
1335
  return $exists ? true : false;
1336
  }
1337
+ public static function isDebugOn(){
1338
+ if(is_null(self::$debugOn)){
1339
+ if(wfConfig::get('debugOn')){
1340
+ self::$debugOn = true;
1341
+ } else {
1342
+ self::$debugOn = false;
1343
+ }
1344
+ }
1345
+ return self::$debugOn;
1346
+ }
1347
  }
1348
  ?>
lib/wordfenceConstants.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
- define('WORDFENCE_API_VERSION', 1.7);
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
  define('WORDFENCE_MAX_SCAN_TIME', 600);
1
  <?php
2
+ define('WORDFENCE_API_VERSION', 1.8);
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
  define('WORDFENCE_MAX_SCAN_TIME', 600);
lib/wordfenceHash.php CHANGED
@@ -1,27 +1,48 @@
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;
11
  public $linesOfPHP = 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
  }
@@ -35,17 +56,42 @@ class wordfenceHash {
35
  continue;
36
  }
37
  $file = $path . $file;
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){
51
  if(substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.'){
@@ -75,20 +121,39 @@ class wordfenceHash {
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");
82
  return;
83
  }
 
84
  if(function_exists('memory_get_usage')){
85
- wordfence::status(2, 'info', "Scanning: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
86
- } else {
87
- wordfence::status(2, 'info', "Scanning: $file");
88
- }
89
- $wfHash = $this->wfHash($file, true);
90
  if($wfHash){
91
- $this->hashes[substr($file, $this->striplen)] = $wfHash;
 
 
 
 
 
 
92
  //Now that we know we can open the file, lets update stats
93
  if(preg_match('/\.(?:js|html|htm|css)$/i', $file)){
94
  $this->linesOfJCH += sizeof(file($file));
@@ -97,15 +162,46 @@ class wordfenceHash {
97
  }
98
  $this->totalFiles++;
99
  $this->totalData += filesize($file);
 
 
 
100
  } else {
101
  wordfence::status(2, 'error', "Could not gen hash for file: $file");
102
  }
103
  }
104
- public function wfHash($file, $binary = true){
105
- $md5 = @md5_file($file, $binary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  if(! $md5){ return false; }
107
- //$sha = @hash_file('sha256', $file, $binary);
108
- //if(! $sha){ return false; }
109
  $fp = @fopen($file, "rb");
110
  if(! $fp){
111
  return false;
@@ -114,29 +210,8 @@ class wordfenceHash {
114
  while (!feof($fp)) {
115
  hash_update($ctx, str_replace($this->whitespace,"",fread($fp, 65536)));
116
  }
117
- $shac = hash_final($ctx, $binary);
118
- //Taking out $sha for now because we don't use it on the scanning server side
119
- return array($md5, '', $shac, filesize($file) );
120
- }
121
- public static function bin2hex($hashes){
122
- function wf_func1($elem){
123
- return array(
124
- bin2hex($elem[0]),
125
- bin2hex($elem[1]),
126
- bin2hex($elem[2])
127
- );
128
- }
129
- return array_map('wf_func1', $hashes);
130
- }
131
- public static function hex2bin($hashes){
132
- function wf_func2($elem){
133
- return array(
134
- pack('H*', $elem[0]),
135
- pack('H*', $elem[1]),
136
- pack('H*', $elem[2])
137
- );
138
- }
139
- return array_map('wf_func2', $hashes);
140
  }
141
  }
142
  ?>
1
  <?php
2
  require_once('wordfenceClass.php');
3
  class wordfenceHash {
4
+ private $apiKey = false;
5
+ private $wp_version = false;
6
+ private $api = false;
7
+ private $db = false;
8
+ private $table = false;
9
+ private $fileQ = array();
10
 
11
  //Begin serialized vars
12
  private $whitespace = array("\n","\r","\t"," ");
 
13
  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}'
14
  public $totalFiles = 0;
15
  public $totalDirs = 0;
16
  public $linesOfPHP = 0;
17
  public $linesOfJCH = 0; //lines of HTML, CSS and javascript
18
  public $striplen = 0;
19
+ private $hashPacket = "";
20
+ public $hashStorageID = false;
21
+ private $hashingStartTime = false;
22
+ private $lastStatusTime = false;
23
  public function __sleep(){ //same order as above
24
+ if(sizeof($this->fileQ) > 0){
25
+ wordfence::status(1, 'error', "Sanity fail. fileQ is not empty. Has: " . sizeof($this->fileQ));
26
+ }
27
+ return array('whitespace', 'totalData', 'totalFiles', 'totalDirs', 'linesOfPHP', 'linesOfJCH', 'striplen', 'hashPacket', 'hashStorageID', 'hashingStartTime', 'lastStatusTime');
28
  }
29
  public function __construct($striplen){
30
  $this->striplen = $striplen;
31
+ $this->db = new wfDB();
32
+ $this->table = $this->db->prefix() . 'wfFileQueue';
33
+ $this->apiKey = wfConfig::get('apiKey');
34
+ $this->wp_version = wfUtils::getWPVersion();
35
+ $this->api = new wfAPI($this->apiKey, $this->wp_version);
36
  }
37
  public function __wakeup(){
38
+ $this->db = new wfDB();
39
+ $this->table = $this->db->prefix() . 'wfFileQueue';
40
+ $this->apiKey = wfConfig::get('apiKey');
41
+ $this->wp_version = wfUtils::getWPVersion();
42
+ $this->api = new wfAPI($this->apiKey, $this->wp_version);
43
  }
44
  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.
45
+ $this->db->query("truncate table " . $this->table);
46
  if($path[strlen($path) - 1] != '/'){
47
  $path .= '/';
48
  }
56
  continue;
57
  }
58
  $file = $path . $file;
59
+ wordfence::status(4, 'info', "Hashing item in base dir: $file");
60
  $this->_dirHash($file);
61
  }
62
+ $this->writeFileQueue(); //Final write to DB
63
 
64
  }
65
  public function genHashes($forkObj){
66
+ if(! $this->hashingStartTime){
67
+ $this->hashingStartTime = microtime(true);
68
+ }
69
+ if(! $this->lastStatusTime){
70
+ $this->lastStatusTime = microtime(true);
71
+ }
72
+ $haveMoreInDB = true;
73
+ while($haveMoreInDB){
74
+ $haveMoreInDB = false;
75
+ $res = $this->db->query("select id, filename from " . $this->table . " limit 1000");
76
+ $ids = array();
77
+ while($rec = mysql_fetch_row($res)){
78
+ $this->processFile($rec[1]);
79
+ array_push($ids, $rec[0]);
80
+ $haveMoreInDB = true;
81
+ }
82
+ if(sizeof($ids) > 0){
83
+ $this->db->query("delete from " . $this->table . " where id IN (" . implode(',', $ids) . ")");
84
+ }
85
  $forkObj->forkIfNeeded();
86
  }
87
+ //Will only reach here if we empty file queue. fork may cause exit
88
+ $this->sendHashPacket();
89
+ $this->db->query("truncate table " . $this->table); //Also resets id autoincrement to 1
90
+ $this->writeHashingStatus();
91
+ }
92
+ private function writeHashingStatus(){
93
+ $this->lastStatusTime = microtime(true);
94
+ wordfence::status(2, 'info', "Scanned " . $this->totalFiles . " files at a rate of " . sprintf('%.2f', ($this->totalFiles / (microtime(true) - $this->hashingStartTime))) . " files per second.");
95
  }
96
  private function _dirHash($path){
97
  if(substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.'){
121
  }
122
  private function qFile($file){
123
  $this->fileQ[] = $file;
124
+ if(sizeof($this->fileQ) > 1000){
125
+ $this->writeFileQueue();
126
+ }
127
+ }
128
+ private function writeFileQueue(){
129
+ $sql = "insert into " . $this->table . " (filename) values ";
130
+ foreach($this->fileQ as $val){
131
+ $sql .= "('" . mysql_real_escape_string($val) . "'),";
132
+ }
133
+ $sql = rtrim($sql, ',');
134
+ $this->db->query($sql);
135
+ $this->fileQ = array();
136
  }
137
  private function processFile($file){
138
  if(@filesize($file) > WORDFENCE_MAX_FILE_SIZE_TO_PROCESS){
139
  wordfence::status(2, 'info', "Skipping file larger than 50 megs: $file");
140
  return;
141
  }
142
+
143
  if(function_exists('memory_get_usage')){
144
+ wordfence::status(4, 'info', "Scanning: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
145
+ } else {
146
+ wordfence::status(4, 'info', "Scanning: $file");
147
+ }
148
+ $wfHash = $this->wfHash($file);
149
  if($wfHash){
150
+ $packetFile = substr($file, $this->striplen);
151
+ $this->hashPacket .= $wfHash[0] . $wfHash[1] . pack('n', strlen($packetFile)) . $packetFile;
152
+ if(strlen($this->hashPacket) > 500000){ //roughly 2 megs in string mem space
153
+ $this->writeHashingStatus();
154
+ $this->sendHashPacket();
155
+ }
156
+
157
  //Now that we know we can open the file, lets update stats
158
  if(preg_match('/\.(?:js|html|htm|css)$/i', $file)){
159
  $this->linesOfJCH += sizeof(file($file));
162
  }
163
  $this->totalFiles++;
164
  $this->totalData += filesize($file);
165
+ if(microtime(true) - $this->lastStatusTime > 1){
166
+ $this->writeHashingStatus();
167
+ }
168
  } else {
169
  wordfence::status(2, 'error', "Could not gen hash for file: $file");
170
  }
171
  }
172
+ private function sendHashPacket(){
173
+ wordfence::status(4, 'info', "Sending packet of hash data to Wordfence scanning servers");
174
+ if(strlen($this->hashPacket) < 1){
175
+ return;
176
+ }
177
+ if($this->hashStorageID){
178
+ $dataArr = $this->api->binCall('add_hash_chunk', "WFID:" . pack('N', $this->hashStorageID) . $this->hashPacket);
179
+ if($this->api->errorMsg){ wordfence::status(1, 'error', $this->api->errorMsg); exit(); }
180
+ $this->hashPacket = "";
181
+ if(is_array($dataArr) && isset($dataArr['data']) && $dataArr['data'] == $this->hashStorageID){
182
+ //keep going
183
+ } else {
184
+ wordfence::status(1, 'error', "Could not store an additional chunk of hash data on Wordfence servers with ID: " . $this->hashStorageID);
185
+ return false;
186
+ }
187
+ } else {
188
+ $dataArr = $this->api->binCall('add_hash_chunk', "WFST:" . $this->hashPacket);
189
+ if($this->api->errorMsg){ wordfence::status(1, 'error', $this->api->errorMsg); exit(); }
190
+ $this->hashPacket = "";
191
+ if(is_array($dataArr) && isset($dataArr['data']) && preg_match('/^\d+$/', $dataArr['data'])){
192
+ $this->hashStorageID = $dataArr['data'];
193
+ } else {
194
+ wordfence::status(1, 'error', "Could not store hash data on Wordfence servers. Got response: " . var_export($dataArr, true));
195
+ return false;
196
+ }
197
+ }
198
+ }
199
+ public function getHashStorageID(){
200
+ return $this->hashStorageID;
201
+ }
202
+ public function wfHash($file){
203
+ $md5 = @md5_file($file, false);
204
  if(! $md5){ return false; }
 
 
205
  $fp = @fopen($file, "rb");
206
  if(! $fp){
207
  return false;
210
  while (!feof($fp)) {
211
  hash_update($ctx, str_replace($this->whitespace,"",fread($fp, 65536)));
212
  }
213
+ $shac = hash_final($ctx, false);
214
+ return array($md5, $shac);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
  }
217
  ?>
lib/wordfenceScanner.php CHANGED
@@ -3,21 +3,25 @@ require_once('wordfenceConstants.php');
3
  require_once('wordfenceClass.php');
4
  require_once('wordfenceURLHoover.php');
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
  }
@@ -28,7 +32,23 @@ class wordfenceScanner {
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
  }
@@ -50,11 +70,12 @@ class wordfenceScanner {
50
  } else {
51
  $fsize = $fsize . "B";
52
  }
53
- if(function_exists('memory_get_usage')){
54
- wordfence::status(2, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
55
- } else {
56
- wordfence::status(2, 'info', "Scanning contents: $file (Size: $fsize)");
57
- }
 
58
  $stime = microtime(true);
59
  $fileSum = @md5_file($this->path . $file);
60
  if(! $fileSum){
@@ -120,11 +141,14 @@ class wordfenceScanner {
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){
@@ -173,6 +197,9 @@ class wordfenceScanner {
173
 
174
  return $this->results;
175
  }
 
 
 
176
  private function addEncIssue($ignoreP, $ignoreC, $encoding, $file){
177
  $this->addResult(array(
178
  'type' => 'file',
3
  require_once('wordfenceClass.php');
4
  require_once('wordfenceURLHoover.php');
5
  class wordfenceScanner {
6
+ //serialized:
7
  protected $path = '';
8
  protected $fileList = array();
9
  protected $results = array();
10
  public $errorMsg = false;
11
  private $apiKey = false;
12
  private $wordpressVersion = '';
13
+ private $totalFilesScanned = 0;
14
+ private $startTime = false;
15
+ private $lastStatusTime = false;
16
  public function __sleep(){
17
+ return array('path', 'fileList', 'results', 'errorMsg', 'apiKey', 'wordpressVersion', 'urlHoover', 'totalFilesScanned', 'startTime', 'lastStatusTime');
18
  }
19
  public function __wakeup(){
20
  }
21
  public function __construct($apiKey, $wordpressVersion, $fileList, $path){
22
  $this->apiKey = $apiKey;
23
  $this->wordpressVersion = $wordpressVersion;
24
+ $this->fileList = $fileList; //A long string of <2 byte network order short showing filename length><filename>
25
  if($path[strlen($path) - 1] != '/'){
26
  $path .= '/';
27
  }
32
  $this->urlHoover = new wordfenceURLHoover($this->apiKey, $this->wordpressVersion);
33
  }
34
  public function scan($forkObj){
35
+ if(! $this->startTime){
36
+ $this->startTime = microtime(true);
37
+ }
38
+ if(! $this->lastStatusTime){
39
+ $this->lastStatusTime = microtime(true);
40
+ }
41
+ while(strlen($this->fileList) > 0){
42
+ $filenameLen = unpack('n', substr($this->fileList, 0, 2));
43
+ $filenameLen = $filenameLen[1];
44
+ if($filenameLen > 1000 || $filenameLen < 1){
45
+ wordfence::status(1, 'error', "wordfenceScanner got bad data from the Wordfence API with a filename length of: " . $filenameLen);
46
+ exit();
47
+ }
48
+
49
+ $file = substr($this->fileList, 2, $filenameLen);
50
+ $this->fileList = substr($this->fileList, 2 + $filenameLen);
51
+
52
  if(! file_exists($this->path . $file)){
53
  continue;
54
  }
70
  } else {
71
  $fsize = $fsize . "B";
72
  }
73
+ if(function_exists('memory_get_usage')){
74
+ wordfence::status(4, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
75
+ } else {
76
+ wordfence::status(4, 'info', "Scanning contents: $file (Size: $fsize)");
77
+ }
78
+
79
  $stime = microtime(true);
80
  $fileSum = @md5_file($this->path . $file);
81
  if(! $fileSum){
141
  }
142
  fclose($fh);
143
  $mtime = sprintf("%.5f", microtime(true) - $stime);
144
+ $this->totalFilesScanned++;
145
+ if(microtime(true) - $this->lastStatusTime > 1){
146
+ $this->lastStatusTime = microtime(true);
147
+ $this->writeScanningStatus();
148
+ }
149
  $forkObj->forkIfNeeded();
150
  }
151
+ $this->writeScanningStatus();
 
 
152
  wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
153
  $hooverResults = $this->urlHoover->getBaddies();
154
  if($this->urlHoover->errorMsg){
197
 
198
  return $this->results;
199
  }
200
+ private function writeScanningStatus(){
201
+ wordfence::status(2, 'info', "Scanned contents of " . $this->totalFilesScanned . " files at a rate of " . sprintf('%.2f', ($this->totalFilesScanned / (microtime(true) - $this->startTime))) . " files per second");
202
+ }
203
  private function addEncIssue($ignoreP, $ignoreC, $encoding, $file){
204
  $this->addResult(array(
205
  'type' => 'file',
lib/wordfenceURLHoover.php CHANGED
@@ -1,9 +1,10 @@
1
  <?php
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;
@@ -11,13 +12,16 @@ class wordfenceURLHoover {
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);
@@ -37,6 +41,7 @@ class wordfenceURLHoover {
37
  @preg_replace("/(?<=^|[^a-zA-Z0-9\-])((?:[a-zA-Z0-9\-]+\.)+)(" . $this->dRegex . ")((?:$|[^a-zA-Z0-9\-\.\'\"])[^\r\n\s\t\"\'\$\{\}<>]*)/ie", "\$this->" . "addHost(\$id, '$1$2', '$3')", $data);
38
  } catch(Exception $e){ error_log("Regex error 1: $e"); }
39
  preg_replace("/(?<=[^\d]|^)(\d{8,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})([^\d\'\"][^\r\n\s\t\"\'\$\{\}<>]*)/e", "\$this->" . "addIP(\$id, \"$1\",\"$2\")", $data);
 
40
  }
41
  private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
42
  public function addHost($id, $host, $path){
@@ -72,15 +77,25 @@ class wordfenceURLHoover {
72
  if(strpos($path, '/') !== 0){
73
  $path = '/';
74
  }
75
- $this->db->query("insert into $this->table (owner, host, path, hostKey) values ('%s', '%s', '%s', '%s')", $id, $host, $path, $this->makeHostKey($host));
 
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,7 +103,6 @@ class wordfenceURLHoover {
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(){
1
  <?php
2
  require_once('wfAPI.php');
3
+ require_once('wfArray.php');
4
  class wordfenceURLHoover {
5
  private $debug = false;
6
  public $errorMsg = false;
7
+ private $hostsToAdd = false;
8
  private $table = '';
9
  private $apiKey = false;
10
  private $wordpressVersion = false;
12
  private $api = false;
13
  private $db = false;
14
  public function __sleep(){
15
+ $this->writeHosts();
16
  return array('debug', 'errorMsg', 'table', 'apiKey', 'wordpressVersion', 'dRegex');
17
  }
18
  public function __wakeup(){
19
+ $this->hostsToAdd = new wfArray(array('owner', 'host', 'path', 'hostKey'));
20
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
21
  $this->db = new wfDB();
22
  }
23
  public function __construct($apiKey, $wordpressVersion){
24
+ $this->hostsToAdd = new wfArray(array('owner', 'host', 'path', 'hostKey'));
25
  $this->apiKey = $apiKey;
26
  $this->wordpressVersion = $wordpressVersion;
27
  $this->api = new wfAPI($apiKey, $wordpressVersion);
41
  @preg_replace("/(?<=^|[^a-zA-Z0-9\-])((?:[a-zA-Z0-9\-]+\.)+)(" . $this->dRegex . ")((?:$|[^a-zA-Z0-9\-\.\'\"])[^\r\n\s\t\"\'\$\{\}<>]*)/ie", "\$this->" . "addHost(\$id, '$1$2', '$3')", $data);
42
  } catch(Exception $e){ error_log("Regex error 1: $e"); }
43
  preg_replace("/(?<=[^\d]|^)(\d{8,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})([^\d\'\"][^\r\n\s\t\"\'\$\{\}<>]*)/e", "\$this->" . "addIP(\$id, \"$1\",\"$2\")", $data);
44
+ $this->writeHosts();
45
  }
46
  private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
47
  public function addHost($id, $host, $path){
77
  if(strpos($path, '/') !== 0){
78
  $path = '/';
79
  }
80
+ $this->hostsToAdd->push(array('owner' => $id, 'host' => $host, 'path' => $path, 'hostKey' => $this->makeHostKey($host)));
81
+ if($this->hostsToAdd->size() > 1000){ $this->writeHosts(); }
82
  return true;
83
  }
84
+ private function writeHosts(){
85
+ if($this->hostsToAdd->size() < 1){ return; }
86
+ $sql = "insert into " . $this->table . " (owner, host, path, hostKey) values ";
87
+ while($elem = $this->hostsToAdd->shift()){
88
+ $sql .= sprintf("('%s', '%s', '%s', '%s'),",
89
+ mysql_real_escape_string($elem['owner']),
90
+ mysql_real_escape_string($elem['host']),
91
+ mysql_real_escape_string($elem['path']),
92
+ mysql_real_escape_string($elem['hostKey'])
93
+ );
94
  }
95
+ $sql = rtrim($sql, ',');
96
+ $this->db->query($sql);
97
+ }
98
+ private function makeHostKey($host){
99
  $hostParts = explode('.', $host);
100
  $hostKey = '';
101
  if(sizeof($hostParts) == 2){
103
  } else if(sizeof($hostParts) > 2){
104
  $hostKey = substr(hash('sha256', $hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
105
  }
 
106
  return $hostKey;
107
  }
108
  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.4
6
- Stable tag: 2.1.5
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,27 @@ or a theme, because often these have been updated to fix a security hole.
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  = 2.1.5 =
156
  * Fixed bug that caused WF to not work when certain DB caching plugins are used and override wpdb object.
157
  * Fixed Wordfence so activity log only shows our own errors unless in debug mode.
@@ -389,4 +410,11 @@ or a theme, because often these have been updated to fix a security hole.
389
  = 1.1 =
390
  * Initial public release of Wordfence.
391
 
 
392
 
 
 
 
 
 
 
3
  Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
  Requires at least: 3.3.1
5
  Tested up to: 3.4
6
+ Stable tag: 3.0.2
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
155
+
156
+ = 3.0.2 =
157
+ * Overall this release is a very important upgrade. It drastically reduces memory usage on systems with large files from hundreds of megs to around 8 megs max memory used per scan.
158
+ * Moved queue of files that get processed to a new DB table to save memory.
159
+ * Reduced max size of tables before we truncate to avoid long DB queries.
160
+ * Reduced max size of wfStatus table from 100,000 rows to 1,000 rows.
161
+ * Introduced feature to kill hung or crashed scans reliably.
162
+ * Made scan locking much more reliable to avoid multiple concurrent scans hogging resources.
163
+ * Debug status messages are no longer written to the DB in non-debug mode.
164
+ * Modified the list of unknown files we receive back from the WF scanning servers to be a packed string rather than an array which is more memory efficient.
165
+ * Added summary at the end of scans to show the peak memory that Wordfence used along with server peak memory.
166
+ * Hashes are now progressively sent to Wordfence servers during scan to drastically reduce memory usage.
167
+ * Upgraded to Wordfence server API version 1.8
168
+ * List of hosts that Wordfence URL scanner compiles now uses wfArray which is a very memory efficient packed binary structure.
169
+ * Writes that WF URL scanner makes to the DB are now batched into bulk inserts to reduce load on DB.
170
+ * Fixed bug in wfscan.php (scanning script) that could have caused scans to loop or pick up old data.
171
+ * Massively reduced the number of status messages we log, but kept very verbose logging for debug mode with a warning about DB load.
172
+ * Added summary messages instead of individual file scanning status messages which show files scanned and scan rate.
173
+ * Removed bin2hex and hex2bin conversions for scanning data which were slow, memory heavy and unneeded.
174
+ * Wordfence database class will now reuse the WordPress database handle from $wpdb if it can to reduce DB connections.
175
+
176
  = 2.1.5 =
177
  * Fixed bug that caused WF to not work when certain DB caching plugins are used and override wpdb object.
178
  * Fixed Wordfence so activity log only shows our own errors unless in debug mode.
410
  = 1.1 =
411
  * Initial public release of Wordfence.
412
 
413
+ == Upgrade Notice ==
414
 
415
+ = 3.0.2 =
416
+ Upgrade immediately. This release drastically reduces memory, reduces new DB connections created by
417
+ Wordfence to zero (we simply reuse the WordPress DB handle), reduces the number of DB queries to
418
+ about 1% of the previous version by removing unneeded status messages and fixes a bug that
419
+ could cause Wordfence to launch multiple concurrent scans that can put high load on your system.
420
+ This is a critical release. Upgrade immediately.
wfscan.php CHANGED
@@ -24,7 +24,9 @@ require_once('lib/wfScanEngine.php');
24
  class wfScan {
25
  public static $debugMode = false;
26
  public static $errorHandlingOn = true;
 
27
  public static function wfScanMain(){
 
28
  $db = new wfDB();
29
  if($db->errorMsg){
30
  self::errorExit("Could not connect to database to start scan: " . $db->errorMsg);
@@ -70,9 +72,13 @@ class wfScan {
70
  wordfence::status(4, 'info', "Becoming admin for scan");
71
  self::becomeAdmin();
72
 
73
- wordfence::status(4, 'info', "Checking if scan is already running");
74
- if(! wfUtils::getScanLock()){
75
- self::errorExit("There is already a scan running.");
 
 
 
 
76
  }
77
  wordfence::status(4, 'info', "Requesting max memory");
78
  wfUtils::requestMaxMemory();
@@ -85,24 +91,32 @@ class wfScan {
85
  @error_reporting(E_ALL);
86
  @ini_set('display_errors','On');
87
  wordfence::status(4, 'info', "Setting up scanRunning and starting scan");
88
- $isFork = ($_GET['isFork'] == '1' ? true : false);
89
- $scan = wfConfig::get_ser('wfsd_engine', false, true);
90
- if($scan){
91
- //Set false so that we don't get stuck in a loop where we're repeating scan stages.
92
- wordfence::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
93
- wfConfig::set('wfsd_engine', '', true);
94
- } else {
95
- if($isFork){ //We encountered an error so blank scan and exit
96
  wordfence::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
97
  wfConfig::set('wfsd_engine', '', true);
98
  exit();
99
- } else {
100
- wordfence::statusPrep(); //Re-initializes all status counters
101
- $scan = new wfScanEngine();
102
  }
 
 
 
103
  }
104
  $scan->go();
105
  wfUtils::clearScanLock();
 
 
 
 
 
 
 
 
 
106
  }
107
  public static function obHandler($buf){
108
  if(strlen($buf) > 1000){
@@ -123,7 +137,7 @@ class wfScan {
123
  }
124
  }
125
  public static function shutdown(){
126
- wfUtils::clearScanLock();
127
  }
128
  private static function errorExit($msg){
129
  echo json_encode(array('errorMsg' => $msg));
24
  class wfScan {
25
  public static $debugMode = false;
26
  public static $errorHandlingOn = true;
27
+ private static $peakMemAtStart = 0;
28
  public static function wfScanMain(){
29
+ self::$peakMemAtStart = memory_get_peak_usage();
30
  $db = new wfDB();
31
  if($db->errorMsg){
32
  self::errorExit("Could not connect to database to start scan: " . $db->errorMsg);
72
  wordfence::status(4, 'info', "Becoming admin for scan");
73
  self::becomeAdmin();
74
 
75
+ $isFork = ($_GET['isFork'] == '1' ? true : false);
76
+
77
+ if(! $isFork){
78
+ wordfence::status(4, 'info', "Checking if scan is already running");
79
+ if(! wfUtils::getScanLock()){
80
+ self::errorExit("There is already a scan running.");
81
+ }
82
  }
83
  wordfence::status(4, 'info', "Requesting max memory");
84
  wfUtils::requestMaxMemory();
91
  @error_reporting(E_ALL);
92
  @ini_set('display_errors','On');
93
  wordfence::status(4, 'info', "Setting up scanRunning and starting scan");
94
+ $scan = false;
95
+ if($isFork){
96
+ $scan = wfConfig::get_ser('wfsd_engine', false, true);
97
+ if($scan){
98
+ wordfence::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
99
+ wfConfig::set('wfsd_engine', '', true);
100
+ } else {
 
101
  wordfence::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
102
  wfConfig::set('wfsd_engine', '', true);
103
  exit();
 
 
 
104
  }
105
+ } else {
106
+ wordfence::statusPrep(); //Re-initializes all status counters
107
+ $scan = new wfScanEngine();
108
  }
109
  $scan->go();
110
  wfUtils::clearScanLock();
111
+ self::logPeakMemory();
112
+ wordfence::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
113
+ }
114
+ private static function logPeakMemory(){
115
+ $oldPeak = wfConfig::get('wfPeakMemory', 0);
116
+ $peak = memory_get_peak_usage();
117
+ if($peak > $oldPeak){
118
+ wfConfig::set('wfPeakMemory', $peak);
119
+ }
120
  }
121
  public static function obHandler($buf){
122
  if(strlen($buf) > 1000){
137
  }
138
  }
139
  public static function shutdown(){
140
+ self::logPeakMemory();
141
  }
142
  private static function errorExit($msg){
143
  echo json_encode(array('errorMsg' => $msg));
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
- Version: 2.1.5
8
  Author URI: http://wordfence.com/
9
  */
10
- define('WORDFENCE_VERSION', '2.1.5');
11
  if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
  require_once('lib/wordfenceConstants.php');
13
  require_once('lib/wordfenceClass.php');
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
+ Version: 3.0.2
8
  Author URI: http://wordfence.com/
9
  */
10
+ define('WORDFENCE_VERSION', '3.0.2');
11
  if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
  require_once('lib/wordfenceConstants.php');
13
  require_once('lib/wordfenceClass.php');