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 | 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 +13 -0
- lib/menu_options.php +2 -1
- lib/menu_scan.php +2 -0
- lib/wfArray.php +46 -0
- lib/wfConfig.php +5 -0
- lib/wfDB.php +19 -11
- lib/wfScanEngine.php +42 -21
- lib/wfSchema.php +4 -0
- lib/wfUtils.php +19 -29
- lib/wordfenceClass.php +36 -9
- lib/wordfenceConstants.php +1 -1
- lib/wordfenceHash.php +116 -41
- lib/wordfenceScanner.php +38 -11
- lib/wordfenceURLHoover.php +22 -8
- readme.txt +29 -1
- wfscan.php +29 -15
- wordfence.php +2 -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 |
+
<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 |
-
|
58 |
-
if($dbh
|
59 |
-
|
60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 =
|
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(
|
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->
|
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 |
-
'
|
237 |
);
|
238 |
-
$
|
239 |
-
|
240 |
-
));
|
241 |
if($this->api->errorMsg){
|
242 |
$this->errorStop($this->api->errorMsg);
|
243 |
wordfence::statusEndErr();
|
244 |
return;
|
245 |
}
|
246 |
-
if(
|
247 |
-
$this->errorStop(
|
248 |
wordfence::statusEndErr();
|
249 |
return;
|
250 |
}
|
251 |
-
|
252 |
-
|
|
|
|
|
|
|
|
|
|
|
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($
|
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 |
-
|
827 |
-
|
828 |
-
|
|
|
|
|
|
|
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 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
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 |
-
|
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 =
|
270 |
-
if(
|
271 |
-
|
|
|
|
|
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 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
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 >
|
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 >
|
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){
|
143 |
-
$wfdb->query("
|
|
|
|
|
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.
|
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 $
|
|
|
|
|
|
|
15 |
public function __sleep(){ //same order as above
|
16 |
-
|
|
|
|
|
|
|
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(
|
39 |
$this->_dirHash($file);
|
40 |
}
|
|
|
41 |
|
42 |
}
|
43 |
public function genHashes($forkObj){
|
44 |
-
|
45 |
-
$this->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
$forkObj->forkIfNeeded();
|
47 |
}
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
$wfHash = $this->wfHash($file
|
90 |
if($wfHash){
|
91 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
118 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
if(! file_exists($this->path . $file)){
|
33 |
continue;
|
34 |
}
|
@@ -50,11 +70,12 @@ class wordfenceScanner {
|
|
50 |
} else {
|
51 |
$fsize = $fsize . "B";
|
52 |
}
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
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 |
-
|
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 |
-
|
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->
|
|
|
76 |
return true;
|
77 |
}
|
78 |
-
private function
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
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 |
-
|
74 |
-
|
75 |
-
|
|
|
|
|
|
|
|
|
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 |
-
$
|
89 |
-
$
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
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 |
-
|
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:
|
8 |
Author URI: http://wordfence.com/
|
9 |
*/
|
10 |
-
define('WORDFENCE_VERSION', '
|
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');
|