Merged in feature/MAW-855-import-code-into-aws (pull request #2)

code import from pantheon

* code import from pantheon
This commit is contained in:
Tony Volpe
2023-12-04 23:08:14 +00:00
parent 8c9b1312bc
commit 8f4b5efda6
4766 changed files with 185592 additions and 239967 deletions

View File

@@ -3068,6 +3068,23 @@
}
});
},
acknowledgeAdminUser: function (issueID) {
var self = this;
this.ajax('wordfence_acknowledgeAdminUser', {
issueID: issueID
}, function(res) {
if (res.ok) {
self.loadIssues(function() {
self.colorboxModal((self.isSmallScreen ? '300px' : '400px'), __("Successfully acknowledged admin"), sprintf(__("The admin user %s will no longer show up in future scans."), res.user_login));
});
} else if (res.errorMsg) {
self.loadIssues(function() {
WFAD.colorboxError(res.errorMsg, res.tokenInvalid);
});
}
});
},
windowHasFocus: function() {
if (typeof document.hasFocus === 'function') {

File diff suppressed because it is too large Load Diff

View File

@@ -237,6 +237,7 @@ echo wfView::create('scanner/site-cleaning-high-sense')->render();
echo wfView::create('scanner/site-cleaning-beta-sigs')->render();
echo wfView::create('scanner/no-issues')->render();
echo wfView::create('scanner/issue-wfUpgrade')->render();
echo wfView::create('scanner/issue-wfUpgradeError')->render();
echo wfView::create('scanner/issue-wfPluginUpgrade')->render();
echo wfView::create('scanner/issue-wfThemeUpgrade')->render();
echo wfView::create('scanner/issue-wfPluginRemoved')->render();

View File

@@ -113,14 +113,14 @@ if (!isset($sendingDiagnosticEmail)) {
))) ?></td>
<td>
<?php if ($infoOnly): ?>
<div class="wf-result-info"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-info"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php elseif ($result['test']): ?>
<div class="wf-result-success"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-success"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php else: ?>
<div class="wf-result-error"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-error"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php endif ?>
<?php if (isset($result['detail']) && !empty($result['detail'])): ?>
<p><strong><?php esc_html_e('Additional Detail', 'wordfence'); ?></strong><br><?php echo nl2br(esc_html($result['detail'])); ?></p>
<p><strong><?php esc_html_e('Additional Detail', 'wordfence'); ?></strong><br><?php echo (is_array($result['detail']) && isset($result['detail']['escaped']) ? $result['detail']['escaped'] : nl2br(esc_html($result['detail']))); ?></p>
<?php endif; ?>
</td>
</tr>
@@ -156,15 +156,15 @@ if (!isset($sendingDiagnosticEmail)) {
))) ?></div>
<div class="wf-right">
<?php if ($infoOnly): ?>
<div class="wf-result-info"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-info"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php elseif ($result['test']): ?>
<div class="wf-result-success"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-success"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php else: ?>
<div class="wf-result-error"><?php echo nl2br(esc_html($result['message'])); ?></div>
<div class="wf-result-error"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
<?php endif ?>
<?php if (isset($result['detail']) && !empty($result['detail'])): ?>
<p><a href="#" onclick="jQuery('#wf-diagnostics-detail-<?php echo esc_attr($key); ?>').show(); jQuery(this).hide(); return false;" role="button"><?php esc_html_e('View Additional Detail', 'wordfence'); ?></a></p>
<pre class="wf-pre wf-split-word" id="wf-diagnostics-detail-<?php echo esc_attr($key); ?>" style="max-width: 600px; display: none;"><?php echo esc_html($result['detail']); ?></pre>
<pre class="wf-pre wf-split-word" id="wf-diagnostics-detail-<?php echo esc_attr($key); ?>" style="max-width: 600px; display: none;"><?php echo (is_array($result['detail']) && isset($result['detail']['escaped']) ? $result['detail']['escaped'] : nl2br(esc_html($result['detail']))); ?></pre>
<?php endif; ?>
</div>
</li>

View File

@@ -37,6 +37,17 @@ class wfCentralAPIRequest {
$this->body = $body;
$this->args = $args;
}
/**
* Handles an internal error when making a Central API request (e.g., a second sodium_compat library with an
* incompatible interface loading instead or in addition to ours).
*
* @param Exception|Throwable $e
*/
public static function handleInternalCentralAPIError($e) {
error_log('Wordfence encountered an internal Central API error: ' . $e->getMessage());
error_log('Wordfence stack trace: ' . $e->getTraceAsString());
}
public function execute() {
$args = array(
@@ -259,7 +270,7 @@ class wfCentralAuthenticatedAPIRequest extends wfCentralAPIRequest {
continue;
}
}
if (empty($token)) {
if (empty($token)) {
if (isset($e)) {
throw $e;
} else {
@@ -406,9 +417,16 @@ class wfCentral {
try {
$response = $request->execute();
return $response;
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
error_log($e);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
return false;
}
@@ -437,9 +455,16 @@ class wfCentral {
try {
$response = $request->execute();
return $response;
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
error_log($e);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
return false;
}
@@ -459,9 +484,16 @@ class wfCentral {
try {
$response = $request->execute();
return $response;
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
error_log($e);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
return false;
}
@@ -484,9 +516,16 @@ class wfCentral {
try {
$response = $request->execute();
return $response;
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
error_log($e);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
return false;
}
@@ -501,9 +540,13 @@ class wfCentral {
try {
$request->execute();
} catch (Exception $e) {
}
catch (Exception $e) {
// We can safely ignore an error here for now.
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
}
protected static $syncConfig = true;
@@ -540,9 +583,16 @@ class wfCentral {
try {
$response = $request->execute();
return $response;
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
error_log($e);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
return false;
}
@@ -598,6 +648,14 @@ class wfCentral {
}
return false;
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
return false;
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
return false;
}
}
else {
$wfdb = new wfDB();

View File

@@ -699,7 +699,7 @@ class wfConfig {
global $wpdb;
$dbh = $wpdb->dbh;
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
$useMySQLi = wfUtils::useMySQLi();
if (!self::$tableExists) {
return;

View File

@@ -671,16 +671,21 @@ class wfDiagnostic
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . $result->get_error_message();
}
else {
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . $result['response']['code'] . " " . $result['response']['message'] . "\n";
$message .= __('This additional info may help you diagnose the issue. The response headers we received were:', 'wordfence') . "\n";
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . '<br>' . $result['response']['code'] . " " . $result['response']['message'] . '<br><br>';
if ($this->_detectBlockedByCloudflare($result)) {
$message .= __('Cloudflare appears to be blocking your site from connecting to itself.', 'wordfence') . '<br>' . sprintf(' <a href="%s" target="_blank" rel="noopener noreferrer">', wfSupportController::esc_supportURL(wfSupportController::ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK)) . __('Get help with Cloudflare compatibility', 'wordfence') . '</a><br><br>';
}
$message .= __('This additional info may help you diagnose the issue. The response headers we received were:', 'wordfence') . '<br><br>';
if (isset($result['http_response']) && is_object($result['http_response']) && method_exists($result['http_response'], 'get_response_object') && is_object($result['http_response']->get_response_object()) && property_exists($result['http_response']->get_response_object(), 'raw')) {
$detail = str_replace("\r\n", "\n", $result['http_response']->get_response_object()->raw);
}
}
$message = wp_kses($message, array('a' => array('href' => array(), 'target' => array(), 'rel' => array()), 'span' => array('class' => array()), 'em' => array(), 'code' => array(), 'br' => array()));
return array(
'test' => false,
'message' => $message,
'message' => array('escaped' => $message),
'detail' => $detail,
);
}
@@ -698,11 +703,14 @@ class wfDiagnostic
$handle = $interceptor->getHandle();
$errorNumber = curl_errno($handle);
if ($errorNumber === 6 /* COULDNT_RESOLVE_HOST */) {
$detail = sprintf(/* translators: error message from failed request */ __('This likely indicates that the server either does not support IPv6 or does not have an IPv6 address assigned or associated with the domain. Original error message: %s', 'wordfence'), is_array($result['message']) ? $result['message']['escaped'] : $result['message']);
$detail = wp_kses($detail, array('a' => array('href' => array(), 'target' => array(), 'rel' => array()), 'span' => array('class' => array()), 'em' => array(), 'code' => array(), 'br' => array()));
return array(
'test' => false,
'infoOnly' => true,
'message' => __('IPv6 DNS resolution failed', 'wordfence'),
'detail' => sprintf(/* translators: error message from failed request */ __('This likely indicates that the server either does not support IPv6 or does not have an IPv6 address assigned or associated with the domain. Original error message: %s', 'wordfence'), $result['message'])
'detail' => array('escaped' => $detail),
);
}
}
@@ -721,6 +729,34 @@ class wfDiagnostic
);
}
/**
* Looks for markers in $result that indicate it was challenged/blocked by Cloudflare.
*
* @param $result
* @return bool
*/
private function _detectBlockedByCloudflare($result) {
$headers = $result['headers'];
if (isset($headers['cf-mitigated']) && strtolower($headers['cf-mitigated']) == 'challenge' /* managed challenge */) { //$headers is an instance of Requests_Utility_CaseInsensitiveDictionary
return true;
}
$body = $result['body'];
$search = array(
'/cdn-cgi/styles/challenges.css', //managed challenge
'/cdn-cgi/challenge-platform', //managed challenge
'/cdn-cgi/styles/cf.errors.css', //block
'cf-error-details', //block
'Cloudflare Ray ID', //block
);
foreach ($search as $s) {
if (stripos($body, $s) !== false) {
return true;
}
}
return false;
}
public function serverIP() {
$serverIPs = wfUtils::serverIPs();
return array(

View File

@@ -78,13 +78,14 @@ class wfIssues {
'wfPluginRemoved' => wfIssues::SEVERITY_CRITICAL,
'wfPluginUpgrade' => wfIssues::SEVERITY_MEDIUM,
'wfThemeUpgrade' => wfIssues::SEVERITY_MEDIUM,
'wfUpgradeError' => wfIssues::SEVERITY_MEDIUM,
'wfUpgrade' => wfIssues::SEVERITY_HIGH,
'wpscan_directoryList' => wfIssues::SEVERITY_HIGH,
'wpscan_fullPathDiscl' => wfIssues::SEVERITY_HIGH,
);
public static function validIssueTypes() {
return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl', 'skippedPaths');
return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgradeError', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl', 'skippedPaths');
}
public static function statusPrep(){
@@ -530,13 +531,13 @@ class wfIssues {
}
public function deleteAllUpdateIssues() {
$issues = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
$issues = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfUpgradeError' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
$this->clearEmailedStatus($issues);
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfUpgradeError' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
if (wfCentral::isConnected()) {
wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfUpgradeError', 'wfPluginUpgrade', 'wfThemeUpgrade'));
}
}

View File

@@ -1625,7 +1625,7 @@ class wfScanEngine {
$counter = 0;
$query = "select ID from " . $wpdb->users;
$dbh = $wpdb->dbh;
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
$useMySQLi = wfUtils::useMySQLi();
if ($useMySQLi) { //If direct-access MySQLi is available, we use it to minimize the memory footprint instead of letting it fetch everything into an array first
$result = $dbh->query($query);
if (!is_object($result)) {
@@ -1889,36 +1889,50 @@ class wfScanEngine {
foreach ($this->pluginRepoStatus as $slug => $status) {
if ($status === false) {
$result = plugins_api('plugin_information', array(
'slug' => $slug,
'fields' => array(
'short_description' => false,
'description' => false,
'sections' => false,
'tested' => true,
'requires' => true,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'downloadlink' => false,
'last_updated' => true,
'added' => false,
'tags' => false,
'compatibility' => true,
'homepage' => true,
'versions' => false,
'donate_link' => false,
'reviews' => false,
'banners' => false,
'icons' => false,
'active_installs' => false,
'group' => false,
'contributors' => false,
),
));
unset($result->versions);
unset($result->screenshots);
$this->pluginRepoStatus[$slug] = $result;
try {
$result = plugins_api('plugin_information', array(
'slug' => $slug,
'fields' => array(
'short_description' => false,
'description' => false,
'sections' => false,
'tested' => true,
'requires' => true,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'downloadlink' => false,
'last_updated' => true,
'added' => false,
'tags' => false,
'compatibility' => true,
'homepage' => true,
'versions' => false,
'donate_link' => false,
'reviews' => false,
'banners' => false,
'icons' => false,
'active_installs' => false,
'group' => false,
'contributors' => false,
),
));
unset($result->versions);
unset($result->screenshots);
$this->pluginRepoStatus[$slug] = $result;
}
catch (Exception $e) {
error_log(sprintf('Caught exception while attempting to refresh update status for slug %s: %s', $slug, $e->getMessage()));
$this->pluginRepoStatus[$slug] = false;
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY, sprintf('%s [%s]', $e->getMessage(), $slug), false);
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY, $slug, false);
}
catch (Throwable $t) {
error_log(sprintf('Caught error while attempting to refresh update status for slug %s: %s', $slug, $t->getMessage()));
$this->pluginRepoStatus[$slug] = false;
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY, sprintf('%s [%s]', $t->getMessage(), $slug), false);
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY, $slug, false);
}
$this->forkIfNeeded();
}
@@ -1929,7 +1943,39 @@ class wfScanEngine {
$haveIssues = wfIssues::STATUS_SECURE;
if (!$this->isFullScan()) {
$this->deleteNewIssues(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
$this->deleteNewIssues(array('wfUpgradeError', 'wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
}
if ($lastError = wfConfig::get(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY)) {
$lastSlug = wfConfig::get(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
$longMsg = sprintf(/* translators: error message. */ __("The update check performed during the scan encountered an error: %s", 'wordfence'), esc_html($lastError));
if ($lastSlug === false) {
$longMsg .= ' ' . __('Wordfence cannot detect if the installed plugins and themes are up to date. This might be caused by a PHP compatibility issue in one or more plugins/themes.', 'wordfence');
}
else {
$longMsg .= ' ' . __('Wordfence cannot detect if this plugin/theme is up to date. This might be caused by a PHP compatibility issue in the plugin.', 'wordfence');
}
$longMsg .= ' ' . sprintf(
/* translators: Support URL. */
__('<a href="%s" target="_blank" rel="noopener noreferrer">Get more information.<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED));
$ignoreKey = ($lastSlug === false ? 'wfUpgradeErrorGeneral' : sprintf('wfUpgradeError-%s', $lastSlug));
$added = $this->addIssue(
'wfUpgradeError',
wfIssues::SEVERITY_MEDIUM,
$ignoreKey,
$ignoreKey,
($lastSlug === false ? __("Update Check Encountered Error", 'wordfence') : sprintf(/* translators: plugin/theme slug. */ __("Update Check Encountered Error on '%s'", 'wordfence'), esc_html($lastSlug))),
$longMsg,
array()
);
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssues = wfIssues::STATUS_PROBLEM;
}
else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssues = wfIssues::STATUS_IGNORED;
}
}
// WordPress core updates needed
@@ -2029,8 +2075,12 @@ class wfScanEngine {
$statusArray['version'] = null;
wordfence::status(3, 'error', "Unable to determine version for plugin $slug");
}
$lastUpdateTimestamp = strtotime($statusArray['last_updated']);
if ($lastUpdateTimestamp > 0 && (time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
if (array_key_exists('last_updated', $statusArray) &&
is_string($statusArray['last_updated']) &&
($lastUpdateTimestamp = strtotime($statusArray['last_updated'])) &&
(time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
$statusArray['dateUpdated'] = wfUtils::formatLocalTime(get_option('date_format'), $lastUpdateTimestamp);
$severity = wfIssues::SEVERITY_MEDIUM;
$statusArray['abandoned'] = true;
@@ -2433,7 +2483,6 @@ class wfScanEngine {
wfConfig::set('wfKillRequested', 0, wfConfig::DONT_AUTOLOAD);
wordfence::status(4, 'info', __("Entering start scan routine", 'wordfence'));
if (wfScanner::shared()->isRunning()) {
wfUtils::getScanFileError();
return __("A scan is already running. Use the stop scan button if you would like to terminate the current scan.", 'wordfence');
}
wfConfig::set('currentCronKey', ''); //Ensure the cron key is cleared

View File

@@ -73,8 +73,8 @@ class wfScanMonitor {
if ($lastAttempt === null || $now - $lastAttempt < self::SCAN_START_TIMEOUT)
return;
$lastSuccess = wfConfig::get(self::CONFIG_LAST_SUCCESS);
self::setRemainingResumeAttempts(--$remainingAttempts);
if ($lastSuccess === null || $lastAttempt > $lastSuccess) {
self::setRemainingResumeAttempts(--$remainingAttempts);
wordfence::status(2, 'info', sprintf(__('Attempting to resume scan stage (%d attempt(s) remaining)...', 'wordfence'), $remainingAttempts));
self::resumeScan();
}

View File

@@ -149,6 +149,7 @@ class wfSupportController {
const ITEM_SCAN_RESULT_PUBLIC_CONFIG = 'scan-result-public-config';
const ITEM_SCAN_RESULT_PLUGIN_ABANDONED = 'scan-result-plugin-abandoned';
const ITEM_SCAN_RESULT_PLUGIN_REMOVED = 'scan-result-plugin-removed';
const ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED = 'scan-result-update-check-failed';
const ITEM_SCAN_RESULT_OPTION_MALWARE_URL = 'scan-result-option-malware-url';
const ITEM_SCAN_RESULT_GEOIP_UPDATE = 'scan-result-geoip-update';
const ITEM_SCAN_RESULT_WAF_DISABLED = 'scan-result-waf-disabled';
@@ -178,6 +179,7 @@ class wfSupportController {
const ITEM_DIAGNOSTICS_OPTION_BETA_TDF = 'diagnostics-option-beta-tdf';
const ITEM_DIAGNOSTICS_OPTION_WORDFENCE_TRANSLATIONS = 'diagnostics-option-wordfence-translations';
const ITEM_DIAGNOSTICS_IPV6 = 'diagnostics-ipv6';
const ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK = 'compatibility-cloudflare';
const ITEM_MODULE_LOGIN_SECURITY = 'module-login-security';
const ITEM_MODULE_LOGIN_SECURITY_2FA = 'module-login-security-2fa';
@@ -341,6 +343,7 @@ class wfSupportController {
case self::ITEM_SCAN_RESULT_PUBLIC_CONFIG:
case self::ITEM_SCAN_RESULT_PLUGIN_ABANDONED:
case self::ITEM_SCAN_RESULT_PLUGIN_REMOVED:
case self::ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED:
case self::ITEM_SCAN_RESULT_OPTION_MALWARE_URL:
case self::ITEM_SCAN_RESULT_GEOIP_UPDATE:
case self::ITEM_SCAN_RESULT_WAF_DISABLED:
@@ -370,6 +373,7 @@ class wfSupportController {
case self::ITEM_DIAGNOSTICS_OPTION_BETA_TDF:
case self::ITEM_DIAGNOSTICS_OPTION_WORDFENCE_TRANSLATIONS:
case self::ITEM_DIAGNOSTICS_IPV6:
case self::ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK:
case self::ITEM_MODULE_LOGIN_SECURITY:
case self::ITEM_MODULE_LOGIN_SECURITY_2FA:

View File

@@ -6,6 +6,9 @@ class wfUpdateCheck {
const VULN_SEVERITY_MEDIUM = 40;
const VULN_SEVERITY_LOW = 1;
const VULN_SEVERITY_NONE = 0;
const LAST_UPDATE_CHECK_ERROR_KEY = 'lastUpdateCheckError';
const LAST_UPDATE_CHECK_ERROR_SLUG_KEY = 'lastUpdateCheckErrorSlug';
private $needs_core_update = false;
private $core_update_version = 0;
@@ -169,6 +172,11 @@ class wfUpdateCheck {
* @return $this
*/
public function checkAllUpdates($useCachedValued = true) {
if (!$useCachedValued) {
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_KEY);
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
}
return $this->checkCoreUpdates($useCachedValued)
->checkPluginUpdates($useCachedValued)
->checkThemeUpdates($useCachedValued);
@@ -189,7 +197,7 @@ class wfUpdateCheck {
require_once(ABSPATH . 'wp-admin/includes/update.php');
}
include(ABSPATH . WPINC . '/version.php'); //defines $wp_version
include(ABSPATH . WPINC . '/version.php'); /** @var $wp_version */
$update_core = get_preferred_from_update_core();
if ($useCachedValued && isset($update_core->last_checked) && isset($update_core->version_checked) && 12 * HOUR_IN_SECONDS > (time() - $update_core->last_checked) && $update_core->version_checked == $wp_version) { //Duplicate of _maybe_update_core, which is a private call
@@ -276,7 +284,19 @@ class wfUpdateCheck {
return $update_plugins;
if (!function_exists('wp_update_plugins'))
require_once(ABSPATH . WPINC . '/update.php');
wp_update_plugins();
try {
wp_update_plugins();
}
catch (Exception $e) {
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $e->getMessage(), false);
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
error_log('Caught exception while attempting to refresh plugin update status: ' . $e->getMessage());
}
catch (Throwable $t) {
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $t->getMessage(), false);
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
error_log('Caught error while attempting to refresh plugin update status: ' . $t->getMessage());
}
return get_site_transient('update_plugins');
}
@@ -293,7 +313,7 @@ class wfUpdateCheck {
self::requirePluginsApi();
$update_plugins = $this->fetchPluginUpdates();
$update_plugins = $this->fetchPluginUpdates($useCachedValued);
//Get the full plugin list
if (!function_exists('get_plugins')) {
@@ -357,7 +377,18 @@ class wfUpdateCheck {
//Do nothing, use cached value
}
else {
wp_update_themes();
try {
wp_update_themes();
}
catch (Exception $e) {
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $e->getMessage(), false);
error_log('Caught exception while attempting to refresh theme update status: ' . $e->getMessage());
}
catch (Throwable $t) {
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $t->getMessage(), false);
error_log('Caught error while attempting to refresh theme update status: ' . $t->getMessage());
}
$update_themes = get_site_transient('update_themes');
}

View File

@@ -1341,39 +1341,12 @@ class wfUtils {
$string = str_replace(",", "\n", $string); // fix old format
return implode("\n", array_unique(array_filter(array_map('trim', explode("\n", $string)))));
}
public static function getScanFileError() {
$fileTime = wfConfig::get('scanFileProcessing');
if (!$fileTime) {
return;
}
list($file, $time) = unserialize($fileTime);
if ($time+10 < time()) {
$add = true;
$excludePatterns = wordfenceScanner::getExcludeFilePattern(wordfenceScanner::EXCLUSION_PATTERNS_USER);
if ($excludePatterns) {
foreach ($excludePatterns as $pattern) {
if (preg_match($pattern, $file)) {
$add = false;
break;
}
}
}
if ($add) {
$files = wfConfig::get('scan_exclude') . "\n" . $file;
wfConfig::set('scan_exclude', self::cleanupOneEntryPerLine($files));
}
self::endProcessingFile();
}
}
public static function beginProcessingFile($file) {
wfConfig::set('scanFileProcessing', serialize(array($file, time())));
//Do nothing
}
public static function endProcessingFile() {
wfConfig::set('scanFileProcessing', null);
if (wfScanner::shared()->useLowResourceScanning()) {
usleep(10000); //10 ms
}
@@ -3148,7 +3121,20 @@ class wfUtils {
}
return $encoded;
}
/**
* Returns whether or not MySQLi should be used directly when needed. Returns true if there's a valid DB handle,
* our database test succeeded, our constant is not set to prevent it, and then either $wpdb indicates it's using
* mysqli (older WordPress versions) or we're on PHP 7+ (only mysqli is ever used).
*
* @return bool
*/
public static function useMySQLi() {
global $wpdb;
$dbh = $wpdb->dbh;
$useMySQLi = (is_object($dbh) && (PHP_MAJOR_VERSION >= 7 || $wpdb->use_mysqli) && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
return $useMySQLi;
}
}
// GeoIP lib uses these as well

View File

@@ -6,5 +6,5 @@ $wfPHPMinimumVersion = '5.5.0'; //The currently supported minimum
$wfOpenSSLDeprecatingVersion = '1.0.1';
$wfOpenSSLMinimumVersion = '1.0.1';
$wfWordPressDeprecatingVersion = '4.4.0';
$wfWordPressDeprecatingVersion = '4.7.0';
$wfWordPressMinimumVersion = '3.9.0';

View File

@@ -6098,7 +6098,7 @@ HTML;
'loadTwoFactor', 'sendTestEmail',
'email_summary_email_address_debug', 'unblockNetwork',
'sendDiagnostic', 'saveDisclosureState', 'saveWAFConfig', 'updateWAFRules', 'loadLiveTraffic', 'whitelistWAFParamKey',
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser',
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser', 'acknowledgeAdminUser',
'hideFileHtaccess', 'saveDebuggingConfig',
'whitelistBulkDelete', 'whitelistBulkEnable', 'whitelistBulkDisable',
'dismissNotification', 'utilityScanForBlacklisted', 'dashboardShowMore',
@@ -7608,6 +7608,35 @@ SQL
'user_login' => $userLogin,
);
}
public static function ajax_acknowledgeAdminUser_callback() {
$issueID = absint(!empty($_POST['issueID']) ? $_POST['issueID'] : 0);
$wfIssues = new wfIssues();
$issue = $wfIssues->getIssueByID($issueID);
if (!$issue) {
return array('errorMsg' => __("We could not find that issue in the database.", 'wordfence'));
}
$data = $issue['data'];
if (empty($data['userID'])) {
return array('errorMsg' => __("We could not find that user in the database.", 'wordfence'));
}
$user = new WP_User($data['userID']);
if (!$user->exists()) {
return array('errorMsg' => __("We could not find that user in the database.", 'wordfence'));
}
$userLogin = $user->user_login;
$adminUsers = new wfAdminUserMonitor();
$adminUsers->addAdmin($data['userID']);
$wfIssues->deleteIssue($issueID);
wfScanEngine::refreshScanNotification($wfIssues);
return array(
'ok' => 1,
'user_login' => $userLogin,
);
}
/**
*
@@ -9464,9 +9493,23 @@ if (file_exists(__DIR__.%1$s)) {
}
$request = new wfCentralAPIRequest('/site/access-token', 'GET', $authGrant);
$response = $request->execute();
try {
$response = $request->execute();
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
if ($response->isError()) {
if (!isset($response)) {
return array(
'err' => 1,
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
);
}
else if ($response->isError()) {
return $response->returnErrorArray();
}
@@ -9539,9 +9582,23 @@ if (file_exists(__DIR__.%1$s)) {
),
),
));
$response = $request->execute();
if ($response->isError()) {
try {
$response = $request->execute();
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
if (!isset($response)) {
return array(
'err' => 1,
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
);
}
else if ($response->isError()) {
return $response->returnErrorArray();
}
@@ -9577,12 +9634,27 @@ if (file_exists(__DIR__.%1$s)) {
'success' => 1,
);
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
return array(
'error' => 1,
'errorMsg' => $e->getMessage(),
);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
return array(
'error' => 1,
'errorMsg' => $e->getMessage(),
);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
return array(
'error' => 1,
'errorMsg' => $t->getMessage(),
);
}
}
public static function ajax_wfcentral_step4_callback() {
@@ -9647,12 +9719,27 @@ if (file_exists(__DIR__.%1$s)) {
rawurlencode(wfConfig::get('wordfenceCentralSiteID')), rawurlencode($body['access-token'])),
);
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
return array(
'error' => 1,
'errorMsg' => $e->getMessage(),
);
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
return array(
'error' => 1,
'errorMsg' => $e->getMessage(),
);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
return array(
'error' => 1,
'errorMsg' => $t->getMessage(),
);
}
}
public static function ajax_wfcentral_step6_callback() {
$wfCentralUserSiteAccessToken = wfConfig::get('wordfenceCentralUserSiteAccessToken');
@@ -9683,9 +9770,16 @@ if (file_exists(__DIR__.%1$s)) {
sprintf('/site/%s', wfConfig::get('wordfenceCentralSiteID')),
'DELETE');
$response = $request->execute();
} catch (wfCentralAPIException $e) {
}
catch (wfCentralAPIException $e) {
}
catch (Exception $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
}
catch (Throwable $t) {
wfCentralAPIRequest::handleInternalCentralAPIError($t);
}
wfRESTConfigController::disconnectConfig();

View File

@@ -181,7 +181,7 @@ class wordfenceURLHoover {
if ($this->useDB) {
global $wpdb;
$dbh = $wpdb->dbh;
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
$useMySQLi = wfUtils::useMySQLi();
if ($useMySQLi) { //If direct-access MySQLi is available, we use it to minimize the memory footprint instead of letting it fetch everything into an array first
wordfence::status(4, 'info', __("Using MySQLi directly.", 'wordfence'));
$result = $dbh->query("SELECT DISTINCT hostKey FROM {$this->table} ORDER BY hostKey ASC LIMIT 100000"); /* We limit to 100,000 prefixes since more than that cannot be reliably checked within the default max_execution_time */

View File

@@ -6,10 +6,12 @@ class Controller_Permissions {
const CAP_ACTIVATE_2FA_SELF = 'wf2fa_activate_2fa_self'; //Activate 2FA on its own user account
const CAP_ACTIVATE_2FA_OTHERS = 'wf2fa_activate_2fa_others'; //Activate 2FA on user accounts other than its own
const CAP_MANAGE_SETTINGS = 'wf2fa_manage_settings'; //Edit settings for the plugin
const SITE_BATCH_SIZE = 50; //The maximum number of sites to process during a single request
const SETTING_LAST_ROLE_CHANGE = 'wfls_last_role_change';
const SETTING_LAST_ROLE_SYNC = 'wfls_last_role_sync';
private $network_roles = array();
private $multisite_roles = null;
/**
* Returns the singleton Controller_Permissions.
@@ -23,15 +25,9 @@ class Controller_Permissions {
}
return $_shared;
}
private function on_role_change() {
update_site_option('wfls_last_role_change', time());
if(is_multisite())
update_site_option('wfls_role_batch_position', 0);
}
public function install() {
$this->on_role_change();
$this->_on_role_change();
if (is_multisite()) {
//Super Admin automatically gets all capabilities, so we don't need to explicitly add them
$this->_add_cap_multisite('administrator', self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
@@ -42,57 +38,102 @@ class Controller_Permissions {
$this->_add_cap('administrator', self::CAP_MANAGE_SETTINGS);
}
}
public function uninstall() {
if (Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_DELETE_ON_DEACTIVATION)) {
if (is_multisite()) {
$sites = $this->get_sites();
foreach ($sites as $id) {
switch_to_blog($id);
wp_clear_scheduled_hook('wordfence_ls_role_sync_cron');
restore_current_blog();
}
}
}
}
public static function _init_actions() {
add_action('wordfence_ls_role_sync_cron', array(Controller_Permissions::shared(), '_role_sync_cron'));
}
public function init() {
global $wp_version;
if(is_multisite()){
if(version_compare($wp_version, '5.1.0', '>=')){
if (is_multisite()) {
if (version_compare($wp_version, '5.1.0', '>=')) {
add_action('wp_initialize_site', array($this, '_wp_initialize_site'), 99);
}
else{
else {
add_action('wpmu_new_blog', array($this, '_wpmu_new_blog'), 10, 5);
}
add_action('init', array($this, 'check_role_sync'), 1);
add_action('init', array($this, '_validate_role_sync_cron'), 1);
}
}
/**
* Syncs roles to the new multisite blog.
*
* @param $site_id
* @param $user_id
* @param $domain
* @param $path
* @param $network_id
*/
public function _wpmu_new_blog($site_id, $user_id, $domain, $path, $network_id) {
$this->sync_roles($network_id, $site_id);
}
/**
* Syncs roles to the new multisite blog.
*
* @param $new_site
*/
public function _wp_initialize_site($new_site) {
$this->sync_roles($new_site->site_id, $new_site->blog_id);
}
public function check_role_sync() {
//Trigger an initial update for existing installations
$last_role_change=(int)get_site_option('wfls_last_role_change', 0);
if($last_role_change===0)
$this->on_role_change();
//Process the current batch if necessary
$position=(int)get_site_option('wfls_role_batch_position', 0);
if($position===-1)
return;
$sites=$this->get_sites($position, self::SITE_BATCH_SIZE);
if(empty($sites)){
$position=-1;
return;
/**
* Creates the hourly cron (if needed) that handles syncing the roles/permissions for the current blog. Because crons
* are specific to individual blogs on multisite rather than to the network itself, this will end up creating a cron
* for every member blog of the multisite.
*
* If there is a new role change since the last sync, a one-off cron will be fired to sync it sooner than the normal
* recurrence period.
*
* Multisite only.
*
*/
public function _validate_role_sync_cron() {
if (!wp_next_scheduled('wordfence_ls_role_sync_cron')) {
wp_schedule_event(time(), 'hourly', 'wordfence_ls_role_sync_cron');
}
else{
$network_id=get_current_site()->id;
foreach($sites as $site){
$site=(int)$site;
$this->sync_roles($network_id, $site);
else {
$last_role_change = (int) get_site_option(self::SETTING_LAST_ROLE_CHANGE, 0);
if ($last_role_change >= get_option(self::SETTING_LAST_ROLE_SYNC, 0)) {
wp_schedule_single_event(time(), 'wordfence_ls_role_sync_cron'); //Force queue an update in case the normal cron is still a while out
}
$position=$site;
}
update_site_option('wfls_role_batch_position', $position);
//Update the current site if not already up to date
$site_id=get_current_blog_id();
if($last_role_change>=get_option('wfls_last_role_sync', 0)&&$site_id>=$position){
$this->sync_roles(get_current_site()->id, $site_id);
update_option('wfls_last_role_sync', time());
}
/**
* Handles syncing the roles/permissions for the current blog when the cron fires.
*/
public function _role_sync_cron() {
$last_role_change = (int) get_site_option(self::SETTING_LAST_ROLE_CHANGE, 0);
if ($last_role_change === 0) {
$this->_on_role_change();
}
if ($last_role_change >= get_option(self::SETTING_LAST_ROLE_SYNC, 0)) {
$network_id = get_current_site()->id;
$blog_id = get_current_blog_id();
$this->sync_roles($network_id, $blog_id);
update_option(self::SETTING_LAST_ROLE_SYNC, time());
}
}
private function _on_role_change() {
update_site_option(self::SETTING_LAST_ROLE_CHANGE, time());
}
/**
@@ -121,9 +162,20 @@ class Controller_Permissions {
return $wpdb->get_col("SELECT blogs.blog_id FROM {$wpdb->site} sites JOIN {$wpdb->blogs} blogs ON blogs.site_id=sites.id AND blogs.path=sites.path");
}
}
private function get_sites($from, $count) {
/**
* Returns an array of all multisite `blog_id` values, optionally limiting the result to the subset between
* ($from, $from + $count].
*
* @param int $from
* @param int $count
* @return array
*/
private function get_sites($from = 0, $count = 0) {
global $wpdb;
if ($from === 0 && $count === 0) {
return $wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0 ORDER BY blog_id ");
}
return $wpdb->get_col($wpdb->prepare("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0 AND blog_id > %d ORDER BY blog_id LIMIT %d", $from, $count));
}
@@ -162,7 +214,7 @@ class Controller_Permissions {
}
public function allow_2fa_self($role_name) {
$this->on_role_change();
$this->_on_role_change();
if (is_multisite()) {
return $this->_add_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
}
@@ -172,7 +224,7 @@ class Controller_Permissions {
}
public function disallow_2fa_self($role_name) {
$this->on_role_change();
$this->_on_role_change();
if (is_multisite()) {
return $this->_remove_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
}
@@ -272,22 +324,125 @@ class Controller_Permissions {
$wp_roles->remove_cap($role_name, $cap);
return true;
}
/**
* Loads the role capability info for the multisite blog IDs in `$includedSites` and appends it to
* `$this->multisite_roles`. Role capability data that is already loaded will be skipped.
*
* @param array $includeSites An array of multisite blog IDs to load.
*/
private function _load_multisite_roles($includeSites) {
global $wpdb;
$needed = array_diff($includeSites, array_keys($this->multisite_roles));
if (empty($needed)) {
return;
}
$suffix = "user_roles";
$queries = array();
foreach ($needed as $b) {
$tables = $wpdb->tables('blog', true, $b);
$queries[] = "SELECT CAST(option_name AS CHAR UNICODE) AS option_name, CAST(option_value AS CHAR UNICODE) AS option_value FROM {$tables['options']} WHERE option_name LIKE '%{$suffix}'";
}
$chunks = array_chunk($queries, 50);
$options = array();
foreach ($chunks as $c) {
$rows = $wpdb->get_results(implode(' UNION ', $c), OBJECT_K);
foreach ($rows as $row) {
$options[$row->option_name] = $row->option_value;
}
}
$extractor = new Utility_MultisiteConfigurationExtractor($wpdb->base_prefix, $suffix);
foreach ($extractor->extract($options) as $site => $option) {
$this->multisite_roles[$site] = maybe_unserialize($option);
}
}
/**
* Returns an array of multisite roles. This is guaranteed to include the multisite blogs in `$includeSites` but may
* include others from earlier calls that are cached.
*
* @param array $includeSites An array for multisite blog IDs.
* @return array
*/
public function get_multisite_roles($includeSites) {
if ($this->multisite_roles === null) {
$this->multisite_roles = array();
}
$this->_load_multisite_roles($includeSites);
return $this->multisite_roles;
}
/**
* Returns the sites + roles that a user has on multisite. The structure of the returned array has the keys as the
* individual site IDs and the associated value as an array of the user's capabilities on that site.
*
* @param WP_User $user
* @return array
*/
public function get_multisite_roles_for_user($user) {
global $wpdb;
$roles = array();
$meta = get_user_meta($user->ID);
if (is_array($meta)) {
$extractor = new Utility_MultisiteConfigurationExtractor($wpdb->base_prefix, 'capabilities');
foreach ($extractor->extract($meta) as $site => $capabilities) {
if (!is_array($capabilities)) { continue; }
$capabilities = array_map('maybe_unserialize', $capabilities);
$localRoles = array();
foreach ($capabilities as $entry) {
foreach ($entry as $role => $state) {
if ($state)
$localRoles[$role] = true;
}
}
$roles[$site] = array_keys($localRoles);
}
}
return $roles;
}
public function get_all_roles($user) {
global $wpdb;
if (is_multisite()) {
$roles = array();
if (is_super_admin($user->ID))
$roles[] = 'super-admin';
foreach (get_blogs_of_user($user->ID) as $id => $blog) {
switch_to_blog($id);
$blogUser = new \WP_User($user->ID);
$roles = array_merge($roles, $blogUser->roles);
restore_current_blog();
if (is_super_admin($user->ID)) {
$roles['super-admin'] = true;
}
return array_unique($roles);
foreach ($this->get_multisite_roles_for_user($user) as $site => $siteRoles) {
foreach ($siteRoles as $role) {
$roles[$role] = true;
}
}
return array_keys($roles);
}
else {
return $user->roles;
}
}
public function does_user_have_multisite_capability($user, $capability) {
$userRoles = $this->get_multisite_roles_for_user($user);
if (in_array('super-admin', $userRoles)) {
return true;
}
$blogRoles = $this->get_multisite_roles(array_keys($userRoles));
$blogs = get_blogs_of_user($user->ID);
foreach ($blogs as $blogId => $blog) {
$blogId = (int) $blogId;
if (!array_key_exists($blogId, $userRoles) || !array_key_exists($blogId, $blogRoles)) { continue; } //Blog with ID `$blogId` should be ignored
foreach ($userRoles[$blogId] as $userRole) {
if (!array_key_exists($userRole, $blogRoles[$blogId]) || !array_key_exists('capabilities', $blogRoles[$blogId][$userRole])) { continue; } //Sanity check for needed keys, should not happen
$capabilities = $blogRoles[$blogId][$userRole]['capabilities'];
if (array_key_exists($capability, $capabilities) && $capabilities[$capability]) { return true; }
}
}
return false;
}
}

View File

@@ -188,42 +188,11 @@ class Controller_Users {
*/
public function can_activate_2fa($user) {
if (is_multisite() && !is_super_admin($user->ID)) {
$blogs = get_blogs_of_user($user->ID);
foreach ($blogs as $id => $info) {
if ($this->_user_can_for_blog($user, $id, Controller_Permissions::CAP_ACTIVATE_2FA_SELF)) {
return true;
}
}
return false;
return Controller_Permissions::shared()->does_user_have_multisite_capability($user, Controller_Permissions::CAP_ACTIVATE_2FA_SELF);
}
return user_can($user, Controller_Permissions::CAP_ACTIVATE_2FA_SELF);
}
/**
* Implementation of current_user_can_for_blog that works for an arbitrary user.
*
* @param int $user_id
* @param int $blog_id
* @param string $capability
* @return bool
*/
private function _user_can_for_blog($user_id, $blog_id, $capability) {
$switched = is_multisite() ? switch_to_blog($blog_id) : false;
$user = new \WP_User($user_id);
$args = array_slice(func_get_args(), 2);
$args = array_merge(array($capability), $args);
$can = call_user_func_array(array($user, 'has_cap'), $args);
if ($switched) {
restore_current_blog();
}
return $can;
}
/**
* Returns whether or not any user has 2FA activated.
*

View File

@@ -82,6 +82,8 @@ class Controller_WordfenceLS {
add_action('init', array($this, '_wordpress_init'));
if ($this->is_shortcode_enabled())
add_action('wp_enqueue_scripts', array($this, '_handle_shortcode_prerequisites'));
Controller_Permissions::_init_actions();
}
public function _wordpress_init() {
@@ -214,6 +216,7 @@ END
public function _uninstall_plugin() {
Controller_Time::shared()->uninstall();
Controller_Permissions::shared()->uninstall();
foreach (array(self::VERSION_KEY) as $opt) {
if (is_multisite() && function_exists('delete_network_option')) {
@@ -348,7 +351,7 @@ END
->enqueue();
wp_enqueue_style('wordfence-ls-login', Model_Asset::css('login.css'), array(), WORDFENCE_LS_VERSION);
wp_localize_script('wordfence-ls-login', 'WFLSVars', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'ajaxurl' => Utility_URL::relative_admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wp-ajax'),
'recaptchasitekey' => Controller_Settings::shared()->get(Controller_Settings::OPTION_RECAPTCHA_SITE_KEY),
'useCAPTCHA' => $useCAPTCHA,
@@ -361,7 +364,7 @@ END
private function get_2fa_management_script_data() {
return array(
'WFLSVars' => array(
'ajaxurl' => admin_url('admin-ajax.php'),
'ajaxurl' => Utility_URL::relative_admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wp-ajax'),
'modalTemplate' => Model_View::create('common/modal-prompt', array('title' => '${title}', 'message' => '${message}', 'primaryButton' => array('id' => 'wfls-generic-modal-close', 'label' => __('Close', 'wordfence'), 'link' => '#')))->render(),
'modalNoButtonsTemplate' => Model_View::create('common/modal-prompt', array('title' => '${title}', 'message' => '${message}'))->render(),

View File

@@ -0,0 +1,19 @@
<?php
namespace WordfenceLS;
class Utility_MeasuredString {
public $string;
public $length;
public function __construct($string) {
$this->string = $string;
$this->length = strlen($string);
}
public function __toString() {
return $this->string;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace WordfenceLS;
class Utility_Multisite {
/**
* Returns an array of all active multisite blogs (if `$blogIds` is `null`) or a list of active multisite blogs
* filtered to only those in `$blogIds` if non-null.
*
* @param array|null $blogIds
* @return array
*/
public static function retrieve_active_sites($blogIds = null) {
$args = array(
'number' => '', /* WordPress core passes an empty string which appears to remove the result set limit */
'update_site_meta_cache' => false, /* Defaults to true which is not desirable for this use case */
//Ignore archived/spam/deleted sites
'archived' => 0,
'spam' => 0,
'deleted' => 0
);
if ($blogIds !== null) {
$args['site__in'] = $blogIds;
}
if (function_exists('get_sites')) {
return get_sites($args);
}
global $wpdb;
if ($blogIds !== null) {
$blogIdsQuery = implode(',', wp_parse_id_list($args['site__in']));
return $wpdb->get_results("SELECT * FROM {$wpdb->blogs} WHERE blog_id IN ({$blogIdsQuery}) AND archived = 0 AND spam = 0 AND deleted = 0");
}
return $wpdb->get_results("SELECT * FROM {$wpdb->blogs} WHERE archived = 0 AND spam = 0 AND deleted = 0");
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace WordfenceLS;
class Utility_MultisiteConfigurationExtractor {
private $prefix, $suffix;
private $suffixOffset;
public function __construct($prefix, $suffix) {
$this->prefix = new Utility_MeasuredString($prefix);
$this->suffix = new Utility_MeasuredString($suffix);
$this->suffixOffset = -$this->suffix->length;
}
/**
* Parses a `get_user_meta` result array into a more usable format. The input array will be something similar to
* [
* 'wp_capabilities' => '...',
* 'wp_3_capabilities' => '...',
* 'wp_4_capabilities' => '...',
* 'wp_10_capabilities' => '...',
* ]
*
* This will return
* [
* 1 => '...',
* 3 => '...',
* 4 => '...',
* 10 => '...',
* ]
*
* @param array $values
* @return array
*/
private function parseBlogIds($values) {
$parsed = array();
foreach ($values as $key => $value) {
if (substr($key, $this->suffixOffset) === $this->suffix->string && strpos($key, (string) $this->prefix) === 0) {
$blogId = substr($key, $this->prefix->length, strlen($key) - $this->prefix->length + $this->suffixOffset);
if (empty($blogId)) {
$parsed[1] = $value;
}
else if (substr($blogId, -1) === '_') {
$parsed[(int) $blogId] = $value;
}
}
}
return $parsed;
}
/**
* Filters $values, which is the resulting array from `$this->parseBlogIds` so it contains only the values for the
* sites in $sites.
*
* @param array $values
* @param array $sites
* @return array
*/
private function filterValues($values, $sites) {
$filtered = array();
foreach ($sites as $site) {
$blogId = (int) $site->blog_id;
$filtered[$blogId] = $values[$blogId];
}
return $filtered;
}
/**
* Processes a `get_user_meta` result array to re-key it so the keys are the numerical ID of all multisite blog IDs
* in `$values` that are still in an active state.
*
* @param array $values
* @return array
*/
public function extract($values) {
$parsed = $this->parseBlogIds($values);
if (empty($parsed))
return $parsed;
$sites = Utility_Multisite::retrieve_active_sites(array_keys($parsed));
return $this->filterValues($parsed, $sites);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace WordfenceLS;
class Utility_URL {
/**
* Similar to WordPress' `admin_url`, this returns a host-relative URL for the given path. It may be used to avoid
* canonicalization issues with CORS (e.g., the site is configured for the www. variant of the URL but doesn't forward
* the other).
*
* @param string $path
* @return string
*/
public static function relative_admin_url($path = '') {
$url = admin_url($path);
$components = parse_url($url);
$s = $components['path'];
if (!empty($components['query'])) {
$s .= '?' . $components['query'];
}
if (!empty($components['fragment'])) {
$s .= '#' . $components['fragment'];
}
return $s;
}
}

View File

@@ -140,6 +140,10 @@
if (heading.length > 0) {
var dom = (type === 'error' ? $('<div id="login_error">') : $('<p class="message">'));
dom.addClass('wfls-login-message');
dom.addClass('notice');
if (type === 'error') {
dom.addClass('notice-error');
}
dom.html(messageHtml);
heading.after(dom);
dom.get(0).scrollIntoView();

View File

@@ -85,7 +85,8 @@ $recovery = $initializationData->get_recovery_codes();
payload,
function(response) {
if (response.error) {
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', response.error);
WFLS.userIsActivating = false;
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', response.error, {includeDefaultButtons: true});
}
else {
$('#wfls-activation-controls').crossfade($('#wfls-deactivation-controls'));
@@ -116,7 +117,7 @@ $recovery = $initializationData->get_recovery_codes();
}
},
function(error) {
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('An error was encountered while trying to activate two-factor authentication. Please try again.', 'wordfence')); ?>');
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('An error was encountered while trying to activate two-factor authentication. Please try again.', 'wordfence')); ?>', {includeDefaultButtons: true});
WFLS.userIsActivating = false;
}
);

View File

@@ -26,8 +26,8 @@ if ($wfCoreActive && !(isset($wfCoreLoading) && $wfCoreLoading)) {
else {
define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
define('WORDFENCE_LS_VERSION', '1.1.4');
define('WORDFENCE_LS_BUILD_NUMBER', '1690810710');
define('WORDFENCE_LS_VERSION', '1.1.7');
define('WORDFENCE_LS_BUILD_NUMBER', '1699289814');
define('WORDFENCE_LS_PLUGIN_BASENAME', plugin_basename(__FILE__));

View File

@@ -3,8 +3,8 @@ Contributors: mmaunder, wfryan, wfmatt, wfmattr
Tags: security, waf, malware, 2fa, two factor, login security, firewall, brute force, scanner, scan, web application firewall, protection, stop hackers, prevent hacks, secure wordpress, wordpress security
Requires at least: 3.9
Requires PHP: 5.5
Tested up to: 6.3
Stable tag: 7.10.3
Tested up to: 6.4
Stable tag: 7.10.7
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -189,13 +189,42 @@ Secure your website with Wordfence.
== Changelog ==
= 7.10.7 - November 6, 2023 =
* Fix: Compatibility fix for WordPress 6.4 on the login page styling
= 7.10.6 - October 30, 2023 =
* Fix: Addressed an issue with multisite installations when the wp_options tables had different encodings/collations
= 7.10.5 - October 23, 2023 =
* Improvement: Updated the bundled GeoIP database
* Improvement: Added detection for Cloudflare reverse proxies blocking callbacks to the site
* Change: Files are no longer excluded from future scans if a previous scan stopped during their processing
* Fix: Added handling for the pending WordPress 6.4 change that removes $wpdb->use_mysqli
* Fix: The WAF MySQLi storage engine will now work correctly when either DB_COLLATE or DB_CHARSET are not defined
* Fix: Added additional error handling to Central calls to better handle request failures or conflicts
* Fix: Addressed a warning that would occur if a non-repo plugin update hook did not provide a last updated date
* Fix: Fixed an error in PHP 8 that could occur if the time correction offset was not numeric
* Fix: 2FA AJAX calls now use an absolute path rather than a full URL to avoid CORS issues on sites that do not canonicalize www and non-www requests
* Fix: Addressed a race condition where multiple concurrent hits on multisite could trigger overlapping role sync tasks
* Fix: Improved performance when viewing the user list on large multisites
* Fix: Fixed a UI bug where an invalid code on 2FA activation would leave the activate button disabled
* Fix: Reverted a change on error modals to bring back the additional close button for better accessibility
= 7.10.4 - September 25, 2023 =
* Improvement: "Admin created outside of WordPress" scan results may now be reviewed and approved
* Improvement: The WAF storage engine may now be specified by setting the environmental variable "WFWAF_STORAGE_ENGINE"
* Improvement: Detect when a plugin or theme with a custom update handler is broken and blocking update version checks
* Change: Deprecated support for WordPress versions lower than 4.7.0
* Change: Exclude parse errors of a damaged compiled rules file from reporting
* Fix: Suppress PHP notices related to rule loading when running WP-CLI
* Fix: Fixed an issue with the scan monitor cron that could leave it running unnecessarily
= 7.10.3 - July 31, 2023 =
* Improvement: Updated GeoIP database
* Fix: Added missing text domain to translation function call
* Fix: Corrected inconsistent styling of switch controls
* Change: Made MySQLi storage engine the default for Flywheel hosted sites
= 7.10.2 - July 17, 2023 =
* Fix: Prevented bundled sodium_compat library from conflicting with versions included with older WordPress versions

View File

@@ -17,8 +17,7 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
return true;
}
$sapi = @php_sapi_name();
if ($sapi == "cli") {
if (wfWAFUtils::isCli()) {
return false;
}
return true;

View File

@@ -1098,13 +1098,12 @@ class wfWAFUtils {
$offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_ntp', false, 'synced');
if ($offset === false) {
$offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_wf', false, 'synced');
if ($offset === false) { $offset = 0; }
}
}
catch (Exception $e) {
//Ignore
}
return time() + $offset;
return time() + ((int) $offset);
}
/**
@@ -1164,17 +1163,30 @@ class wfWAFUtils {
'pass' => 'DB_PASSWORD',
'database' => 'DB_NAME',
'host' => 'DB_HOST',
'charset' => 'DB_CHARSET',
'collation' => 'DB_COLLATE'
'charset' => array('constant' => 'DB_CHARSET', 'default' => ''),
'collation' => array('constant' => 'DB_COLLATE', 'default' => ''),
);
$constants += $optionalConstants;
foreach ($constants as $key => $constant) {
unset($defaultValue);
if (is_array($constant)) {
$defaultValue = $constant['default'];
$constant = $constant['constant'];
}
if (array_key_exists($key, $return)) {
continue;
} else if (array_key_exists($constant, $parsedConstants)) {
}
else if (array_key_exists($constant, $parsedConstants)) {
$return[$key] = $parsedConstants[$constant];
} else if (!array_key_exists($key, $optionalConstants)){
return ($return = false);
}
else if (!array_key_exists($key, $optionalConstants)){
if (isset($defaultValue)) {
$return[$key] = $defaultValue;
}
else {
return ($return = false);
}
}
}
@@ -1229,6 +1241,9 @@ class wfWAFUtils {
public static function isVersionBelow($target, $compared) {
return $compared === null || version_compare($compared, $target, '<');
}
public static function isCli() {
return (@php_sapi_name()==='cli') || !array_key_exists('REQUEST_METHOD', $_SERVER);
}
}
}

View File

@@ -328,6 +328,11 @@ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
public function loadRules() {
$storageEngine = $this->getStorageEngine();
if ($storageEngine instanceof wfWAFStorageFile) {
$logLevel = error_reporting();
if (wfWAFUtils::isCli()) { //Done to suppress errors from WP-CLI when the WAF is run on environments that have a server level constant to use the MySQLi storage engine that is not in place when running from the CLI
error_reporting(0);
}
// Acquire lock on this file so we're not including it while it's being written in another process.
$handle = fopen($storageEngine->getRulesFile(), 'r');
$locked = $handle !== false && flock($handle, LOCK_SH);
@@ -337,6 +342,10 @@ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
flock($handle, LOCK_UN);
if ($handle !== false)
fclose($handle);
if (wfWAFUtils::isCli()) {
error_reporting($logLevel);
}
} else {
$wafRules = $storageEngine->getRules();
if (is_array($wafRules)) {

View File

@@ -14,6 +14,7 @@ echo wfView::create('scanner/issue-base', array(
'detailControls' => array(
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.deleteAdminUser(\'${id}\'); return false;" role="button">' . __('Delete User', 'wordfence') . '</a>',
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.revokeAdminUser(\'${id}\'); return false;" role="button">' . __('Revoke Capabilities', 'wordfence') . '</a>',
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.acknowledgeAdminUser(\'${id}\'); return false;" role="button">' . __('Acknowledge User', 'wordfence') . '</a>',
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed" role="button">' . __('Mark as Fixed', 'wordfence') . '</a>',
),
'textOutput' => (isset($textOutput) ? $textOutput : null),

View File

@@ -0,0 +1,22 @@
<?php
if (!defined('WORDFENCE_VERSION')) { exit; }
/**
* Presents an issue template.
*/
echo wfView::create('scanner/issue-base', array(
'internalType' => 'wfUpgradeError',
'displayType' => __('Update Check Error', 'wordfence'),
'iconSVG' => '<svg viewBox="0 0 20 20"><g><path d="M13.11 4.36L9.87 7.6 8 5.73l3.24-3.24c.35-.34 1.05-.2 1.56.32.52.51.66 1.21.31 1.55zm-8 1.77l.91-1.12 9.01 9.01-1.19.84c-.71.71-2.63 1.16-3.82 1.16H6.14L4.9 17.26c-.59.59-1.54.59-2.12 0-.59-.58-.59-1.53 0-2.12l1.24-1.24v-3.88c0-1.13.4-3.19 1.09-3.89zm7.26 3.97l3.24-3.24c.34-.35 1.04-.21 1.55.31.52.51.66 1.21.31 1.55l-3.24 3.25z"/></g></svg>',
'summaryControls' => array(wfView::create('scanner/issue-control-ignore', array('ignoreC' => __('Ignore Update', 'wordfence'))), wfView::create('scanner/issue-control-show-details')),
'detailPairs' => array(
__('Details', 'wordfence') => '{{html longMsg}}',
),
'detailControls' => array(
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed" role="button">' . __('Mark as Fixed', 'wordfence') . '</a>',
'<a href="' . esc_url(wfUtils::wpAdminURL('update-core.php')) . '" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-view-updates">' . __('View Updates', 'wordfence') . '</a>',
),
'textOutput' => (isset($textOutput) ? $textOutput : null),
'textOutputDetailPairs' => array(
__('Details', 'wordfence') => '$longMsg',
),
))->render();

View File

@@ -837,8 +837,10 @@ if (!is_dir(WFWAF_LOG_PATH)) {
try {
if (!defined('WFWAF_STORAGE_ENGINE') && (WF_IS_WP_ENGINE || WF_IS_FLYWHEEL)) {
if (!defined('WFWAF_STORAGE_ENGINE') && isset($_SERVER['WFWAF_STORAGE_ENGINE'])) {
define('WFWAF_STORAGE_ENGINE', $_SERVER['WFWAF_STORAGE_ENGINE']);
}
else if (!defined('WFWAF_STORAGE_ENGINE') && (WF_IS_WP_ENGINE || WF_IS_FLYWHEEL)) {
define('WFWAF_STORAGE_ENGINE', 'mysqli');
}
@@ -1019,10 +1021,15 @@ catch (Exception $e) { // In PHP 5, Throwable does not exist
);
}
catch (Throwable $t) {
error_log("An unexpected error occurred during WAF execution: {$t}");
$wf_waf_failure = array(
'throwable' => $t
);
error_log("An unexpected exception occurred during WAF execution: {$t}");
if (class_exists('ParseError') && $t instanceof ParseError) {
//Do nothing
}
else {
$wf_waf_failure = array(
'throwable' => $t
);
}
}
if (wfWAF::getInstance() === null) {
require_once __DIR__ . '/dummy.php';

View File

@@ -4,7 +4,7 @@ Plugin Name: Wordfence Security
Plugin URI: http://www.wordfence.com/
Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
Author: Wordfence
Version: 7.10.3
Version: 7.10.7
Author URI: http://www.wordfence.com/
Text Domain: wordfence
Domain Path: /languages
@@ -38,8 +38,8 @@ if(defined('WP_INSTALLING') && WP_INSTALLING){
if (!defined('ABSPATH')) {
exit;
}
define('WORDFENCE_VERSION', '7.10.3');
define('WORDFENCE_BUILD_NUMBER', '1690810710');
define('WORDFENCE_VERSION', '7.10.7');
define('WORDFENCE_BUILD_NUMBER', '1699289814');
define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
basename(dirname(__FILE__)) . '/' . basename(__FILE__));
@@ -124,4 +124,4 @@ if(! defined('WORDFENCE_VERSIONONLY_MODE')){ //Used to get version from file.
require_once(dirname(__FILE__) . '/lib/wordfenceConstants.php');
require_once(dirname(__FILE__) . '/lib/wordfenceClass.php');
wordfence::install_actions();
}
}