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.