* },
* ...
* }
* }
* }
*/
if (!is_array($response) || !isset($response['records']) || !is_array($response['records'])) {
return array('ok' => 0, 'fail' => 1);
}
$secrets = $response['records'];
if (!isset($secrets['totp']) || !is_array($secrets['totp'])) {
return array('ok' => 0, 'fail' => 2);
}
$import = array();
foreach ($twoFactorUsers as &$t) {
if ($t[3] == 'activated') {
$user = new WP_User($t[0]);
if ($user instanceof WP_User && $user->exists()) {
if ((!isset($t[5]) || $t[5] != 'authenticator')) {
//Do nothing
}
else {
if (isset($secrets['totp'][$t[6]])) {
$import[$user->ID] = $secrets['totp'][$t[6]];
$import[$user->ID]['type'] = 'authenticator';
$total++;
}
}
}
}
}
$imported = WFLSPHP52Compatability::import_2fa($import);
}
catch (Exception $e) {
wordfence::status(4, 'error', sprintf(/* translators: Error message. */ __('2FA Migration Error: %s', 'wordfence'), $e->getMessage()));
return array('ok' => 0, 'fail' => 1);
}
wfConfig::remove('new2FAMigrationNonce');
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, true);
return array('ok' => 1, 'total' => $total, 'imported' => $imported);
}
//No legacy 2FA active, just set the option.
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, true);
return array('ok' => 1);
}
public static function ajax_switchTo2FAOld_callback() {
wfConfig::set(wfCredentialsController::DISABLE_LEGACY_2FA_OPTION, false);
return array('ok' => 1);
}
public static function validateProfileUpdate($errors, $update, $userData){
wordfence::validatePassword($errors, $userData);
}
public static function validatePassword($errors, $userData) {
$password = (isset($_POST['pass1']) && trim($_POST['pass1'])) ? $_POST['pass1'] : false;
$user_id = isset($userData->ID) ? $userData->ID : false;
$username = isset($_POST["user_login"]) ? $_POST["user_login"] : $userData->user_login;
if ($password == false) { return $errors; }
if ($errors->get_error_data("pass")) { return $errors; }
$enforceStrongPasswds = false;
if (wfConfig::get('loginSec_strongPasswds_enabled')) {
if (wfConfig::get('loginSec_strongPasswds') == 'pubs') {
if (user_can($user_id, 'publish_posts')) {
$enforceStrongPasswds = true;
}
}
else if (wfConfig::get('loginSec_strongPasswds') == 'all') {
$enforceStrongPasswds = true;
}
}
if ($enforceStrongPasswds && !wordfence::isStrongPasswd($password, $username)) {
$errors->add('pass', __('ERROR: The password could not be changed. Please choose a stronger password and try again. A strong password will follow these guidelines:
- At least 12 characters
- Uppercase and lowercase letters
- At least one symbol
- At least one number
- Avoid common words or sequences of letters/numbers
', 'wordfence'));
return $errors;
}
$twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
if (preg_match(self::$passwordCodePattern, $password) && is_array($twoFactorUsers) && count($twoFactorUsers) > 0) {
$errors->add('pass', __('Passwords containing a space followed by "wf" without quotes are not allowed.', 'wordfence'));
return $errors;
}
$enforceBreachedPasswds = false;
if (wfConfig::get('loginSec_breachPasswds_enabled')) {
if ($user_id !== false && wfConfig::get('loginSec_breachPasswds') == 'admins' && wfUtils::isAdmin($user_id)) {
$enforceBreachedPasswds = true;
}
else if ($user_id !== false && wfConfig::get('loginSec_breachPasswds') == 'pubs' && user_can($user_id, 'publish_posts')) {
$enforceBreachedPasswds = true;
}
}
if ($enforceBreachedPasswds && wfCredentialsController::isLeakedPassword($username, $password)) {
$errors->add('pass', sprintf(/* translators: Support URL. */ __('Please choose a different password. The password you are using exists on lists of passwords leaked in data breaches. Attackers use such lists to break into sites and install malicious code. Learn More', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_USING_BREACH_PASSWORD)));
return $errors;
}
else if ($user_id !== false) {
wfAdminNoticeQueue::removeAdminNotice(false, '2faBreachPassword', array($user_id));
wfAdminNoticeQueue::removeAdminNotice(false, 'previousIPBreachPassword', array($user_id));
wfCredentialsController::clearCachedCredentialStatus($userData);
}
return $errors;
}
public static function isStrongPasswd($passwd, $username ) {
$passwd = trim($passwd);
$lowerPasswd = strtolower($passwd);
$passwdLength = strlen($lowerPasswd);
if ($passwdLength < 12)
return false;
if ($lowerPasswd == strtolower( $username ) )
return false;
if (preg_match('/(?:password|passwd|mypass|wordpress)/i', $passwd))
return false;
if (preg_match('/(.)\1{2,}/', $lowerPasswd)) //Disallow any character repeated 3 or more times
return false;
/*
* Check for ordered sequences of at least 4 characters for alphabetic sequences and 3 characters for other sequences, ignoring case
* Examples:
* - 321
* - abcd
* - abab
*/
$last = null;
$sequenceLength = 1;
$alphabetic = true;
for ($i = 0; $i < $passwdLength; $i++) {
$current = ord($lowerPasswd[$i]);
if ($last !== null) {
if (abs($current - $last) === 1) {
$alphabetic &= ctype_alpha($lowerPasswd[$i]);
if (++$sequenceLength > ($alphabetic ? 3 : 2))
return false;
}
else {
$sequenceLength = 1;
$alphabetic = true;
}
}
$last = $current;
}
$characterTypes = array(
'/[a-z]/',
'/[A-Z]/',
'/[0-9]/',
'/[^a-zA-Z0-9]/'
);
foreach ($characterTypes as $type) {
if (!preg_match($type, $passwd))
return false;
}
return true;
}
public static function lostPasswordPost($errors = null, $user = null) {
$IP = wfUtils::getIP();
if ($request = self::getLog()->getCurrentRequest()) {
$request->action = 'lostPassword';
$request->save();
}
if (wfBlock::isWhitelisted($IP)) {
return;
}
$lockout = wfBlock::lockoutForIP(wfUtils::getIP());
if ($lockout !== false) {
$lockout->recordBlock();
$customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
require(dirname(__FILE__) . '/wfLockedOut.php');
}
if ($user === null) {
if (empty($_POST['user_login'])) { return; }
$user_login = $_POST['user_login'];
if (is_array($user_login)) { $user_login = wfUtils::array_first($user_login); }
$user_login = trim($user_login);
$user = get_user_by('login', $user_login);
if (!$user) {
$user = get_user_by('email', $user_login);
}
}
if ($user === false && wfConfig::get('loginSec_maskLoginErrors')) {
if (self::hasWoocommerce() && isset($_POST['wc_reset_password'], $_POST['user_login'])) {
$redirectUrl = add_query_arg('reset-link-sent', 'true', wc_get_account_endpoint_url('lost-password'));
}
else {
$redirectUrl = !empty($_REQUEST['redirect_to']) ? $_REQUEST['redirect_to'] : 'wp-login.php?checkemail=confirm';
}
wp_safe_redirect($redirectUrl);
exit;
}
if($user){
$alertCallback = array(new wfLostPasswdFormAlert($user, wfUtils::getIP()), 'send');
do_action('wordfence_security_event', 'lostPasswdForm', array(
'email' => $user->user_email,
'ip' => wfUtils::getIP(),
), $alertCallback);
}
// do not count password reset attempts if there is a user logged in with the edit_users capability
// because they're probably using the "send password reset" feature in the WP admin and therefore we shouldn't
// be locking them out!
if(wfConfig::get('loginSecurityEnabled') && !current_user_can( 'edit_users' ) ){
$tKey = self::getForgotPasswordFailureCountTransient($IP);
$forgotAttempts = get_transient($tKey);
if($forgotAttempts){
$forgotAttempts++;
} else {
$forgotAttempts = 1;
}
if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
self::lockOutIP($IP, sprintf(
/* translators: 1. Password reset limit (number). 2. WordPress username. */
__('Exceeded the maximum number of tries to recover their password which is set at: %1$s. The last username or email they entered before getting locked out was: \'%2$s\'', 'wordfence'),
wfConfig::get('loginSec_maxForgotPasswd'),
$_POST['user_login']
));
$customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
require(dirname(__FILE__) . '/wfLockedOut.php');
}
set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
}
}
public static function lockOutIP($IP, $reason) {
wfBlock::createLockout($reason, $IP, wfBlock::lockoutDuration(), time(), time(), 1);
self::getLog()->tagRequestForLockout($reason);
$alertCallback = array(new wfLoginLockoutAlert($IP, $reason), 'send');
do_action('wordfence_security_event', 'loginLockout', array(
'ip' => $IP,
'reason' => $reason,
'duration' => wfBlock::lockoutDuration(),
), $alertCallback);
}
public static function getLoginFailureCountTransient($IP) {
return 'wflginfl_' . bin2hex(wfUtils::inet_pton($IP));
}
public static function getForgotPasswordFailureCountTransient($IP) {
return 'wffgt_' . bin2hex(wfUtils::inet_pton($IP));
}
public static function clearLockoutCounters($IP) {
delete_transient(self::getLoginFailureCountTransient($IP));
delete_transient(self::getForgotPasswordFailureCountTransient($IP));
}
public static function veryFirstAction() {
/** @var wpdb $wpdb ; */
global $wpdb;
self::initProtection();
$wfFunc = wfUtils::array_get($_GET, '_wfsf');
if ($wfFunc == 'unlockEmail') {
$nonceValid = false;
if (isset($_POST['nonce']) && is_string($_POST['nonce'])) {
$nonceValid = wp_verify_nonce($_POST['nonce'], 'wf-form');
if (!$nonceValid && method_exists(wfWAF::getInstance(), 'createNonce')) {
$nonceValid = wfWAF::getInstance()->verifyNonce($_POST['nonce'], 'wf-form');
}
}
if(!$nonceValid){
die(__("Sorry but your browser sent an invalid security token when trying to use this form.", 'wordfence'));
}
$numTries = get_transient('wordfenceUnlockTries');
if($numTries > 10){
printf("%s
%s
",
esc_html__('Please wait 3 minutes and try again', 'wordfence'),
esc_html__('You have used this form too much. Please wait 3 minutes and try again.', 'wordfence')
);
exit();
}
if(! $numTries){ $numTries = 1; } else { $numTries = $numTries + 1; }
set_transient('wordfenceUnlockTries', $numTries, 180);
$email = trim(@$_POST['email']);
global $wpdb;
$ws = $wpdb->get_results($wpdb->prepare("SELECT ID, user_login FROM $wpdb->users WHERE user_email = %s", $email));
$found = false;
foreach($ws as $user){
$userDat = get_userdata($user->ID);
if(wfUtils::isAdmin($userDat)){
if($email == $userDat->user_email){
$found = true;
break;
}
}
}
if(! $found){
foreach(wfConfig::getAlertEmails() as $alertEmail){
if($alertEmail == $email){
$found = true;
break;
}
}
}
if($found){
$key = wfUtils::bigRandomHex();
$IP = wfUtils::getIP();
set_transient('wfunlock_' . $key, $IP, 1800);
$content = wfUtils::tmpl('email_unlockRequest.php', array(
'siteName' => get_bloginfo('name', 'raw'),
'siteURL' => wfUtils::getSiteBaseURL(),
'unlockHref' => wfUtils::getSiteBaseURL() . '?_wfsf=unlockAccess&key=' . $key,
'key' => $key,
'IP' => $IP
));
wp_mail($email, __("Unlock email requested", 'wordfence'), $content, "Content-Type: text/html");
}
echo "" . esc_html__('Your request was received', 'wordfence') . "
" .
esc_html(sprintf(/* translators: Email address. */ __("We received a request to email \"%s\" instructions to unlock their access. If that is the email address of a site administrator or someone on the Wordfence alert list, they have been emailed instructions on how to regain access to this system. The instructions we sent will expire 30 minutes from now.", 'wordfence'), wp_kses($email, array())))
. "
";
exit();
} else if($wfFunc == 'unlockAccess'){
if (!preg_match('/^(?:(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9](?::|$)){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))$/i', get_transient('wfunlock_' . $_GET['key']))) {
_e("Invalid key provided for authentication.", 'wordfence');
exit();
}
if($_GET['func'] == 'unlockMyIP'){
wfBlock::unblockIP(wfUtils::getIP());
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
exit();
} else if($_GET['func'] == 'unlockAllIPs'){
wordfence::status(1, 'info', __("Request received via unlock email link to unblock all IPs.", 'wordfence'));
wfBlock::removeAllIPBlocks();
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
exit();
} else if($_GET['func'] == 'disableRules'){
wfConfig::set('firewallEnabled', 0);
wfConfig::set('loginSecurityEnabled', 0);
wordfence::status(1, 'info', __("Request received via unlock email link to unblock all IPs via disabling firewall rules.", 'wordfence'));
wfBlock::removeAllIPBlocks();
wfBlock::removeAllCountryBlocks();
if (class_exists('wfWAFIPBlocksController')) { wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(); }
self::clearLockoutCounters(wfUtils::getIP());
header('Location: ' . wp_login_url());
exit();
} else {
_e("Invalid function specified. Please check the link we emailed you and make sure it was not cut-off by your email reader.", 'wordfence');
exit();
}
}
else if ($wfFunc == 'detectProxy') {
wfUtils::doNotCache();
if (wfUtils::processDetectProxyCallback()) {
self::getLog()->getCurrentRequest()->action = 'scan:detectproxy'; //Exempt a valid callback from live traffic
echo wfConfig::get('detectProxyRecommendation', '-');
}
else {
echo '0';
}
exit();
}
else if ($wfFunc == 'removeAlertEmail') {
wfUtils::doNotCache();
$payloadStatus = false;
$jwt = (isset($_GET['jwt']) && is_string($_GET['jwt'])) ? $_GET['jwt'] : '';
if (!empty($jwt)) {
$payload = wfUtils::decodeJWT($jwt);
if ($payload && isset($payload['email'])) {
$payloadStatus = true;
}
}
if (isset($_POST['resend'])) {
$email = trim(wfUtils::array_get($_POST, 'email', ''));
$found = false;
$alertEmails = wfConfig::getAlertEmails();
foreach ($alertEmails as $e) {
if ($e == $email) {
$found = true;
break;
}
}
if ($found) {
$content = wfUtils::tmpl('email_unsubscribeRequest.php', array(
'siteName' => get_bloginfo('name', 'raw'),
'siteURL' => wfUtils::getSiteBaseURL(),
'IP' => wfUtils::getIP(),
'jwt' => wfUtils::generateJWT(array('email' => $email)),
));
wp_mail($email, __("Unsubscribe Requested", 'wordfence'), $content, "Content-Type: text/html");
}
echo wfView::create('common/unsubscribe', array(
'state' => 'resent',
))->render();
exit();
}
else if (!$payloadStatus) {
echo wfView::create('common/unsubscribe', array(
'state' => 'bad',
))->render();
exit();
}
else if (isset($_POST['confirm'])) {
$confirm = wfUtils::truthyToBoolean($_POST['confirm']);
if ($confirm) {
$found = false;
$alertEmails = wfConfig::getAlertEmails();
$updatedAlertEmails = array();
foreach ($alertEmails as $alertEmail) {
if ($alertEmail == $payload['email']) {
$found = true;
}
else {
$updatedAlertEmails[] = $alertEmail;
}
}
if ($found) {
wfConfig::set('alertEmails', implode(',', $updatedAlertEmails));
}
echo wfView::create('common/unsubscribe', array(
'jwt' => $_GET['jwt'],
'email' => $payload['email'],
'state' => 'unsubscribed',
))->render();
exit();
}
}
echo wfView::create('common/unsubscribe', array(
'jwt' => $_GET['jwt'],
'email' => $payload['email'],
'state' => 'prompt',
))->render();
exit();
}
else if ($wfFunc == 'installLicense') {
if (wfUtils::isAdmin()) {
wfUtils::doNotCache();
if (isset($_POST['license'])) {
$nonceValid = wp_verify_nonce(@$_POST['nonce'], 'wf-form');
if (!$nonceValid) {
die(__('Sorry but your browser sent an invalid security token when trying to use this form.', 'wordfence'));
}
$changes = array('apiKey' => $_POST['license']);
$errors = wfConfig::validate($changes);
if ($errors !== true) {
$error = __('An error occurred while saving the license.', 'wordfence');
if (count($errors) == 1) {
$error = sprintf(/* translators: Error message. */ __('An error occurred while saving the license: %s', 'wordfence'), $errors[0]['error']);
}
echo wfView::create('common/license', array(
'state' => 'bad',
'error' => $error,
))->render();
exit();
}
try {
wfConfig::save(wfConfig::clean($changes));
echo wfView::create('common/license', array(
'state' => 'installed',
))->render();
exit();
}
catch (Exception $e) {
echo wfView::create('common/license', array(
'state' => 'bad',
'error' => sprintf(/* translators: Error message. */ __('An error occurred while saving the license: %s', 'wordfence'), $e->getMessage()),
))->render();
exit();
}
}
echo wfView::create('common/license', array(
'state' => 'prompt',
))->render();
exit();
}
}
if (is_main_site() && wfUtils::isAdmin()) {
if (wp_next_scheduled('wordfence_daily_cron') === false) {
wp_schedule_event(time() + 600, 'daily', 'wordfence_daily_cron');
wordfence::status(2, 'info', __("Rescheduled missing daily cron", 'wordfence'));
}
if (wp_next_scheduled('wordfence_hourly_cron') === false) {
wp_schedule_event(time() + 600, 'hourly', 'wordfence_hourly_cron');
wordfence::status(2, 'info', __("Rescheduled missing hourly cron", 'wordfence'));
}
}
// Sync the WAF data with the database.
if (!WFWAF_SUBDIRECTORY_INSTALL && $waf = wfWAF::getInstance()) {
$homeurl = wfUtils::wpHomeURL();
$siteurl = wfUtils::wpSiteURL();
//Sync the GeoIP database if needed
$destination = WFWAF_LOG_PATH . '/GeoLite2-Country.mmdb';
if (!file_exists($destination) || wfConfig::get('needsGeoIPSync')) {
$allowSync = false;
if (wfConfig::createLock('wfSyncGeoIP')) {
$status = get_transient('wfSyncGeoIPActive');
if (!$status) {
$allowSync = true;
set_transient('wfSyncGeoIPActive', true, 3600);
}
wfConfig::releaseLock('wfSyncGeoIP');
}
if ($allowSync) {
wfUtils::requireIpLocator();
try {
$wflogsLocator = wfIpLocator::getInstance(wfIpLocator::SOURCE_WFLOGS);
$bundledLocator = wfIpLocator::getInstance(wfIpLocator::SOURCE_BUNDLED);
if (!$wflogsLocator->isPreferred() || $wflogsLocator->getDatabaseVersion() !== $bundledLocator->getDatabaseVersion()) {
$source = dirname(__FILE__) . '/GeoLite2-Country.mmdb';
if (copy($source, $destination)) {
$shash = '';
$dhash = '';
$sp = @fopen($source, "rb");
if ($sp) {
$scontext = hash_init('sha256');
while (!feof($sp)) {
$data = fread($sp, 65536);
if ($data === false) {
$scontext = false;
break;
}
hash_update($scontext, $data);
}
fclose($sp);
if ($scontext !== false) {
$shash = hash_final($scontext, false);
}
}
$dp = @fopen($destination, "rb");
if ($dp) {
$dcontext = hash_init('sha256');
while (!feof($dp)) {
$data = fread($dp, 65536);
if ($data === false) {
$dcontext = false;
break;
}
hash_update($dcontext, $data);
}
fclose($dp);
if ($scontext !== false) {
$dhash = hash_final($dcontext, false);
}
}
if (hash_equals($shash, $dhash)) {
wfConfig::remove('needsGeoIPSync');
delete_transient('wfSyncGeoIPActive');
}
}
}
else {
wfConfig::remove('needsGeoIPSync');
delete_transient('wfSyncGeoIPActive');
}
}
catch (Exception $e) {
//Ignore
}
}
}
try {
$sapi = @php_sapi_name();
if ($sapi != "cli") {
$lastPermissionsTemplateCheck = wfConfig::getInt('lastPermissionsTemplateCheck', 0);
if (defined('WFWAF_LOG_PATH') && ($lastPermissionsTemplateCheck + 43200) < time()) { //Run no more frequently than every 12 hours
$timestamp = preg_replace('/[^0-9]/', '', microtime(false)); //We avoid using tmpfile since it can potentially create one with different permissions than the defaults
$tmpTemplate = rtrim(WFWAF_LOG_PATH, '/') . "/template.{$timestamp}.tmp";
$template = rtrim(WFWAF_LOG_PATH, '/') . '/template.php';
@unlink($tmpTemplate);
@file_put_contents($tmpTemplate, "\n");
$tmpStat = @stat($tmpTemplate);
if ($tmpStat !== false) {
$mode = $tmpStat[2] & 0777;
$updatedMode = 0600;
if (($mode & 0020) == 0020) { //Group writable
$updatedMode = $updatedMode | 0060;
}
if (defined('WFWAF_LOG_FILE_MODE')) {
$updatedMode = WFWAF_LOG_FILE_MODE;
}
$stat = @stat($template);
if ($stat === false || ($stat[2] & 0777) != $updatedMode) {
@chmod($tmpTemplate, $updatedMode);
@unlink($template);
@rename($tmpTemplate, $template);
}
@unlink($tmpTemplate);
}
else {
@unlink($tmpTemplate);
}
wfConfig::set('lastPermissionsTemplateCheck', time());
@chmod(WFWAF_LOG_PATH, (wfWAFWordPress::permissions() | 0755));
wfWAFWordPress::writeHtaccess();
$contents = self::_wflogsContents();
if ($contents) {
$validFiles = wfWAF::getInstance()->fileList();
foreach ($validFiles as &$vf) {
$vf = basename($vf);
}
$validFiles = array_filter($validFiles);
$previousWflogsFileList = wfConfig::getJSON('previousWflogsFileList', array());
$wflogs = realpath(WFWAF_LOG_PATH);
$filesRemoved = array();
foreach ($contents as $f) {
if (!in_array($f, $validFiles) && in_array($f, $previousWflogsFileList)) {
$fullPath = $f;
$removed = self::_recursivelyRemoveWflogs($f);
$filesRemoved = array_merge($filesRemoved, $removed);
}
}
$contents = self::_wflogsContents();
wfConfig::setJSON('previousWflogsFileList', $contents);
if (!empty($filesRemoved)) {
$removalHistory = wfConfig::getJSON('diagnosticsWflogsRemovalHistory', array());
$removalHistory = array_slice($removalHistory, 0, 4);
array_unshift($removalHistory, array(time(), $filesRemoved));
wfConfig::setJSON('diagnosticsWflogsRemovalHistory', $removalHistory);
}
}
}
}
}
catch (Exception $e) {
//Ignore
}
try {
$configDefaults = array(
'apiKey' => wfConfig::get('apiKey'),
'isPaid' => !!wfConfig::get('isPaid'),
'siteURL' => $siteurl,
'homeURL' => $homeurl,
'whitelistedIPs' => (string) wfConfig::get('whitelisted'),
'whitelistedServiceIPs' => json_encode(wfUtils::whitelistedServiceIPs()),
'howGetIPs' => (string) wfConfig::get('howGetIPs'),
'howGetIPs_trusted_proxies_unified' => implode("\n", wfUtils::unifiedTrustedProxies()),
'detectProxyRecommendation' => (string) wfConfig::get('detectProxyRecommendation'),
'other_WFNet' => !!wfConfig::get('other_WFNet', true),
'pluginABSPATH' => ABSPATH,
'serverIPs' => json_encode(wfUtils::serverIPs()),
'blockCustomText' => wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', ''))),
'disableWAFIPBlocking' => wfConfig::get('disableWAFIPBlocking'),
'wordpressVersion' => wfConfig::get('wordpressVersion'),
'wordpressPluginVersions' => wfConfig::get_ser('wordpressPluginVersions'),
'wordpressThemeVersions' => wfConfig::get_ser('wordpressThemeVersions'),
'WPLANG' => get_site_option('WPLANG'),
);
if (wfUtils::isAdmin()) {
$errorNonceKey = 'errorNonce_' . get_current_user_id();
$configDefaults[$errorNonceKey] = wp_create_nonce('wf-waf-error-page'); //Used by the AJAX watcher script
}
foreach ($configDefaults as $key => $value) {
$waf->getStorageEngine()->setConfig($key, $value, 'synced');
}
if (wfConfig::get('timeoffset_wf') !== false) {
$waf->getStorageEngine()->setConfig('timeoffset_wf', wfConfig::get('timeoffset_wf'), 'synced');
}
else {
$waf->getStorageEngine()->unsetConfig('timeoffset_wf', 'synced');
}
if (class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
if (wfUtils::isAdmin()) {
if ($waf->getStorageEngine()->getConfig('wafStatus', '') == 'learning-mode') {
if ($waf->getStorageEngine()->getConfig('learningModeGracePeriodEnabled', false)) {
if ($waf->getStorageEngine()->getConfig('learningModeGracePeriod', 0) <= time()) {
// Reached the end of the grace period, activate the WAF.
$waf->getStorageEngine()->setConfig('wafStatus', 'enabled');
$waf->getStorageEngine()->setConfig('learningModeGracePeriodEnabled', 0);
$waf->getStorageEngine()->unsetConfig('learningModeGracePeriod');
$firewall = new wfFirewall();
$firewall->syncStatus(true);
}
}
}
}
if (empty($_GET['wordfence_syncAttackData'])) {
$table_wfHits = wfDB::networkTable('wfHits');
if ($waf->getStorageEngine() instanceof wfWAFStorageMySQL) {
$lastAttackMicroseconds = floatval($waf->getStorageEngine()->getConfig('lastAttackDataTruncateTime'));
} else {
$lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$table_wfHits}");
}
if (get_site_option('wordfence_lastSyncAttackData', 0) < time() - 8) {
if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
if (get_site_option('wordfence_syncingAttackData') <= time() - 60) {
// Could be the request to itself is not completing, add ajax to the head as a workaround
$attempts = get_site_option('wordfence_syncAttackDataAttempts', 0);
if ($attempts > 10) {
add_action('wp_head', 'wordfence::addSyncAttackDataAjax');
add_action('login_head', 'wordfence::addSyncAttackDataAjax');
add_action('admin_head', 'wordfence::addSyncAttackDataAjax');
} else {
update_site_option('wordfence_syncAttackDataAttempts', ++$attempts);
wp_remote_post(add_query_arg('wordfence_syncAttackData', microtime(true), home_url('/')), array(
'timeout' => 0.01,
'blocking' => false,
'sslverify' => apply_filters('https_local_ssl_verify', false)
));
}
}
}
}
}
if ($waf instanceof wfWAFWordPress && ($learningModeAttackException = $waf->getLearningModeAttackException())) {
$log = self::getLog();
$log->initLogRequest();
$request = $log->getCurrentRequest();
$request->action = 'learned:waf';
$request->attackLogTime = microtime(true);
$ruleIDs = array();
/** @var wfWAFRule $failedRule */
foreach ($learningModeAttackException->getFailedRules() as $failedRule) {
$ruleIDs[] = $failedRule->getRuleID();
}
$actionData = array(
'learningMode' => 1,
'failedRules' => $ruleIDs,
'paramKey' => $learningModeAttackException->getParamKey(),
'paramValue' => $learningModeAttackException->getParamValue(),
);
if ($ruleIDs && $ruleIDs[0]) {
$rule = $waf->getRule($ruleIDs[0]);
if ($rule) {
$request->actionDescription = $rule->getDescription();
$actionData['category'] = $rule->getCategory();
$actionData['ssl'] = $waf->getRequest()->getProtocol() === 'https';
$actionData['fullRequest'] = base64_encode($waf->getRequest());
}
}
$request->actionData = wfRequestModel::serializeActionData($actionData);
register_shutdown_function(array($request, 'save'));
self::scheduleSendAttackData();
}
} catch (wfWAFStorageFileException $e) {
// We don't have anywhere to write files in this scenario.
} catch (wfWAFStorageEngineMySQLiException $e) {
// Ignore and continue
}
}
if(wfConfig::get('firewallEnabled')){
$wfLog = self::getLog();
$wfLog->firewallBadIPs();
$IP = wfUtils::getIP();
if (wfBlock::isWhitelisted($IP)) {
return;
}
if (wfConfig::get('neverBlockBG') == 'neverBlockUA' && wfCrawl::isGoogleCrawler()) {
return;
}
if (wfConfig::get('neverBlockBG') == 'neverBlockVerified' && wfCrawl::isVerifiedGoogleCrawler()) {
return;
}
if (wfConfig::get('bannedURLs', false)) {
$URLs = explode("\n", wfUtils::cleanupOneEntryPerLine(wfConfig::get('bannedURLs')));
foreach ($URLs as $URL) {
if (preg_match(wfUtils::patternToRegex($URL, ''), $_SERVER['REQUEST_URI'])) {
$reason = __('Accessed a banned URL', 'wordfence');
wfBlock::createIP($reason, $IP, wfBlock::blockDuration(), time(), time(), 1, wfBlock::TYPE_IP_AUTOMATIC_TEMPORARY);
wfActivityReport::logBlockedIP($IP, null, 'bannedurl');
$wfLog->tagRequestForBlock($reason);
$wfLog->do503(3600, __("Accessed a banned URL", 'wordfence'));
//exits
}
}
}
if (wfConfig::get('other_blockBadPOST') == '1' && $_SERVER['REQUEST_METHOD'] == 'POST' && empty($_SERVER['HTTP_USER_AGENT']) && empty($_SERVER['HTTP_REFERER'])) {
$reason = __('POST received with blank user-agent and referer', 'wordfence');
wfBlock::createIP($reason, $IP, wfBlock::blockDuration(), time(), time(), 1, wfBlock::TYPE_IP_AUTOMATIC_TEMPORARY);
wfActivityReport::logBlockedIP($IP, null, 'badpost');
$wfLog->tagRequestForBlock($reason);
$wfLog->do503(3600, __("POST received with blank user-agent and referer", 'wordfence'));
//exits
}
}
}
private static function _wflogsContents() {
$dir = opendir(WFWAF_LOG_PATH);
if ($dir) {
$contents = array();
while ($path = readdir($dir)) {
if ($path == '.' || $path == '..') { continue; }
$contents[] = $path;
}
closedir($dir);
return $contents;
}
return false;
}
/**
* Removes a path within wflogs, recursing as necessary.
*
* @param string $file
* @param array $processedDirs
* @return array The list of removed files/folders.
*/
private static function _recursivelyRemoveWflogs($file, $processedDirs = array()) {
if (preg_match('~(?:^|/|\\\\)\.\.(?:/|\\\\|$)~', $file)) {
return array();
}
if (stripos(WFWAF_LOG_PATH, 'wflogs') === false) { //Sanity check -- if not in a wflogs folder, user will have to do removal manually
return array();
}
$path = rtrim(WFWAF_LOG_PATH, '/') . '/' . $file;
if (is_link($path)) {
if (@unlink($path)) {
return array($file);
}
return array();
}
if (is_dir($path)) {
$real = realpath($file);
if (in_array($real, $processedDirs)) {
return array();
}
$processedDirs[] = $real;
$count = 0;
$dir = opendir($path);
if ($dir) {
$contents = array();
while ($sub = readdir($dir)) {
if ($sub == '.' || $sub == '..') { continue; }
$contents[] = $sub;
}
closedir($dir);
$filesRemoved = array();
foreach ($contents as $f) {
$removed = self::_recursivelyRemoveWflogs($file . '/' . $f, $processedDirs);
$filesRemoved = array($filesRemoved, $removed);
}
}
if (@rmdir($path)) {
$filesRemoved[] = $file;
}
return $filesRemoved;
}
if (@unlink($path)) {
return array($file);
}
return array();
}
public static function loginAction($username){
if(sizeof($_POST) < 1){ return; } //only execute if login form is posted
if(! $username){ return; }
wfConfig::inc('totalLogins');
$user = get_user_by('login', $username);
$userID = $user ? $user->ID : 0;
self::getLog()->logLogin('loginOK', 0, $username);
if(wfUtils::isAdmin($user)){
wfConfig::set_ser('lastAdminLogin', array(
'userID' => $userID,
'username' => $username,
'firstName' => $user->first_name,
'lastName' => $user->last_name,
'time' => wfUtils::localHumanDateShort(),
'IP' => wfUtils::getIP()
));
}
$salt = wp_salt('logged_in');
//TODO: Drop support for legacy cookie after 1 year
$legacyCookieName = 'wf_loginalerted_' . hash_hmac('sha256', wfUtils::getIP() . '|' . $user->ID, $salt);
$cookieName = 'wf_loginalerted_' . hash_hmac('sha256', $user->ID, $salt);
$cookieValue = hash_hmac('sha256', $user->user_login, $salt);
$newDevice = !(isset($_COOKIE[$legacyCookieName]) && hash_equals($cookieValue, $_COOKIE[$legacyCookieName])); //Check legacy cookie
if($newDevice){
$newDevice = !(isset($_COOKIE[$cookieName]) && hash_equals($cookieValue, $_COOKIE[$cookieName]));
}
else{
$_COOKIE[$cookieName]=$cookieValue;
}
if(wfUtils::isAdmin($userID)){
$securityEvent = 'adminLogin';
$alertCallback = array(new wfAdminLoginAlert($cookieName, $cookieValue, $username, wfUtils::getIP()), 'send');
} else {
$securityEvent = 'nonAdminLogin';
$alertCallback = array(new wfNonAdminLoginAlert($cookieName, $cookieValue, $username, wfUtils::getIP()), 'send');
}
if($newDevice)
$securityEvent.='NewLocation';
do_action('wordfence_security_event', $securityEvent, array(
'username' => $username,
'ip' => wfUtils::getIP(),
), $alertCallback);
if (wfConfig::get(wfUtils::isAdmin($userID)?'alertOn_firstAdminLoginOnly':'alertOn_firstNonAdminLoginOnly')) {
//Purge legacy cookie if still present
if(array_key_exists($legacyCookieName, $_COOKIE))
wfUtils::setcookie($legacyCookieName, '', 1, '/', null, wfUtils::isFullSSL(), true);
wfUtils::setcookie($cookieName, $cookieValue, time() + (86400 * 365), '/', null, wfUtils::isFullSSL(), true);
}
}
public static function registrationFilter($errors, $sanitizedLogin, $userEmail) {
if (wfConfig::get('loginSec_blockAdminReg') && $sanitizedLogin == 'admin') {
$errors->add('user_login_error', __('ERROR: You can\'t register using that username', 'wordfence'));
}
return $errors;
}
public static function wooRegistrationFilter($wooCustomerData) {
/*
$wooCustomerData matches:
array(
'user_login' => $username,
'user_pass' => $password,
'user_email' => $email,
'role' => 'customer',
)
*/
if (wfConfig::get('loginSec_blockAdminReg') && is_array($wooCustomerData) && isset($wooCustomerData['user_login']) && isset($wooCustomerData['user_email']) && preg_match('/^admin\d*$/i', $wooCustomerData['user_login'])) {
//Converts a username of `admin` generated from something like `admin@example.com` to `adminexample`
$emailComponents = explode('@', $wooCustomerData['user_email']);
if (strpos(wfUtils::array_last($emailComponents), '.') === false) { //e.g., admin@localhost
$wooCustomerData['user_login'] .= wfUtils::array_last($emailComponents);
}
else { //e.g., admin@example.com
$hostComponents = explode('.', wfUtils::array_last($emailComponents));
array_pop($hostComponents);
$wooCustomerData['user_login'] .= wfUtils::array_last($hostComponents);
}
//If it's still `admin` at this point, it will fall through and get blocked by wordfence::blacklistedUsernames
}
return $wooCustomerData;
}
public static function oembedAuthorFilter($data, $post, $width, $height) {
unset($data['author_name']);
unset($data['author_url']);
return $data;
}
public static function jsonAPIAuthorFilter($response, $handler, $request) {
$route = $request->get_route();
if (!current_user_can('edit_others_posts')) {
$urlBase = wfWP_REST_Users_Controller::wfGetURLBase();
if (preg_match('~' . preg_quote($urlBase, '~') . '/*$~i', $route)) {
$error = new WP_Error('rest_user_cannot_view', __('Sorry, you are not allowed to list users.', 'wordfence'), array('status' => rest_authorization_required_code()));
$response = rest_ensure_response($error);
if (!defined('WORDFENCE_REST_API_SUPPRESSED')) { define('WORDFENCE_REST_API_SUPPRESSED', true); }
}
else if (preg_match('~' . preg_quote($urlBase, '~') . '/+(\d+)/*$~i', $route, $matches)) {
$id = (int) $matches[1];
if (get_current_user_id() !== $id) {
$error = new WP_Error('rest_user_invalid_id', __('Invalid user ID.', 'wordfence'), array('status' => 404));
$response = rest_ensure_response($error);
if (!defined('WORDFENCE_REST_API_SUPPRESSED')) { define('WORDFENCE_REST_API_SUPPRESSED', true); }
}
}
}
return $response;
}
public static function jsonAPIAdjustHeaders($response, $server, $request) {
if (defined('WORDFENCE_REST_API_SUPPRESSED')) {
$response->header('Allow', 'GET');
}
return $response;
}
public static function wpSitemapUserProviderFilter($provider, $name) {
if ($name === 'users') {
return false;
}
return $provider;
}
public static function _filterCentralFromLiveTraffic($dispatch_result, $request, $route, $handler) {
if (preg_match('~^/wordfence/v\d+/~i', $route)) {
self::getLog()->canLogHit = false;
}
return $dispatch_result;
}
public static function showTwoFactorField() {
$existingContents = ob_get_contents();
if (!preg_match('/wftwofactornonce:([0-9]+)\/(.+?)\s/', $existingContents, $matches)) {
return;
}
$userID = intval($matches[1]);
$twoFactorNonce = preg_replace('/[^a-f0-9]/i', '', $matches[2]);
if (!self::verifyTwoFactorIntermediateValues($userID, $twoFactorNonce)) {
return;
}
//Strip out the username and password fields
$formPosition = strrpos($existingContents, '