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