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 | 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
- js/admin.js +1 -1
- lib/wfConfig.php +2 -3
- lib/wfScanEngine.php +128 -116
- readme.txt +9 -1
- wfscan.php +59 -70
- 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 |
-
},
|
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
|
377 |
-
|
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->
|
|
|
|
|
|
|
|
|
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
|
573 |
$this->statusIDX['passwds'] = wordfence::statusStart('Scanning for weak passwords');
|
574 |
global $wpdb;
|
575 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
576 |
$haveIssues = false;
|
577 |
-
|
578 |
-
$this->
|
579 |
-
$
|
580 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
581 |
}
|
582 |
-
|
|
|
|
|
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(
|
589 |
$shortMsg = "";
|
590 |
$longMsg = "";
|
591 |
$level = 1;
|
@@ -618,7 +652,7 @@ class wfScanEngine {
|
|
618 |
break;
|
619 |
}
|
620 |
}
|
621 |
-
$this->status(
|
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 |
-
|
870 |
-
|
871 |
-
|
872 |
-
|
873 |
-
'
|
874 |
-
|
875 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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(
|
38 |
-
|
39 |
-
|
40 |
-
|
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 |
-
|
75 |
self::becomeAdmin();
|
76 |
-
|
77 |
|
78 |
$isFork = ($_GET['isFork'] == '1' ? true : false);
|
79 |
|
80 |
if(! $isFork){
|
81 |
-
|
82 |
if(! wfUtils::getScanLock()){
|
83 |
self::errorExit("There is already a scan running.");
|
84 |
}
|
85 |
}
|
86 |
-
|
87 |
wfUtils::requestMaxMemory();
|
88 |
-
|
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 |
-
|
97 |
$scan = false;
|
98 |
if($isFork){
|
99 |
$scan = wfConfig::get_ser('wfsd_engine', false, true);
|
100 |
if($scan){
|
101 |
-
|
102 |
wfConfig::set('wfsd_engine', '', true);
|
103 |
} else {
|
104 |
-
|
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 |
-
|
117 |
-
|
118 |
exit();
|
119 |
}
|
120 |
wfUtils::clearScanLock();
|
121 |
self::logPeakMemory();
|
122 |
-
|
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 |
-
|
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 |
-
|
147 |
}
|
148 |
}
|
149 |
public static function shutdown(){
|
150 |
self::logPeakMemory();
|
151 |
}
|
152 |
private static function errorExit($msg){
|
153 |
-
|
154 |
exit();
|
155 |
}
|
156 |
public static function becomeAdmin(){
|
157 |
-
|
158 |
global $wpdb;
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
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 |
-
|
179 |
-
|
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.
|
8 |
Author URI: http://wordfence.com/
|
9 |
*/
|
10 |
-
define('WORDFENCE_VERSION', '3.0.
|
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.
|