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)#x2F;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)#x2F;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+#x2F;', $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');