Wordfence Security – Firewall & Malware Scan - Version 6.3.20

Version Description

  • Improvement: The scan will now alert for a publicly visible .user.ini file.
  • Fix: Fixed status code and human/bot tagging of block hit entries for live traffic and the Wordfence Security Network.
  • Fix: Added internal throttling to ensure the daily cron does not run too frequently on some hosts.
Download this release

Release Info

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

Code changes from version 6.3.19 to 6.3.20

lib/wfScanEngine.php CHANGED
@@ -611,7 +611,7 @@ class wfScanEngine {
611
  $status = wfIssues::statusStart("Check for publicly accessible configuration files, backup files and logs");
612
 
613
  $backupFileTests = array(
614
- // wfCommonBackupFileTest::createFromRootPath('.user.ini'),
615
  // wfCommonBackupFileTest::createFromRootPath('.htaccess'),
616
  wfCommonBackupFileTest::createFromRootPath('wp-config.php.bak'),
617
  wfCommonBackupFileTest::createFromRootPath('wp-config.php.swo'),
@@ -637,15 +637,17 @@ class wfScanEngine {
637
  )),
638
  );
639
  $backupFileTests = array_merge($backupFileTests, wfCommonBackupFileTest::createAllForFile('searchreplacedb2.php', wfCommonBackupFileTest::MATCH_REGEX, '/<title>Search and replace DB/i'));
640
- // $userIniFilename = ini_get('user_ini.filename');
641
- // if ($userIniFilename && $userIniFilename !== '.user.ini') {
642
- // $backupFileTests[] = wfCommonBackupFileTest::createFromRootPath($userIniFilename);
643
- // }
 
644
 
645
 
646
  /** @var wfCommonBackupFileTest $test */
647
  foreach ($backupFileTests as $test) {
648
  $pathFromRoot = (strpos($test->getPath(), ABSPATH) === 0) ? substr($test->getPath(), strlen(ABSPATH)) : $test->getPath();
 
649
  if ($test->fileExists() && $test->isPubliclyAccessible()) {
650
  $key = "configReadable" . bin2hex($test->getUrl());
651
  $added = $this->addIssue(
@@ -654,9 +656,7 @@ class wfScanEngine {
654
  $key,
655
  $key,
656
  'Publicly accessible config, backup, or log file found: ' . esc_html($pathFromRoot),
657
- '<a href="' . $test->getUrl() . '" target="_blank" rel="noopener noreferrer">' . $test->getUrl() . '</a> is publicly
658
- accessible and may expose sensitive information about your site or allow administrative functions to be performed by anyone. Files such as this one are commonly
659
- checked for by both attackers and scanners such as WPScan and should be removed or made inaccessible.',
660
  array(
661
  'url' => $test->getUrl(),
662
  'file' => $pathFromRoot,
@@ -874,9 +874,7 @@ class wfScanEngine {
874
  $key,
875
  $key,
876
  'Publicly accessible quarantined file found: ' . esc_html($file),
877
- '<a href="' . $test->getUrl() . '" target="_blank" rel="noopener noreferrer">' . $test->getUrl() . '</a> is publicly
878
- accessible and may expose source code or sensitive information about your site. Files such as this one are commonly
879
- checked for by scanners and should be removed or made inaccessible.',
880
  array(
881
  'url' => $test->getUrl(),
882
  'file' => $file,
611
  $status = wfIssues::statusStart("Check for publicly accessible configuration files, backup files and logs");
612
 
613
  $backupFileTests = array(
614
+ wfCommonBackupFileTest::createFromRootPath('.user.ini'),
615
  // wfCommonBackupFileTest::createFromRootPath('.htaccess'),
616
  wfCommonBackupFileTest::createFromRootPath('wp-config.php.bak'),
617
  wfCommonBackupFileTest::createFromRootPath('wp-config.php.swo'),
637
  )),
638
  );
639
  $backupFileTests = array_merge($backupFileTests, wfCommonBackupFileTest::createAllForFile('searchreplacedb2.php', wfCommonBackupFileTest::MATCH_REGEX, '/<title>Search and replace DB/i'));
640
+
641
+ $userIniFilename = ini_get('user_ini.filename');
642
+ if ($userIniFilename && $userIniFilename !== '.user.ini') {
643
+ $backupFileTests[] = wfCommonBackupFileTest::createFromRootPath('.user.ini');
644
+ }
645
 
646
 
647
  /** @var wfCommonBackupFileTest $test */
648
  foreach ($backupFileTests as $test) {
649
  $pathFromRoot = (strpos($test->getPath(), ABSPATH) === 0) ? substr($test->getPath(), strlen(ABSPATH)) : $test->getPath();
650
+ wordfence::status(4, 'info', "Testing {$pathFromRoot}");
651
  if ($test->fileExists() && $test->isPubliclyAccessible()) {
652
  $key = "configReadable" . bin2hex($test->getUrl());
653
  $added = $this->addIssue(
656
  $key,
657
  $key,
658
  'Publicly accessible config, backup, or log file found: ' . esc_html($pathFromRoot),
659
+ '<a href="' . $test->getUrl() . '" target="_blank" rel="noopener noreferrer">' . $test->getUrl() . '</a> is publicly accessible and may expose source code or sensitive information about your site. Files such as this one are commonly checked for by scanners and should be made inaccessible. Alternately, some can be removed if you are certain your site does not need them. Sites using the nginx web server may need manual configuration changes to protect such files. <a href="https://docs.wordfence.com/en/Understanding_scan_results#Publicly_accessible_config_backup_or_log_file_found" target="_blank" rel="noopener noreferrer">Learn more</a>',
 
 
660
  array(
661
  'url' => $test->getUrl(),
662
  'file' => $pathFromRoot,
874
  $key,
875
  $key,
876
  'Publicly accessible quarantined file found: ' . esc_html($file),
877
+ '<a href="' . $test->getUrl() . '" target="_blank" rel="noopener noreferrer">' . $test->getUrl() . '</a> is publicly accessible and may expose source code or sensitive information about your site. Files such as this one are commonly checked for by scanners and should be removed or made inaccessible.',
 
 
878
  array(
879
  'url' => $test->getUrl(),
880
  'file' => $file,
lib/wordfenceClass.php CHANGED
@@ -165,7 +165,14 @@ class wordfence {
165
  private static function keyAlert($msg){
166
  self::alert($msg, $msg . " To ensure uninterrupted Premium Wordfence protection on your site,\nplease renew your API key by visiting http://www.wordfence.com/ Sign in, go to your dashboard,\nselect the key about to expire and click the button to renew that API key.", false);
167
  }
168
- public static function dailyCron(){
 
 
 
 
 
 
 
169
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
170
  try {
171
  $keyData = $api->call('ping_api_key');
@@ -748,6 +755,12 @@ SQL
748
  wfConfig::set('fileContentsGSB6315Migration', 1);
749
  }
750
 
 
 
 
 
 
 
751
 
752
  //Check the How does Wordfence get IPs setting
753
  wfUtils::requestDetectProxyCallback();
@@ -818,13 +831,13 @@ SQL
818
  if($blog_id == 1 && get_option('wordfenceActivated') != 1){ return; } //Because the plugin is active once installed, even before it's network activated, for site 1 (WordPress team, why?!)
819
  }
820
  //User may be logged in or not, so register both handlers
821
- add_action('wp_ajax_nopriv_wordfence_logHuman', 'wordfence::ajax_logHuman_callback');
822
  add_action('wp_ajax_nopriv_wordfence_doScan', 'wordfence::ajax_doScan_callback');
823
  add_action('wp_ajax_nopriv_wordfence_testAjax', 'wordfence::ajax_testAjax_callback');
824
  add_action('wp_ajax_nopriv_wordfence_perfLog', 'wordfence::ajax_perfLog_callback');
825
  if(wfUtils::hasLoginCookie()){ //may be logged in. Fast way to check. These aren't secure functions, this is just a perf optimization, along with every other use of hasLoginCookie()
826
  add_action('wp_ajax_wordfence_perfLog', 'wordfence::ajax_perfLog_callback');
827
- add_action('wp_ajax_wordfence_logHuman', 'wordfence::ajax_logHuman_callback');
828
  add_action('wp_ajax_wordfence_doScan', 'wordfence::ajax_doScan_callback');
829
  add_action('wp_ajax_wordfence_testAjax', 'wordfence::ajax_testAjax_callback');
830
 
@@ -1069,7 +1082,7 @@ SQL
1069
  $wfLog->logPerf(wfUtils::getIP(), $UA, $URL, $data);
1070
  die(json_encode(array('ok' => 1)));
1071
  }
1072
- public static function ajax_logHuman_callback(){
1073
  self::getLog()->canLogHit = false;
1074
  $UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
1075
  $isCrawler = false;
@@ -4388,8 +4401,8 @@ HTACCESS;
4388
  wfScanEngine::startScan();
4389
  }
4390
  public static function templateRedir(){
4391
- if (!empty($_GET['wordfence_logHuman'])) {
4392
- self::ajax_logHuman_callback();
4393
  exit;
4394
  }
4395
 
@@ -4557,7 +4570,7 @@ EOL;
4557
 
4558
  self::$hitID = self::getLog()->logHit();
4559
  if (self::$hitID) {
4560
- $URL = home_url('/?wordfence_logHuman=1&hid=' . wfUtils::encrypt(self::$hitID));
4561
  $URL = addslashes(preg_replace('/^https?:/i', '', $URL));
4562
  #Load as external script async so we don't slow page down.
4563
  echo <<<HTML
@@ -6906,13 +6919,47 @@ to your httpd.conf if using Apache, or find documentation on how to disable dire
6906
  global $wpdb;
6907
  $p = $wpdb->base_prefix;
6908
  $wfdb = new wfDB();
 
6909
  $count = $wfdb->querySingle("select count(*) as cnt from $p"."wfHits");
6910
  $liveTrafficMaxRows = absint(wfConfig::get('liveTraf_maxRows', 2000));
6911
  if ($count > $liveTrafficMaxRows * 10) {
 
6912
  $wfdb->truncate($p . "wfHits"); //So we don't slow down sites that have very large wfHits tables
6913
- } else if ($count > $liveTrafficMaxRows) {
 
 
6914
  $wfdb->queryWrite("delete from $p" . "wfHits order by id asc limit %d", ($count - $liveTrafficMaxRows) + ($liveTrafficMaxRows * .2));
6915
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6916
  }
6917
 
6918
  private static function scheduleSendAttackData($timeToSend = null) {
@@ -7139,6 +7186,7 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7139
  public static function syncAttackData($exit = true) {
7140
  global $wpdb;
7141
  if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
 
7142
  $waf = wfWAF::getInstance();
7143
  $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
7144
  if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
@@ -7155,10 +7203,11 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7155
  if ($logTimeMicroseconds <= $lastAttackMicroseconds || $learningMode) {
7156
  continue;
7157
  }
 
 
7158
 
7159
  $hit = new wfRequestModel();
7160
  $hit->attackLogTime = $logTimeMicroseconds;
7161
- $hit->statusCode = 403;
7162
  $hit->ctime = $requestTime;
7163
  $hit->IP = wfUtils::inet_pton($ip);
7164
 
@@ -7175,19 +7224,23 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7175
  $hit->URL = 'http' . ($ssl ? 's' : '') . '://' . trim($hostMatches[1]) . trim($uriMatches[1]);
7176
  }
7177
 
 
7178
  if (preg_match('/cookie:(.*?)\n/i', $requestString, $matches)) {
7179
  $hit->newVisit = strpos($matches[1], 'wfvt_' . crc32(site_url())) !== false ? 1 : 0;
7180
  $hasVerifiedHumanCookie = strpos($matches[1], 'wordfence_verifiedHuman') !== false;
7181
  if ($hasVerifiedHumanCookie && preg_match('/wordfence_verifiedHuman=(.*?);/', $matches[1], $cookieMatches)) {
7182
- $hit->jsRun = (int) wp_verify_nonce($cookieMatches[1], 'wordfence_verifiedHuman' . $hit->UA . $ip);
 
7183
  }
7184
 
7185
- $hasLoginCookie = strpos($matches[1], $ssl ? SECURE_AUTH_COOKIE : AUTH_COOKIE) !== false;
7186
- if ($hasLoginCookie && preg_match('/' . ($ssl ? SECURE_AUTH_COOKIE : AUTH_COOKIE) . '=(.*?);/', $matches[1], $cookieMatches)) {
 
7187
  $authCookie = rawurldecode($cookieMatches[1]);
7188
- $authID = $ssl ? wp_validate_auth_cookie($authCookie, 'secure_auth') : wp_validate_auth_cookie($authCookie, 'auth');
7189
- if ($authID) {
7190
- $hit->userID = $authID;
 
7191
  }
7192
  }
7193
  }
@@ -7278,9 +7331,11 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7278
  //Do nothing
7279
  }
7280
  }
 
7281
  $hit->actionDescription = $actionDescription;
7282
  }
7283
  else if ($failedRules == 'logged') {
 
7284
  $hit->action = 'logged:waf';
7285
  }
7286
  else { // Blocked by the WAF but would've been blocked anyway by the plugin settings so that message takes priority
@@ -7290,6 +7345,7 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7290
  }
7291
  else {
7292
  if ($failedRules == 'logged') {
 
7293
  $hit->action = 'logged:waf';
7294
  }
7295
  else {
@@ -7337,6 +7393,7 @@ LIMIT %d", sprintf('%.6f', $lastSendTime), $limit));
7337
  }
7338
 
7339
  $hit->actionData = wfRequestModel::serializeActionData($actionData);
 
7340
  $hit->save();
7341
 
7342
  self::scheduleSendAttackData();
165
  private static function keyAlert($msg){
166
  self::alert($msg, $msg . " To ensure uninterrupted Premium Wordfence protection on your site,\nplease renew your API key by visiting http://www.wordfence.com/ Sign in, go to your dashboard,\nselect the key about to expire and click the button to renew that API key.", false);
167
  }
168
+ public static function dailyCron() {
169
+ $lastDailyCron = (int) wfConfig::get('lastDailyCron', 0);
170
+ if (($lastDailyCron + 43200) > time()) { //Run no more frequently than every 12 hours
171
+ return;
172
+ }
173
+
174
+ wfConfig::set('lastDailyCron', time());
175
+
176
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
177
  try {
178
  $keyData = $api->call('ping_api_key');
755
  wfConfig::set('fileContentsGSB6315Migration', 1);
756
  }
757
 
758
+ //6.3.20
759
+ $lastBlockAggregation = wfConfig::get('lastBlockAggregation', 0);
760
+ if ($lastBlockAggregation == 0) {
761
+ wfConfig::set('lastBlockAggregation', time());
762
+ }
763
+
764
 
765
  //Check the How does Wordfence get IPs setting
766
  wfUtils::requestDetectProxyCallback();
831
  if($blog_id == 1 && get_option('wordfenceActivated') != 1){ return; } //Because the plugin is active once installed, even before it's network activated, for site 1 (WordPress team, why?!)
832
  }
833
  //User may be logged in or not, so register both handlers
834
+ add_action('wp_ajax_nopriv_wordfence_lh', 'wordfence::ajax_lh_callback');
835
  add_action('wp_ajax_nopriv_wordfence_doScan', 'wordfence::ajax_doScan_callback');
836
  add_action('wp_ajax_nopriv_wordfence_testAjax', 'wordfence::ajax_testAjax_callback');
837
  add_action('wp_ajax_nopriv_wordfence_perfLog', 'wordfence::ajax_perfLog_callback');
838
  if(wfUtils::hasLoginCookie()){ //may be logged in. Fast way to check. These aren't secure functions, this is just a perf optimization, along with every other use of hasLoginCookie()
839
  add_action('wp_ajax_wordfence_perfLog', 'wordfence::ajax_perfLog_callback');
840
+ add_action('wp_ajax_wordfence_lh', 'wordfence::ajax_lh_callback');
841
  add_action('wp_ajax_wordfence_doScan', 'wordfence::ajax_doScan_callback');
842
  add_action('wp_ajax_wordfence_testAjax', 'wordfence::ajax_testAjax_callback');
843
 
1082
  $wfLog->logPerf(wfUtils::getIP(), $UA, $URL, $data);
1083
  die(json_encode(array('ok' => 1)));
1084
  }
1085
+ public static function ajax_lh_callback(){
1086
  self::getLog()->canLogHit = false;
1087
  $UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
1088
  $isCrawler = false;
4401
  wfScanEngine::startScan();
4402
  }
4403
  public static function templateRedir(){
4404
+ if (!empty($_GET['wordfence_lh'])) {
4405
+ self::ajax_lh_callback();
4406
  exit;
4407
  }
4408
 
4570
 
4571
  self::$hitID = self::getLog()->logHit();
4572
  if (self::$hitID) {
4573
+ $URL = home_url('/?wordfence_lh=1&hid=' . wfUtils::encrypt(self::$hitID));
4574
  $URL = addslashes(preg_replace('/^https?:/i', '', $URL));
4575
  #Load as external script async so we don't slow page down.
4576
  echo <<<HTML
6919
  global $wpdb;
6920
  $p = $wpdb->base_prefix;
6921
  $wfdb = new wfDB();
6922
+ $lastAggregation = wfConfig::get('lastBlockAggregation', 0);
6923
  $count = $wfdb->querySingle("select count(*) as cnt from $p"."wfHits");
6924
  $liveTrafficMaxRows = absint(wfConfig::get('liveTraf_maxRows', 2000));
6925
  if ($count > $liveTrafficMaxRows * 10) {
6926
+ self::_aggregateBlockStats($lastAggregation);
6927
  $wfdb->truncate($p . "wfHits"); //So we don't slow down sites that have very large wfHits tables
6928
+ }
6929
+ else if ($count > $liveTrafficMaxRows) {
6930
+ self::_aggregateBlockStats($lastAggregation);
6931
  $wfdb->queryWrite("delete from $p" . "wfHits order by id asc limit %d", ($count - $liveTrafficMaxRows) + ($liveTrafficMaxRows * .2));
6932
  }
6933
+ else if ($lastAggregation < (time() - 86400)) {
6934
+ self::_aggregateBlockStats($lastAggregation);
6935
+ }
6936
+ }
6937
+
6938
+ private static function _aggregateBlockStats($since = false) {
6939
+ global $wpdb;
6940
+
6941
+ if (!wfConfig::get('other_WFNet', true)) {
6942
+ return;
6943
+ }
6944
+
6945
+ if ($since === false) {
6946
+ $since = wfConfig::get('lastBlockAggregation', 0);
6947
+ }
6948
+
6949
+ $hitsTable = wfDB::networkPrefix() . 'wfHits';
6950
+ $query = $wpdb->prepare("SELECT COUNT(*) AS cnt, CASE WHEN (jsRun = 1 OR userID > 0) THEN 1 ELSE 0 END AS isHuman, statusCode FROM {$hitsTable} WHERE ctime > %d GROUP BY isHuman, statusCode", $since);
6951
+ $rows = $wpdb->get_results($query, ARRAY_A);
6952
+ if (count($rows)) {
6953
+ try {
6954
+ $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
6955
+ $api->call('aggregate_stats', array(), array('stats' => json_encode($rows)));
6956
+ }
6957
+ catch (Exception $e) {
6958
+ // Do nothing
6959
+ }
6960
+ }
6961
+
6962
+ wfConfig::set('lastBlockAggregation', time());
6963
  }
6964
 
6965
  private static function scheduleSendAttackData($timeToSend = null) {
7186
  public static function syncAttackData($exit = true) {
7187
  global $wpdb;
7188
  if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
7189
+ $log = self::getLog();
7190
  $waf = wfWAF::getInstance();
7191
  $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
7192
  if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
7203
  if ($logTimeMicroseconds <= $lastAttackMicroseconds || $learningMode) {
7204
  continue;
7205
  }
7206
+
7207
+ $statusCode = 403;
7208
 
7209
  $hit = new wfRequestModel();
7210
  $hit->attackLogTime = $logTimeMicroseconds;
 
7211
  $hit->ctime = $requestTime;
7212
  $hit->IP = wfUtils::inet_pton($ip);
7213
 
7224
  $hit->URL = 'http' . ($ssl ? 's' : '') . '://' . trim($hostMatches[1]) . trim($uriMatches[1]);
7225
  }
7226
 
7227
+ $isHuman = false;
7228
  if (preg_match('/cookie:(.*?)\n/i', $requestString, $matches)) {
7229
  $hit->newVisit = strpos($matches[1], 'wfvt_' . crc32(site_url())) !== false ? 1 : 0;
7230
  $hasVerifiedHumanCookie = strpos($matches[1], 'wordfence_verifiedHuman') !== false;
7231
  if ($hasVerifiedHumanCookie && preg_match('/wordfence_verifiedHuman=(.*?);/', $matches[1], $cookieMatches)) {
7232
+ $hit->jsRun = (int) $log->validateVerifiedHumanCookie($cookieMatches[1], $hit->UA, $ip);
7233
+ $isHuman = !!$hit->jsRun;
7234
  }
7235
 
7236
+ $authCookieName = $waf->getAuthCookieName();
7237
+ $hasLoginCookie = strpos($matches[1], $authCookieName) !== false;
7238
+ if ($hasLoginCookie && preg_match('/' . preg_quote($authCookieName) . '=(.*?);/', $matches[1], $cookieMatches)) {
7239
  $authCookie = rawurldecode($cookieMatches[1]);
7240
+ $decodedAuthCookie = $waf->parseAuthCookie($authCookie);
7241
+ if ($decodedAuthCookie !== false) {
7242
+ $hit->userID = $decodedAuthCookie['userID'];
7243
+ $isHuman = true;
7244
  }
7245
  }
7246
  }
7331
  //Do nothing
7332
  }
7333
  }
7334
+ $statusCode = 503;
7335
  $hit->actionDescription = $actionDescription;
7336
  }
7337
  else if ($failedRules == 'logged') {
7338
+ $statusCode = 200;
7339
  $hit->action = 'logged:waf';
7340
  }
7341
  else { // Blocked by the WAF but would've been blocked anyway by the plugin settings so that message takes priority
7345
  }
7346
  else {
7347
  if ($failedRules == 'logged') {
7348
+ $statusCode = 200;
7349
  $hit->action = 'logged:waf';
7350
  }
7351
  else {
7393
  }
7394
 
7395
  $hit->actionData = wfRequestModel::serializeActionData($actionData);
7396
+ $hit->statusCode = $statusCode;
7397
  $hit->save();
7398
 
7399
  self::scheduleSendAttackData();
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.8.2
6
- Stable tag: 6.3.19
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
@@ -160,6 +160,11 @@ Secure your website with Wordfence.
160
 
161
  == Changelog ==
162
 
 
 
 
 
 
163
  = 6.3.19 =
164
  * Emergency Fix: Updated wpdb::prepare calls using %.6f since it is no longer supported.
165
 
3
  Tags: security, secure, security plugin, wordpress security, login security, firewall, malware, antivirus, web application firewall, block hackers, country blocking
4
  Requires at least: 3.9
5
  Tested up to: 4.8.2
6
+ Stable tag: 6.3.20
7
 
8
  Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
9
 
160
 
161
  == Changelog ==
162
 
163
+ = 6.3.20 =
164
+ * Improvement: The scan will now alert for a publicly visible .user.ini file.
165
+ * Fix: Fixed status code and human/bot tagging of block hit entries for live traffic and the Wordfence Security Network.
166
+ * Fix: Added internal throttling to ensure the daily cron does not run too frequently on some hosts.
167
+
168
  = 6.3.19 =
169
  * Emergency Fix: Updated wpdb::prepare calls using %.6f since it is no longer supported.
170
 
waf/wfWAFIPBlocksController.php CHANGED
@@ -134,7 +134,7 @@ class wfWAFIPBlocksController
134
  return false;
135
  }
136
 
137
- $logHuman = $request->getQueryString('wordfence_logHuman');
138
  if ($logHuman !== null) {
139
  return false;
140
  }
134
  return false;
135
  }
136
 
137
+ $logHuman = $request->getQueryString('wordfence_lh');
138
  if ($logHuman !== null) {
139
  return false;
140
  }
wordfence.php CHANGED
@@ -4,14 +4,14 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
- Version: 6.3.19
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
- define('WORDFENCE_VERSION', '6.3.19');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17
 
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
6
  Author: Wordfence
7
+ Version: 6.3.20
8
  Author URI: http://www.wordfence.com/
9
  Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
+ define('WORDFENCE_VERSION', '6.3.20');
15
  define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
  basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17