Wordfence Security – Firewall & Malware Scan - Version 3.0.7

Version Description

  • Fixed bug that caused scan to loop, stop halfway or not start for many sites.
  • Fix bug that caused scan to not start on sites with thousands (over 20,000 in one case) users.
  • Scan start is now faster for sites with large numbers of users.
  • Fix bug that caused scan to get killed when checking passwords on sites with thousands of users.
  • Wordfence now intelligently determines how to do a loopback request to kick off a scan.
  • Scan is no longer called with a cron key in HTTP header but uses a query string value to authenticate itself which is more reliable.
Download this release

Release Info

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

Code changes from version 3.0.6 to 3.0.7

Files changed (6) hide show
  1. js/admin.js +1 -1
  2. lib/wfConfig.php +2 -3
  3. lib/wfScanEngine.php +128 -116
  4. readme.txt +9 -1
  5. wfscan.php +59 -70
  6. wordfence.php +2 -2
js/admin.js CHANGED
@@ -351,7 +351,7 @@ window['wordfenceAdmin'] = {
351
setTimeout(function(res){
352
clearInterval(scanReqAnimation);
353
jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Start a Wordfence Scan");
354
- }, 2000);
355
this.ajax('wordfence_scan', {}, function(res){ } );
356
},
357
loadIssues: function(callback){
351
setTimeout(function(res){
352
clearInterval(scanReqAnimation);
353
jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Start a Wordfence Scan");
354
+ }, 3000);
355
this.ajax('wordfence_scan', {}, function(res){ } );
356
},
357
loadIssues: function(callback){
lib/wfConfig.php CHANGED
@@ -373,9 +373,8 @@ class wfConfig {
373
}
374
public static function set($key, $val){
375
if(is_array($val)){
376
- $msg = "wfConfig::set() got an array as second param with key: $key - Please report this bug. Exiting.";
377
- wfstatus(1, 'error', $msg);
378
- error_log($msg);
379
return;
380
}
381
373
}
374
public static function set($key, $val){
375
if(is_array($val)){
376
+ $msg = "wfConfig::set() got an array as second param with key: $key and value: " . var_export($val, true);
377
+ wordfence::status(1, 'error', $msg);
378
return;
379
}
380
lib/wfScanEngine.php CHANGED
@@ -9,6 +9,7 @@ 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;
@@ -36,8 +37,10 @@ class wfScanEngine {
36
'theme' => false,
37
'unknown' => false
38
);
39
public function __sleep(){ //Same order here as above for properties that are included in serialization
40
- return array('hasher', 'hashes', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'scanStep', 'maxExecTime', 'malwareScanEnabled', 'pluginScanEnabled', 'coreScanEnabled', 'themeScanEnabled', 'unknownFiles', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX');
41
}
42
public function __construct(){
43
$this->startTime = time();
@@ -106,7 +109,11 @@ class wfScanEngine {
106
call_user_func(array($this, 'scan_' . $jobName));
107
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
108
self::checkForKill();
109
- $this->fork();
110
}
111
$summary = $this->i->getSummaryItems();
112
$this->status(1, 'info', '-------------------');
@@ -569,23 +576,50 @@ class wfScanEngine {
569
}
570
return false;
571
}
572
- private function scan_passwds(){
573
$this->statusIDX['passwds'] = wordfence::statusStart('Scanning for weak passwords');
574
global $wpdb;
575
- $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
576
$haveIssues = false;
577
- foreach($ws as $user){
578
- $this->status(2, 'info', "Checking password strength for: " . $user->user_login);
579
- $isWeak = $this->scanUserPassword($user->ID);
580
- if($isWeak){ $haveIssues = true; }
581
}
582
- wordfence::statusEnd($this->statusIDX['passwds'], $haveIssues);
583
}
584
public function scanUserPassword($userID){
585
require_once( ABSPATH . 'wp-includes/class-phpass.php');
586
$passwdHasher = new PasswordHash(8, TRUE);
587
$userDat = get_userdata($userID);
588
- $this->status(2, 'info', "Checking password strength of user '" . $userDat->user_login . "'");
589
$shortMsg = "";
590
$longMsg = "";
591
$level = 1;
@@ -618,7 +652,7 @@ class wfScanEngine {
618
break;
619
}
620
}
621
- $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
622
return $haveIssue;
623
}
624
/*
@@ -852,6 +886,68 @@ class wfScanEngine {
852
throw new Exception("Scan was killed on administrator request.");
853
}
854
}
855
public static function startScan($isFork = false){
856
if(! $isFork){ //beginning of scan
857
wfConfig::set('wfKillRequested', 0);
@@ -859,119 +955,35 @@ class wfScanEngine {
859
if(wfUtils::isScanRunning()){
860
return "A scan is already running. Use the kill link if you would like to terminate the current scan.";
861
}
862
}
863
864
- $cron_url = plugins_url('wordfence/wfscan.php?isFork=' . ($isFork ? '1' : '0'));
865
- wordfence::status(4, 'info', "Cron URL is: " . $cron_url);
866
$cronKey = wfUtils::bigRandomHex();
867
- wordfence::status(4, 'info', "cronKey is: " . $cronKey);
868
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
869
- wordfence::status(4, 'info', "cronKey is set");
870
- $result = wp_remote_post( $cron_url, array(
871
- 'timeout' => 0.5,
872
- 'blocking' => true,
873
- 'sslverify' => false,
874
- 'headers' => array(
875
- 'x-wordfence-cronkey' => $cronKey
876
- )
877
- ) );
878
- $procResp = self::processResponse($result);
879
- if($procResp){ return $procResp; }
880
- //If the currentCronKey was eaten, then cron executed so return
881
- wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){
882
- wordfence::status(4, 'info', "cronkey is empty so cron executed. Returning.");
883
- return false;
884
- }
885
-
886
-
887
- //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.
888
- wordfence::status(4, 'info', "cronkey is still set so sleeping for 0.2 seconds and checking again before trying another approach");
889
- usleep(200000);
890
- wfConfig::clearCache();
891
- if(wfConfig::get('currentCronKey')){ //cron key is still set, so cron hasn't executed yet. Maybe the request didn't go through
892
- wordfence::status(4, 'info', "cronkey is still set so about to manually set host header and try again");
893
- $cron_url2 = preg_replace('/^(https?):\/\/[^\/]+/', '$1://127.0.0.1', $cron_url);
894
- wordfence::status(4, 'info', "cron url is: $cron_url2");
895
- $siteURL = site_url();
896
- wordfence::status(4, 'info', "siteURL is: $siteURL");
897
- if(preg_match('/^https?:\/\/([^\/]+)/i', site_url(), $matches)){
898
- $host = $matches[1];
899
- wordfence::status(4, 'info', "Extracted host $host from siteURL and trying remote post with manual host header set.");
900
- $result = wp_remote_post( $cron_url2, array(
901
- 'timeout' => 0.5,
902
- 'blocking' => true,
903
- 'sslverify' => false,
904
- 'headers' => array(
905
- 'x-wordfence-cronkey' => $cronKey,
906
- 'Host' => $host
907
- )
908
- ) );
909
- $procResp = self::processResponse($result);
910
- if($procResp){ return $procResp; }
911
- }
912
} else {
913
- return false;
914
- }
915
-
916
- //Try again with longer timeout. Dreamhost was giving DNS lookup timeouts
917
- usleep(200000);
918
- wfConfig::clearCache();
919
- if(wfConfig::get('currentCronKey')){
920
- wordfence::status(4, "info", "cronkey is still set. Trying hostname again with longer timeout.");
921
- $result = wp_remote_post( $cron_url, array(
922
- 'timeout' => 10,
923
- 'blocking' => true,
924
- 'sslverify' => false,
925
- 'headers' => array(
926
- 'x-wordfence-cronkey' => $cronKey
927
- )
928
- ) );
929
- $procResp = self::processResponse($result);
930
- if($procResp){ return $procResp; }
931
- //If the currentCronKey was eaten, then cron executed so return
932
- wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){
933
- wordfence::status(4, 'info', "cronkey is empty so cron executed. Returning.");
934
- return false;
935
- }
936
}
937
- return false;
938
}
939
public function processResponse($result){
940
- if(is_wp_error($result)){
941
- wordfence::status(4, 'info', "Debug output of WP_Error: " . implode('; ', $result->get_error_messages()) );
942
- } else {
943
- if(is_array($result)){
944
- if(is_array($result['response'])){
945
- wordfence::status(4, 'info', "POST response: " . $result['response']['code'] . ' ' . $result['response']['message']);
946
- }
947
- wordfence::status(4, 'info', "POST response body: " . $result['body']);
948
- }
949
- }
950
- if((! is_wp_error($result)) && is_array($result) && empty($result['body']) === false){
951
- if(strpos($result['body'], 'WFSOURCEVISIBLE') !== false){
952
- wordfence::status(4, 'info', "wfscan.php source is visible.");
953
- $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.";
954
- $htfiles = array();
955
- if(file_exists(ABSPATH . 'wp-content/.htaccess')){
956
- 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>');
957
- }
958
- if(file_exists(ABSPATH . 'wp-content/plugins/.htaccess')){
959
- 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>');
960
- }
961
- if(sizeof($htfiles) > 0){
962
- $msg .= "<br /><br />Click to view the .htaccess files below that may be the cause of this problem:<br />" . implode('<br />', $htfiles);
963
- }
964
- return $msg;
965
-
966
- } else if(strpos($result['body'], '{') !== false && strpos($result['body'], 'errorMsg') !== false){
967
- wordfence::status(4, 'info', "Got response from cron containing json");
968
- $resp = json_decode($result['body'], true);
969
- if(empty($resp['errorMsg']) === false){
970
- wordfence::status(4, 'info', "Got an error message from cron: " . $resp['errorMsg']);
971
- return $resp['errorMsg'];
972
- }
973
- }
974
- }
975
return false;
976
}
977
}
9
class wfScanEngine {
10
private $api = false;
11
private $dictWords = array();
12
+ private $forkRequested = false;
13
14
//Beginning of serialized properties on sleep
15
private $hasher = false;
37
'theme' => false,
38
'unknown' => false
39
);
40
+ private $userPasswdQueue = "";
41
+ private $passwdHasIssues = false;
42
public function __sleep(){ //Same order here as above for properties that are included in serialization
43
+ return array('hasher', 'hashes', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'scanStep', 'maxExecTime', 'malwareScanEnabled', 'pluginScanEnabled', 'coreScanEnabled', 'themeScanEnabled', 'unknownFiles', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues');
44
}
45
public function __construct(){
46
$this->startTime = time();
109
call_user_func(array($this, 'scan_' . $jobName));
110
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
111
self::checkForKill();
112
+ if($this->forkRequested){
113
+ $this->fork();
114
+ } else {
115
+ $this->forkIfNeeded();
116
+ }
117
}
118
$summary = $this->i->getSummaryItems();
119
$this->status(1, 'info', '-------------------');
576
}
577
return false;
578
}
579
+ private function scan_passwds_init(){
580
$this->statusIDX['passwds'] = wordfence::statusStart('Scanning for weak passwords');
581
global $wpdb;
582
+ $wfdb = new wfDB();
583
+ $res1 = $wfdb->query("select ID from " . $wpdb->users);
584
+ $counter = 0;
585
+ while($rec = mysql_fetch_row($res1)){
586
+ $this->userPasswdQueue .= pack('N', $rec[0]);
587
+ $counter++;
588
+ }
589
+ wordfence::status(2, 'info', "Starting password strength check on $counter users.");
590
+ }
591
+ private function scan_passwds_main(){
592
+ global $wpdb;
593
+ $wfdb = new wfDB();
594
$haveIssues = false;
595
+ while(strlen($this->userPasswdQueue) > 3){
596
+ $usersLeft = strlen($this->userPasswdQueue) / 4; //4 byte ints
597
+ if($usersLeft % 100 == 0){
598
+ wordfence::status(2, 'info', "Total of $usersLeft users left to process in password strength check.");
599
+ }
600
+ $userID = unpack('N', substr($this->userPasswdQueue, 0, 4));
601
+ $userID = $userID[1];
602
+ $this->userPasswdQueue = substr($this->userPasswdQueue, 4);
603
+ $userLogin = $wfdb->querySingle("select user_login from $wpdb->users where ID=%s", $userID);
604
+ if(! $userLogin){
605
+ wordfence::status(2, 'error', "Could not get username for user with ID $userID when checking password strenght.");
606
+ continue;
607
+ }
608
+ wordfence::status(4, 'info', "Checking password strength for user $userLogin with ID $userID");
609
+ if($this->scanUserPassword($userID)){
610
+ $this->passwdHasIssues = true;
611
+ }
612
+ $this->forkIfNeeded();
613
}
614
+ }
615
+ private function scan_passwds_finish(){
616
+ wordfence::statusEnd($this->statusIDX['passwds'], $this->passwdHasIssues);
617
}
618
public function scanUserPassword($userID){
619
require_once( ABSPATH . 'wp-includes/class-phpass.php');
620
$passwdHasher = new PasswordHash(8, TRUE);
621
$userDat = get_userdata($userID);
622
+ $this->status(4, 'info', "Checking password strength of user '" . $userDat->user_login . "'");
623
$shortMsg = "";
624
$longMsg = "";
625
$level = 1;
652
break;
653
}
654
}
655
+ $this->status(4, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
656
return $haveIssue;
657
}
658
/*
886
throw new Exception("Scan was killed on administrator request.");
887
}
888
}
889
+ private static function getOwnHostname(){
890
+ if(preg_match('/https?:\/\/([^\/]+)/i', site_url(), $matches)){
891
+ $host = $matches[1];
892
+ } else {
893
+ wordfence::status(2, 'error', "Warning: Could not extract hostname from site URL: " . site_url());
894
+ $host = site_url();
895
+ }
896
+ return $host;
897
+ }
898
+ private static function tryCronURL(){
899
+ if(! wfConfig::get('cronTestID')){
900
+ wfConfig::set('cronTestID', wfUtils::bigRandomHex());
901
+ }
902
+ $URL = wfConfig::get('cronURL');
903
+ $sendHeader = wfConfig::get('cronSendHeader');
904
+ $opts = array(
905
+ 'timeout' => 30, //Long timeout here which is fine because it should return immediately if there are no delays.
906
+ 'blocking' => true,
907
+ 'sslverify' => false
908
+ );
909
+ if($sendHeader){
910
+ $host = self::getOwnHostname();
911
+ $opts['headers'] = array( 'Host' => $host);
912
+ }
913
+ wordfence::status(4, 'info', "Starting HTTP connection to test if we can kick off a scan");
914
+ $result = wp_remote_post($URL . '?test=1', $opts);
915
+ wordfence::status(4, 'info', "Done test HTTP connection");
916
+ if( is_array($result) && isset($result['body']) && preg_match('/WFCRONTESTOK:' . wfConfig::get('cronTestID') . '/', $result['body'])){
917
+ return true;
918
+ }
919
+ return false;
920
+ }
921
+ private static function detectCronURL(){
922
+ $URL = wfConfig::get('cronURL');
923
+ if($URL){
924
+ if(self::tryCronURL()){
925
+ return true;
926
+ }
927
+ }
928
+
929
+ $host = self::getOwnHostname();
930
+ $URLS = array();
931
+ $URLS[] = array(false, plugins_url('wordfence/wfscan.php'));
932
+ $URLS[] = array(true, preg_replace('/^https?:\/\/[^\/]+/i', 'http://127.0.0.1', $URLS[0][1]));
933
+ $URLS[] = array(true, preg_replace('/^https?:\/\/[^\/]+/i', 'https://127.0.0.1', $URLS[0][1]));
934
+ $withHostInsecure = 'http://' . $host . '/wp-content/plugins/wordfence/wfscan.php';
935
+ $withHostSecure = 'https://' . $host . '/wp-content/plugins/wordfence/wfscan.php';
936
+ if($URLS[0][1] != $withHostInsecure){
937
+ $URLS[] = array(false, $withHostInsecure);
938
+ }
939
+ if($URLS[0][1] != $withHostSecure){
940
+ $URLS[] = array(false, $withHostSecure);
941
+ }
942
+ foreach($URLS as $elem){
943
+ wfConfig::set('cronSendHeader', $elem[0] ? 1 : 0);
944
+ wfConfig::set('cronURL', $elem[1]);
945
+ if(self::tryCronURL()){
946
+ return true;
947
+ }
948
+ }
949
+ return false;
950
+ }
951
public static function startScan($isFork = false){
952
if(! $isFork){ //beginning of scan
953
wfConfig::set('wfKillRequested', 0);
955
if(wfUtils::isScanRunning()){
956
return "A scan is already running. Use the kill link if you would like to terminate the current scan.";
957
}
958
+ if(! self::detectCronURL()){
959
+ return "We could not determine how this WordPress server connects to itself. You can try asking your WordPress host to edit their hosts file and add the hostname for this machine to the file. This machine's hostname is: " . self::getOwnHostname();
960
+ }
961
}
962
963
$cronKey = wfUtils::bigRandomHex();
964
wfConfig::set('currentCronKey', time() . ',' . $cronKey);
965
+ $cronURL = wfConfig::get('cronURL') . '?isFork=' . ($isFork ? '1' : '0') . '&cronKey=' . $cronKey;
966
+ wordfence::status(4, 'info', "Starting cron at URL $cronURL");
967
+ $headers = array();
968
+ if(wfConfig::get('cronSendHeader')){
969
+ $headers['Host'] = self::getOwnHostname();
970
+ }
971
+ wordfence::status(4, 'info', "Starting wp_remote_post");
972
+ if($isFork){
973
+ $timeout = 8; //2 seconds shorter than max execution time which ensures that only 2 HTTP processes are ever occupied
974
} else {
975
+ $timeout = 3; //3 seconds if we're kicking off the scan so that the Ajax call returns quickly and UI isn't too slow
976
}
977
+ $result = wp_remote_post( $cronURL, array(
978
+ 'timeout' => $timeout, //Must be less than max execution time or more than 2 HTTP children will be occupied by scan
979
+ 'blocking' => true, //Non-blocking seems to block anyway, so we use blocking
980
+ 'sslverify' => false,
981
+ 'headers' => $headers
982
+ ) );
983
+ wordfence::status(4, 'info', "Scan process ended after forking.");
984
+ return false; //No error
985
}
986
public function processResponse($result){
987
return false;
988
}
989
}
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
Requires at least: 3.3.1
5
Tested up to: 3.4.1
6
- Stable tag: 3.0.6
7
8
Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
@@ -152,6 +152,14 @@ or a theme, because often these have been updated to fix a security hole.
152
5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
154
== Changelog ==
155
= 3.0.6 =
156
* Improved malware and phishing URL detection.
157
* Upgraded to Wordfence API version 1.9
3
Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
Requires at least: 3.3.1
5
Tested up to: 3.4.1
6
+ Stable tag: 3.0.7
7
8
Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
152
5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
154
== Changelog ==
155
+ = 3.0.7 =
156
+ * Fixed bug that caused scan to loop, stop halfway or not start for many sites.
157
+ * Fix bug that caused scan to not start on sites with thousands (over 20,000 in one case) users.
158
+ * Scan start is now faster for sites with large numbers of users.
159
+ * Fix bug that caused scan to get killed when checking passwords on sites with thousands of users.
160
+ * Wordfence now intelligently determines how to do a loopback request to kick off a scan.
161
+ * Scan is no longer called with a cron key in HTTP header but uses a query string value to authenticate itself which is more reliable.
162
+
163
= 3.0.6 =
164
* Improved malware and phishing URL detection.
165
* Upgraded to Wordfence API version 1.9
wfscan.php CHANGED
@@ -34,58 +34,53 @@ class wfScan {
34
if(! wordfence::wfSchemaExists()){
35
self::errorExit("Looks like the Wordfence database tables have been deleted. You can fix this by de-activating and re-activating the Wordfence plugin from your Plugins menu.");
36
}
37
- if(wfUtils::isAdmin() && $_GET['debugMode'] == '1'){
38
- header('Content-type: text/plain');
39
- wordfence::status(1, 'info', "Running in debug mode and writing directly to browser.");
40
- if(! wp_verify_nonce($_GET['nonce'], 'wp-ajax')){
41
- echo("The security key (nonce) provided for this debug scan is invalid. Please close this window, refresh your options page and try again.");
42
- exit();
43
- }
44
- self::$debugMode = true;
45
- wordfence::$printStatus = true;
46
- } else {
47
- wordfence::status(4, 'info', "Scan engine received request.");
48
- wordfence::status(4, 'info', "Checking cronkey header");
49
- if(! $_SERVER['HTTP_X_WORDFENCE_CRONKEY']){
50
- wordfence::status(2, 'error', "Wordfence wfscan.php accessed directly, or WF did not receive the secure HTTP header.");
51
- echo "If you see this message it means Wordfence is working correctly. You should not access this URL directly. It is part of the Wordfence security plugin and is designed for internal use only.";
52
- exit();
53
- }
54
- wordfence::status(4, 'info', "Fetching stored cronkey for comparison.");
55
- $currentCronKey = wfConfig::get('currentCronKey', false);
56
- if(! $currentCronKey){
57
- self::errorExit("Wordfence could not find a saved cron key to start the scan.");
58
- }
59
-
60
- wordfence::status(4, 'info', "Exploding stored cronkey");
61
- $savedKey = explode(',',$currentCronKey);
62
- if(time() - $savedKey[0] > 86400){
63
- self::errorExit("The key used to start a scan expired. The value is: " . $savedKey[0] . " and split is: " . $currentCronKey . " and time is: " . time());
64
- } //keys only last 60 seconds and are used within milliseconds of creation
65
- wordfence::status(4, 'info', "Checking saved cronkey against cronkey header");
66
- if($savedKey[1] != $_SERVER['HTTP_X_WORDFENCE_CRONKEY']){
67
- self::errorExit("Wordfence could not start a scan because the cron key does not match the saved key.");
68
- }
69
- wordfence::status(4, 'info', "Deleting stored cronkey");
70
- wfConfig::set('currentCronKey', '');
71
}
72
73
ini_set('max_execution_time', 1800); //30 mins
74
- wordfence::status(4, 'info', "Becoming admin for scan");
75
self::becomeAdmin();
76
- wordfence::status(4, 'info', "Done become admin");
77
78
$isFork = ($_GET['isFork'] == '1' ? true : false);
79
80
if(! $isFork){
81
- wordfence::status(4, 'info', "Checking if scan is already running");
82
if(! wfUtils::getScanLock()){
83
self::errorExit("There is already a scan running.");
84
}
85
}
86
- wordfence::status(4, 'info', "Requesting max memory");
87
wfUtils::requestMaxMemory();
88
- wordfence::status(4, 'info', "Setting up error handling environment");
89
set_error_handler('wfScan::error_handler', E_ALL);
90
register_shutdown_function('wfScan::shutdown');
91
if(! self::$debugMode){
@@ -93,15 +88,15 @@ class wfScan {
93
}
94
@error_reporting(E_ALL);
95
@ini_set('display_errors','On');
96
- wordfence::status(4, 'info', "Setting up scanRunning and starting scan");
97
$scan = false;
98
if($isFork){
99
$scan = wfConfig::get_ser('wfsd_engine', false, true);
100
if($scan){
101
- wordfence::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
102
wfConfig::set('wfsd_engine', '', true);
103
} else {
104
- wordfence::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
105
wfConfig::set('wfsd_engine', '', true);
106
exit();
107
}
@@ -113,13 +108,13 @@ class wfScan {
113
$scan->go();
114
} catch (Exception $e){
115
wfUtils::clearScanLock();
116
- wordfence::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
117
- wordfence::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
118
exit();
119
}
120
wfUtils::clearScanLock();
121
self::logPeakMemory();
122
- wordfence::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
123
}
124
private static function logPeakMemory(){
125
$oldPeak = wfConfig::get('wfPeakMemory', 0);
@@ -133,7 +128,7 @@ class wfScan {
133
$buf = substr($buf, 0, 255);
134
}
135
if(empty($buf) === false && preg_match('/[a-zA-Z0-9]+/', $buf)){
136
- wordfence::status(1, 'error', $buf);
137
}
138
}
139
public static function error_handler($errno, $errstr, $errfile, $errline){
@@ -143,41 +138,35 @@ class wfScan {
143
} else {
144
$level = 4; //It's someone elses plugin so only show if debug is enabled
145
}
146
- wordfence::status($level, 'error', "$errstr ($errno) File: $errfile Line: $errline");
147
}
148
}
149
public static function shutdown(){
150
self::logPeakMemory();
151
}
152
private static function errorExit($msg){
153
- echo json_encode(array('errorMsg' => $msg));
154
exit();
155
}
156
public static function becomeAdmin(){
157
- wordfence::status('4', 'info', "Starting become admin");
158
global $wpdb;
159
- wordfence::status('4', 'info', "About to query");
160
- $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
161
- wordfence::status('4', 'info', "Done query");
162
- $users = array();
163
- foreach($ws as $user){
164
- wordfence::status('4', 'info', "Processing user");
165
- $userDat = get_userdata($user->ID);
166
- array_push($users, array(
167
- 'id' => $user->ID,
168
- 'user_login' => $user->user_login,
169
- 'level' => $userDat->user_level
170
- ));
171
- }
172
- wordfence::status('4', 'info', "Done users and about to sort");
173
- usort($users, 'wfScan::usort');
174
- wordfence::status('4', 'info', "Done sort and setting user");
175
- wp_set_current_user($users[0]['id'], $users[0]['user_login']);
176
- wordfence::status('4', 'info', "Done setting user");
177
}
178
- public static function usort($b, $a){
179
- if($a['level'] == $b['level']){ return 0; }
180
- return ($a['level'] < $b['level']) ? -1 : 1;
181
}
182
}
183
wfScan::wfScanMain();
34
if(! wordfence::wfSchemaExists()){
35
self::errorExit("Looks like the Wordfence database tables have been deleted. You can fix this by de-activating and re-activating the Wordfence plugin from your Plugins menu.");
36
}
37
+ if($_GET['test'] == '1'){
38
+ echo "WFCRONTESTOK:" . wfConfig::get('cronTestID');
39
+ self::status(4, 'info', "Cron test received and message printed");
40
+ exit();
41
}
42
+ /* ----------Starting cronkey check -------- */
43
+ self::status(4, 'info', "Scan engine received request.");
44
+ self::status(4, 'info', "Checking cronkey");
45
+ if(! $_GET['cronKey']){
46
+ self::status(4, 'error', "Wordfence wfscan.php accessed directly, or WF did not receive a cronkey.");
47
+ echo "If you see this message it means Wordfence is working correctly. You should not access this URL directly. It is part of the Wordfence security plugin and is designed for internal use only.";
48
+ exit();
49
+ }
50
+ self::status(4, 'info', "Fetching stored cronkey for comparison.");
51
+ $currentCronKey = wfConfig::get('currentCronKey', false);
52
+ wfConfig::set('currentCronKey', '');
53
+ if(! $currentCronKey){
54
+ wordfence::status(4, 'error', "Wordfence could not find a saved cron key to start the scan so assuming it started and exiting.");
55
+ exit();
56
+ }
57
+ self::status(4, 'info', "Exploding stored cronkey");
58
+ $savedKey = explode(',',$currentCronKey);
59
+ if(time() - $savedKey[0] > 86400){
60
+ self::errorExit("The key used to start a scan expired. The value is: " . $savedKey[0] . " and split is: " . $currentCronKey . " and time is: " . time());
61
+ } //keys only last 60 seconds and are used within milliseconds of creation
62
+ self::status(4, 'info', "Checking saved cronkey against cronkey param");
63
+ if($savedKey[1] != $_GET['cronKey']){
64
+ self::errorExit("Wordfence could not start a scan because the cron key does not match the saved key.");
65
+ }
66
+ /* --------- end cronkey check ---------- */
67
68
ini_set('max_execution_time', 1800); //30 mins
69
+ self::status(4, 'info', "Becoming admin for scan");
70
self::becomeAdmin();
71
+ self::status(4, 'info', "Done become admin");
72
73
$isFork = ($_GET['isFork'] == '1' ? true : false);
74
75
if(! $isFork){
76
+ self::status(4, 'info', "Checking if scan is already running");
77
if(! wfUtils::getScanLock()){
78
self::errorExit("There is already a scan running.");
79
}
80
}
81
+ self::status(4, 'info', "Requesting max memory");
82
wfUtils::requestMaxMemory();
83
+ self::status(4, 'info', "Setting up error handling environment");
84
set_error_handler('wfScan::error_handler', E_ALL);
85
register_shutdown_function('wfScan::shutdown');
86
if(! self::$debugMode){
88
}
89
@error_reporting(E_ALL);
90
@ini_set('display_errors','On');
91
+ self::status(4, 'info', "Setting up scanRunning and starting scan");
92
$scan = false;
93
if($isFork){
94
$scan = wfConfig::get_ser('wfsd_engine', false, true);
95
if($scan){
96
+ self::status(4, 'info', "Got a true deserialized value back from 'wfsd_engine' with type: " . gettype($scan));
97
wfConfig::set('wfsd_engine', '', true);
98
} else {
99
+ self::status(2, 'error', "Scan can't continue - stored data not found after a fork. Got type: " . gettype($scan));
100
wfConfig::set('wfsd_engine', '', true);
101
exit();
102
}
108
$scan->go();
109
} catch (Exception $e){
110
wfUtils::clearScanLock();
111
+ self::status(2, 'error', "Scan terminated with error: " . $e->getMessage());
112
+ self::status(10, 'info', "SUM_KILLED:Previous scan terminated with an error. See below.");
113
exit();
114
}
115
wfUtils::clearScanLock();
116
self::logPeakMemory();
117
+ self::status(2, 'info', "Wordfence used " . sprintf('%.2f', (wfConfig::get('wfPeakMemory') - self::$peakMemAtStart) / 1024 / 1024) . "MB of memory for scan. Server peak memory usage was: " . sprintf('%.2f', wfConfig::get('wfPeakMemory') / 1024 / 1024) . "MB");
118
}
119
private static function logPeakMemory(){
120
$oldPeak = wfConfig::get('wfPeakMemory', 0);
128
$buf = substr($buf, 0, 255);
129
}
130
if(empty($buf) === false && preg_match('/[a-zA-Z0-9]+/', $buf)){
131
+ self::status(1, 'error', $buf);
132
}
133
}
134
public static function error_handler($errno, $errstr, $errfile, $errline){
138
} else {
139
$level = 4; //It's someone elses plugin so only show if debug is enabled
140
}
141
+ self::status($level, 'error', "$errstr ($errno) File: $errfile Line: $errline");
142
}
143
}
144
public static function shutdown(){
145
self::logPeakMemory();
146
}
147
private static function errorExit($msg){
148
+ wordfence::status(1, 'error', "Scan Engine Error: $msg");
149
exit();
150
}
151
public static function becomeAdmin(){
152
+ $db = new wfDB();
153
global $wpdb;
154
+ $adminUserID = $db->querySingle("select user_id from " . $wpdb->usermeta . " where meta_key='wp_user_level' order by meta_value desc, user_id asc limit 1");
155
+ if(! $adminUserID){
156
+ self::status(1, 'error', "Could not get the administrator's user ID. Scan can't continue.");
157
+ exit();
158
+ }
159
+ $adminUsername = $db->querySingle("select user_nicename from " . $wpdb->users . " where ID=%d", $adminUserID);
160
+ self::status(4, 'info', "Scan will run as admin user '$adminUsername' with ID '$adminUserID'");
161
+ wp_set_current_user($adminUserID);
162
+ if(! is_user_logged_in()){
163
+ self::status(1, 'error', "Scan could not sign in as user '$adminUsername' with ID '$adminUserID'. Scan can't continue.");
164
+ exit();
165
+ }
166
+ self::status(4, 'info', "Scan authentication complete.");
167
}
168
+ private static function status($level, $type, $msg){
169
+ wordfence::status($level, $type, $msg);
170
}
171
}
172
wfScan::wfScanMain();
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
Plugin URI: http://wordfence.com/
5
Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
Author: Mark Maunder
7
- Version: 3.0.6
8
Author URI: http://wordfence.com/
9
*/
10
- define('WORDFENCE_VERSION', '3.0.6');
11
if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
if((int) @ini_get('memory_limit') < 64){
13
@ini_set('memory_limit', '64M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.
4
Plugin URI: http://wordfence.com/
5
Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
Author: Mark Maunder
7
+ Version: 3.0.7
8
Author URI: http://wordfence.com/
9
*/
10
+ define('WORDFENCE_VERSION', '3.0.7');
11
if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
if((int) @ini_get('memory_limit') < 64){
13
@ini_set('memory_limit', '64M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.