scanner/wfScanner.php 0000644 00000126030 14720676271 0010652 0 ustar 00 $oldVersions,
'scansEnabled_wafStatus' => $wafStatus,
));
}
/**
* Returns an array of the scan options (as keys) and the corresponding value for the limited scan type.
*
* @return array
*/
public static function limitedScanTypeOptions() {
return array_merge(self::_inactiveScanOptions(), array(
'scansEnabled_checkHowGetIPs' => true,
'scansEnabled_malware' => true,
'scansEnabled_fileContents' => true,
'scansEnabled_fileContentsGSB' => true,
'scansEnabled_suspiciousOptions' => true,
'scansEnabled_oldVersions' => true,
'scansEnabled_wafStatus' => true,
'lowResourceScansEnabled' => true,
'scan_exclude' => wfConfig::get('scan_exclude', ''),
'scan_include_extra' => wfConfig::get('scan_include_extra', ''),
'scansEnabled_geoipSupport' => true,
));
}
/**
* Returns an array of the scan options (as keys) and the corresponding value for the standard scan type.
*
* @return array
*/
public static function standardScanTypeOptions() {
return array_merge(self::_inactiveScanOptions(), array(
'spamvertizeCheck' => true,
'checkSpamIP' => true,
'scansEnabled_checkGSB' => true,
'scansEnabled_checkHowGetIPs' => true,
'scansEnabled_checkReadableConfig' => true,
'scansEnabled_suspectedFiles' => true,
'scansEnabled_core' => true,
'scansEnabled_coreUnknown' => true,
'scansEnabled_malware' => true,
'scansEnabled_fileContents' => true,
'scansEnabled_fileContentsGSB' => true,
'scansEnabled_posts' => true,
'scansEnabled_comments' => true,
'scansEnabled_suspiciousOptions' => true,
'scansEnabled_oldVersions' => true,
'scansEnabled_suspiciousAdminUsers' => true,
'scansEnabled_passwds' => true,
'scansEnabled_diskSpace' => true,
'scansEnabled_wafStatus' => true,
'scan_exclude' => wfConfig::get('scan_exclude', ''),
'scan_include_extra' => wfConfig::get('scan_include_extra', ''),
'scansEnabled_geoipSupport' => true,
));
}
/**
* Returns an array of the scan options (as keys) and the corresponding value for the high sensitivity scan type.
*
* @return array
*/
public static function highSensitivityScanTypeOptions() {
return array_merge(self::_inactiveScanOptions(), array(
'spamvertizeCheck' => true,
'checkSpamIP' => true,
'scansEnabled_checkGSB' => true,
'scansEnabled_checkHowGetIPs' => true,
'scansEnabled_checkReadableConfig' => true,
'scansEnabled_suspectedFiles' => true,
'scansEnabled_core' => true,
'scansEnabled_themes' => true,
'scansEnabled_plugins' => true,
'scansEnabled_coreUnknown' => true,
'scansEnabled_malware' => true,
'scansEnabled_fileContents' => true,
'scansEnabled_fileContentsGSB' => true,
'scansEnabled_posts' => true,
'scansEnabled_comments' => true,
'scansEnabled_suspiciousOptions' => true,
'scansEnabled_oldVersions' => true,
'scansEnabled_suspiciousAdminUsers' => true,
'scansEnabled_passwds' => true,
'scansEnabled_diskSpace' => true,
'scansEnabled_wafStatus' => true,
'other_scanOutside' => true,
'scansEnabled_scanImages' => true,
'scan_exclude' => wfConfig::get('scan_exclude', ''),
'scan_include_extra' => wfConfig::get('scan_include_extra', ''),
'scansEnabled_geoipSupport' => true,
));
}
/**
* Returns an array of the scan options (as keys) and the corresponding value for the custom scan type.
*
* @return array
*/
public static function customScanTypeOptions() {
$allOptions = self::_inactiveScanOptions();
foreach ($allOptions as $key => &$value) {
$value = wfConfig::get($key);
}
$allOptions['scansEnabled_geoipSupport'] = true;
$allOptions['scansEnabled_highSense'] = false; //deprecated
return $allOptions;
}
/**
* Returns an array of scan options and their inactive values for convenience in merging with the various scan type
* option arrays.
*
* @return array
*/
protected static function _inactiveScanOptions() {
return array(
'spamvertizeCheck' => false,
'checkSpamIP' => false,
'scansEnabled_checkGSB' => false,
'scansEnabled_checkHowGetIPs' => false,
'scansEnabled_checkReadableConfig' => false,
'scansEnabled_suspectedFiles' => false,
'scansEnabled_core' => false,
'scansEnabled_themes' => false,
'scansEnabled_plugins' => false,
'scansEnabled_coreUnknown' => false,
'scansEnabled_malware' => false,
'scansEnabled_fileContents' => false,
'scan_include_extra' => '',
'scansEnabled_fileContentsGSB' => false,
'scansEnabled_posts' => false,
'scansEnabled_comments' => false,
'scansEnabled_suspiciousOptions' => false,
'scansEnabled_oldVersions' => false,
'scansEnabled_suspiciousAdminUsers' => false,
'scansEnabled_passwds' => false,
'scansEnabled_diskSpace' => false,
'scansEnabled_wafStatus' => false,
'other_scanOutside' => false,
'scansEnabled_scanImages' => false,
'scansEnabled_highSense' => false,
'lowResourceScansEnabled' => false,
'scan_exclude' => '',
'scansEnabled_geoipSupport' => false,
);
}
/**
* Returns the scan options only available to premium users.
*
* @return array
*/
protected static function _premiumScanOptions() {
return array('spamvertizeCheck', 'checkSpamIP', 'scansEnabled_checkGSB');
}
/**
* Returns an array of weights for calculating the scan option status score.
*
* @return array
*/
protected static function _scanOptionWeights() {
return array(
'spamvertizeCheck' => 0.05,
'checkSpamIP' => 0.05,
'scansEnabled_checkGSB' => 0.05,
'scansEnabled_checkHowGetIPs' => 0.05,
'scansEnabled_checkReadableConfig' => 0.05,
'scansEnabled_suspectedFiles' => 0.05,
'scansEnabled_core' => 0.05,
'scansEnabled_themes' => 0,
'scansEnabled_plugins' => 0,
'scansEnabled_coreUnknown' => 0.05,
'scansEnabled_malware' => 0.05,
'scansEnabled_fileContents' => 0.1,
'scan_include_extra' => 0,
'scansEnabled_fileContentsGSB' => 0.05,
'scansEnabled_posts' => 0.05,
'scansEnabled_comments' => 0.05,
'scansEnabled_suspiciousOptions' => 0.05,
'scansEnabled_oldVersions' => 0.1,
'scansEnabled_suspiciousAdminUsers' => 0.05,
'scansEnabled_passwds' => 0.05,
'scansEnabled_diskSpace' => 0.05,
'other_scanOutside' => 0,
'scansEnabled_scanImages' => 0,
'scansEnabled_highSense' => 0,
'lowResourceScansEnabled' => 0,
'scan_exclude' => 0,
'scansEnabled_geoipSupport' => 0,
'scansEnabled_wafStatus' => 0,
);
}
/**
* wfScanner constructor.
* @param int|bool $scanType If false, defaults to the config option `scanType`.
*/
public function __construct($scanType = false) {
if ($scanType === false || !self::isValidScanType($scanType)) {
$this->_scanType = wfConfig::get('scanType');
}
else {
$this->_scanType = $scanType;
}
}
/**
* Returns whether or not the scanner will run as premium.
*
* @return bool
*/
public function isPremiumScan() {
return !!wfConfig::get('isPaid');
}
/**
* Returns whether or not automatic scans will run.
*
* @return bool
*/
public function isEnabled() {
return !!wfConfig::get('scheduledScansEnabled');
}
/**
* Returns whether or not a scan is running. A scan is considered running if the timestamp
* under wf_scanRunning is within WORDFENCE_MAX_SCAN_LOCK_TIME seconds of now.
*
* @return bool
*/
public function isRunning() {
$scanRunning = wfConfig::get('wf_scanRunning');
return ($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_LOCK_TIME);
}
/**
* Returns the current scan scheduling mode.
*
* @return string One of the SCAN_SCHEDULING_MODE_ constants
*/
public function schedulingMode() {
if (wfConfig::get('isPaid') && wfConfig::get('schedMode') == 'manual') {
return self::SCAN_SCHEDULING_MODE_MANUAL;
}
return self::SCAN_SCHEDULING_MODE_AUTOMATIC;
}
/**
* Returns the manual scheduling type. This is only applicable when the scheduling mode is
* SCAN_SCHEDULING_MODE_MANUAL.
*
* @return string One of the MANUAL_SCHEDULING_ constants.
*/
public function manualSchedulingType() {
return wfConfig::get('manualScanType', self::MANUAL_SCHEDULING_ONCE_DAILY);
}
/**
* Returns the start hour used for non-custom manual schedules. This is initially random but may be modified
* by the user later.
*
* @return int An hour number.
*/
public function manualSchedulingStartHour() {
return wfConfig::get('schedStartHour');
}
/**
* Returns the currently defined custom schedule. This is only applicable when the scheduling mode is
* SCAN_SCHEDULING_MODE_MANUAL and the manual type is set to MANUAL_SCHEDULING_CUSTOM.
*
* @return array The array will be of the format array(0 => array(0 => 0, 1 => 0 ... 23 => 0), ... 6 => array(...))
*/
public function customSchedule() {
$normalizedSchedule = array_fill(0, 7, array_fill(0, 24, 0));
$storedSchedule = wfConfig::get_ser('scanSched', array());
if (is_array($storedSchedule) && !empty($storedSchedule) && is_array($storedSchedule[0])) {
foreach ($storedSchedule as $dayNumber => $day) {
foreach ($day as $hourNumber => $enabled) {
$normalizedSchedule[$dayNumber][$hourNumber] = wfUtils::truthyToInt($enabled);
}
}
}
return $normalizedSchedule;
}
public function shouldRunQuickScan() {
if (!$this->isEnabled()) {
return false;
}
if (time() - $this->lastQuickScanTime() < 79200) { //Do not run within 22 hours of a completed quick scan
return false;
}
$lastFullScanCompletion = (int) $this->lastScanTime();
if (time() - $lastFullScanCompletion < 43200) { //Do not run within 12 hours of a completed full scan
return false;
}
$nextFullScan = $this->nextScheduledScanTime();
if ($nextFullScan === false || $nextFullScan - time() < 3600) { //Scans are not running (e.g., custom schedule selected with no times configured) or if scheduled, then avoid running within 1 hour of a pending full scan
return false;
}
$now = time();
$tzOffset = wfUtils::formatLocalTime('Z', $now);
$currentDayOfWeekUTC = date('w', $now);
$currentHourUTC = date('G', $now);
$preferredHourUTC = false;
if ($this->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_MANUAL) {
$manualType = $this->manualSchedulingType();
$preferredHourUTC = round(($this->manualSchedulingStartHour() * 3600 - $tzOffset) / 3600, 2) % 24; //round() rather than floor() to account for fractional time zones
switch ($manualType) {
case self::MANUAL_SCHEDULING_ONCE_DAILY:
case self::MANUAL_SCHEDULING_EVERY_OTHER_DAY:
case self::MANUAL_SCHEDULING_WEEKDAYS:
case self::MANUAL_SCHEDULING_WEEKENDS:
case self::MANUAL_SCHEDULING_ODD_DAYS_WEEKENDS:
$preferredHourUTC = ($preferredHourUTC + 12) % 24;
break;
case self::MANUAL_SCHEDULING_TWICE_DAILY:
$preferredHourUTC = ($preferredHourUTC + 6) % 24; //When automatic scans run twice daily, possibly run a quick scan 6 hours offset (will only run if either scheduled one fails for some reason)
break;
case self::MANUAL_SCHEDULING_CUSTOM: //Iterate from the current day backwards and base it on the first time found, may or may not actually run depending on the spacing of the custom schedule
$preferredHourUTC = false;
$oneWeekSchedule = $this->customSchedule();
for ($i = 7; $i > 0; $i--) { //Sample sequence for `$currentDayOfWeekUTC == 2` => 2, 1, 0, 6, 5, 4, 3
$checkingDayNumber = ($currentDayOfWeekUTC + $i) % 7;
$day = $oneWeekSchedule[$checkingDayNumber];
$dayHour = array_search(1, $day);
if ($dayHour !== false) {
$preferredHourUTC = (round(($dayHour * 3600 - $tzOffset) / 3600, 2) + 12) % 24;
break;
}
}
break;
}
if ($preferredHourUTC !== false) { //The preferred hour of a manual scan schedule has been determined, run the quick scan at the desired offset if we're in that hour
return ($currentHourUTC >= $preferredHourUTC);
}
}
$noc1ScanSchedule = wfConfig::get_ser('noc1ScanSchedule', array());
if (count($noc1ScanSchedule)) {
$preferredHourUTC = (((int) (($noc1ScanSchedule[0] % 86400) / 3600)) + 12) % 24;
return ($currentHourUTC >= $preferredHourUTC);
}
return false; //If we've reached this point, the scan config is in a weird state so just skip the quick scan
}
/**
* Returns an associative array containing the current state each scan stage and its corresponding status.
*
* @return array
*/
public function stageStatus() {
$status = $this->_defaultStageStatuses();
$runningStatus = wfConfig::get_ser('scanStageStatuses', array(), false);
$status = array_merge($status, $runningStatus);
foreach ($status as $stage => &$value) { //Convert value array into status only
$value = $value['status'];
if (!$this->isRunning() && $value == self::STATUS_RUNNING) {
$value = self::STATUS_PENDING;
}
}
return $status;
}
/**
* Returns an array of all scan options for the given stage that are enabled.
*
* @param string $stage One of the STAGE_ constants
* @return array
*/
private function _scanJobsForStage($stage) {
$always = array();
$options = array();
switch ($stage) {
case self::STAGE_SPAMVERTISING_CHECKS:
$options = array(
'spamvertizeCheck',
);
break;
case self::STAGE_SPAM_CHECK:
$options = array(
'checkSpamIP',
);
break;
case self::STAGE_BLACKLIST_CHECK:
$options = array(
'scansEnabled_checkGSB',
);
break;
case self::STAGE_SERVER_STATE:
if ($this->scanType() != self::SCAN_TYPE_QUICK) {
$always = array(
'checkSkippedFiles',
);
}
$options = array(
'scansEnabled_checkHowGetIPs',
'scansEnabled_diskSpace',
'scansEnabled_wafStatus',
'scansEnabled_geoipSupport',
);
break;
case self::STAGE_FILE_CHANGES:
$options = array(
'scansEnabled_core',
'scansEnabled_themes',
'scansEnabled_plugins',
'scansEnabled_coreUnknown',
);
break;
case self::STAGE_PUBLIC_FILES:
$options = array(
'scansEnabled_checkReadableConfig',
'scansEnabled_suspectedFiles',
);
break;
case self::STAGE_MALWARE_SCAN:
$options = array(
'scansEnabled_malware',
'scansEnabled_fileContents',
);
break;
case self::STAGE_CONTENT_SAFETY:
$options = array(
'scansEnabled_posts',
'scansEnabled_comments',
'scansEnabled_fileContentsGSB',
);
break;
case self::STAGE_PASSWORD_STRENGTH:
$options = array(
'scansEnabled_passwds',
);
break;
case self::STAGE_VULNERABILITY_SCAN:
$options = array(
'scansEnabled_oldVersions',
);
break;
case self::STAGE_OPTIONS_AUDIT:
$options = array(
'scansEnabled_suspiciousOptions',
'scansEnabled_suspiciousAdminUsers',
);
break;
}
$enabledOptions = $this->scanOptions();
$filteredOptions = array();
foreach ($options as $o) {
if (isset($enabledOptions[$o]) && $enabledOptions[$o]) {
$filteredOptions[] = $o;
}
}
return array_merge($filteredOptions, $always);
}
/**
* Returns an associative array containing each scan stage's default state. The keys are the stage identifiers and the value
* is an array in the format
* array(
* 'started' => the number of tasks for this stage that have started (initially 0),
* 'finished' => the number of tasks that have started and finished (initially 0),
* 'expected' => the expected number of tasks to run for this stage (based on the scan type and options enabled)
* )
*
* @return array
*/
private function _defaultStageStatuses() {
$status = array(
self::STAGE_SPAMVERTISING_CHECKS => array('status' => ($this->isPremiumScan() ? self::STATUS_PENDING : self::STATUS_PREMIUM), 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_SPAM_CHECK => array('status' => ($this->isPremiumScan() ? self::STATUS_PENDING : self::STATUS_PREMIUM), 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_BLACKLIST_CHECK => array('status' => ($this->isPremiumScan() ? self::STATUS_PENDING : self::STATUS_PREMIUM), 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_SERVER_STATE => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_FILE_CHANGES => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_PUBLIC_FILES => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_MALWARE_SCAN => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_CONTENT_SAFETY => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_PASSWORD_STRENGTH => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_VULNERABILITY_SCAN => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
self::STAGE_OPTIONS_AUDIT => array('status' => self::STATUS_PENDING, 'started' => 0, 'finished' => 0, 'expected' => 0),
);
foreach ($status as $stage => &$parameters) {
if ($parameters['status'] == self::STATUS_PREMIUM) {
continue;
}
$jobs = $this->_scanJobsForStage($stage);
if (count($jobs)) {
$parameters['expected'] = count($jobs);
}
else {
$parameters['status'] = self::STATUS_DISABLED;
}
}
return $status;
}
/**
* Resets the state of the scan stage status record.
*/
public function resetStages() {
if ($this->scanType() == self::SCAN_TYPE_QUICK) { //Suppress for quick scans
return;
}
wfConfig::set_ser('scanStageStatuses', $this->_defaultStageStatuses(), false, wfConfig::DONT_AUTOLOAD);
}
private function _shouldForceUpdate($stageID) {
if ($stageID == wfScanner::STAGE_MALWARE_SCAN) {
return true;
}
return false;
}
/**
* Increments the stage started counter and marks it as running if not already in that state.
*
* @param string $stageID One of the STAGE_ constants
*/
public function startStage($stageID) {
if ($this->scanType() == self::SCAN_TYPE_QUICK) { //Suppress for quick scans
return;
}
$runningStatus = wfConfig::get_ser('scanStageStatuses', array(), false);
if ($runningStatus[$stageID]['status'] != self::STATUS_RUNNING_WARNING) {
$runningStatus[$stageID]['status'] = self::STATUS_RUNNING;
}
$runningStatus[$stageID]['started'] += 1;
wfConfig::set_ser('scanStageStatuses', $runningStatus, false, wfConfig::DONT_AUTOLOAD);
if (wfCentral::isConnected() && ($this->_shouldForceUpdate($stageID) || (time() - wfConfig::getInt('lastScanStageStatusUpdate', 0)) > self::CENTRAL_STAGE_UPDATE_THRESHOLD)) {
wfCentral::updateScanStatus($runningStatus);
}
}
/**
* Increments the stage finished counter and updates the stage status according to whether it's fully finished or encountered a negative status.
*
* @param string $stageID One of the STAGE_ constants.
* @param string $status One of the wfIssues::STATUS_ constants
*/
public function completeStage($stageID, $status) {
if ($this->scanType() == self::SCAN_TYPE_QUICK) { //Suppress for quick scans
return;
}
$runningStatus = wfConfig::get_ser('scanStageStatuses', array(), false);
if ($runningStatus[$stageID]['status'] == self::STATUS_RUNNING && ($status == wfIssues::STATUS_PROBLEM)) {
$runningStatus[$stageID]['status'] = self::STATUS_RUNNING_WARNING;
}
$runningStatus[$stageID]['finished'] += 1;
if ($runningStatus[$stageID]['finished'] >= $runningStatus[$stageID]['expected']) {
if ($runningStatus[$stageID]['status'] == self::STATUS_RUNNING) {
$runningStatus[$stageID]['status'] = self::STATUS_COMPLETE_SUCCESS;
}
else {
$runningStatus[$stageID]['status'] = self::STATUS_COMPLETE_WARNING;
}
}
wfConfig::set_ser('scanStageStatuses', $runningStatus, false, wfConfig::DONT_AUTOLOAD);
if (wfCentral::isConnected()) {
$forceSend = true; //Force sending the last stage completion update even if the timing would otherwise prevent it
foreach ($runningStatus as $stageID => $stage) {
if ($runningStatus[$stageID]['finished'] < $runningStatus[$stageID]['expected']) {
$forceSend = false;
break;
}
}
if ($forceSend || (time() - wfConfig::getInt('lastScanStageStatusUpdate', 0)) > self::CENTRAL_STAGE_UPDATE_THRESHOLD) {
wfCentral::updateScanStatus($runningStatus);
}
}
}
/**
* Returns the selected type of the scan.
*
* @return string
*/
public function scanType() {
switch ($this->_scanType) {
case self::SCAN_TYPE_QUICK://SCAN_TYPE_QUICK is not user-selectable
case self::SCAN_TYPE_LIMITED:
case self::SCAN_TYPE_STANDARD:
case self::SCAN_TYPE_HIGH_SENSITIVITY:
case self::SCAN_TYPE_CUSTOM:
return $this->_scanType;
}
return self::SCAN_TYPE_STANDARD;
}
/**
* Returns the display name for the selected type of the scan.
*
* @return string
*/
public function scanTypeName() {
switch ($this->_scanType) {
case self::SCAN_TYPE_QUICK:
return __('Quick Scan', 'wordfence');
case self::SCAN_TYPE_LIMITED:
return __('Limited Scan', 'wordfence');
case self::SCAN_TYPE_HIGH_SENSITIVITY:
return __('High Sensitivity', 'wordfence');
case self::SCAN_TYPE_CUSTOM:
return __('Custom Scan', 'wordfence');
case self::SCAN_TYPE_STANDARD:
default:
return __('Standard Scan', 'wordfence');
}
}
/**
* Returns a normalized percentage (i.e., in the range [0, 1]) to the corresponding display percentage
* based on license type.
*
* @param float $percentage
* @return float
*/
protected function _normalizedPercentageToDisplay($percentage) {
if ($this->isPremiumScan()) {
return round($percentage, 2);
}
return round($percentage * 0.70, 2);
}
/**
* Returns a normalized percentage (i.e., in the range [0, 1]) for the scan type status indicator.
*
* @return float
*/
public function scanTypeStatus() {
$isFree = !wfConfig::get('isPaid');
$weights = self::_scanOptionWeights();
$options = $this->scanOptions();
$score = 0.0;
$premiumOptions = self::_premiumScanOptions();
foreach ($options as $key => $value) {
if ($isFree && array_search($key, $premiumOptions) !== false) {
continue;
}
if ($value) {
$score += $weights[$key];
}
}
return $this->_normalizedPercentageToDisplay($score);
}
public function scanTypeStatusList() {
$isFree = !wfConfig::get('isPaid');
$weights = self::_scanOptionWeights();
$options = $this->scanOptions();
$disabledOptionCount = 0;
$premiumDisabledOptionCount = 0;
$percentage = 0.0;
$premiumPercentage = 0.0;
$premiumOptions = self::_premiumScanOptions();
$statusList = array();
foreach ($options as $key => $value) {
if ($isFree && array_search($key, $premiumOptions) !== false) {
$premiumPercentage += $weights[$key];
$premiumDisabledOptionCount++;
continue;
}
if (!$value && $weights[$key] > 0) {
$percentage += $weights[$key];
$disabledOptionCount++;
}
}
$remainingPercentage = 1 - $this->scanTypeStatus();
if ($isFree) {
$remainingPercentage -= 0.30;
$statusList[] = array(
'percentage' => 0.30,
'title' => __('Enable Premium Scan Signatures.', 'wordfence'),
);
}
if ($premiumPercentage > 0) {
$subtraction = min($this->_normalizedPercentageToDisplay($premiumPercentage), $remainingPercentage);
$remainingPercentage -= $subtraction;
$statusList[] = array(
'percentage' => $subtraction,
'title' => __('Enable Premium Reputation Checks.', 'wordfence'),
);
}
if ($percentage > 0) {
$subtraction = min($this->_normalizedPercentageToDisplay($percentage), $remainingPercentage);
$statusList[] = array(
'percentage' => $subtraction,
'title' => sprintf(_n('Enable %d scan option.', 'Enable %d scan options.', $disabledOptionCount,'wordfence'), number_format_i18n($disabledOptionCount)),
);
}
return $statusList;
}
/**
* Returns the malware signature feed that is in use.
*
* @return string
*/
public function signatureMode() {
if ($this->isPremiumScan()) {
return self::SIGNATURE_MODE_PREMIUM;
}
return self::SIGNATURE_MODE_COMMUNITY;
}
/**
* Returns a normalized percentage (i.e., in the range [0, 1]) for the reputation status indicator.
*
* @return float
*/
public function reputationStatus() {
$score = 0.0;
if ($this->isPremiumScan()) {
$options = $this->scanOptions();
if ($options['spamvertizeCheck']) { $score += 0.333; }
if ($options['checkSpamIP']) { $score += 0.333; }
if ($options['scansEnabled_checkGSB']) { $score += 0.333; }
}
return round($score, 2);
}
/**
* @return array
*/
public function reputationStatusList() {
$statusList = array();
$options = $this->scanOptions();
$reputationChecks = array(
'spamvertizeCheck' => __('Enable scan option to check if this website is being "Spamvertised".', 'wordfence'),
'checkSpamIP' => __('Enable scan option to check if your website IP is generating spam.', 'wordfence'),
'scansEnabled_checkGSB' => __('Enable scan option to check if your website is on a domain blocklist.', 'wordfence'),
);
foreach ($reputationChecks as $option => $optionLabel) {
if (!$this->isPremiumScan() || !$options[$option]) {
$statusList[] = array(
'percentage' => round(1 / count($reputationChecks), 2),
'title' => $optionLabel,
);
}
}
return $statusList;
}
/**
* Returns the options for the configured scan type.
*
* @return array
*/
public function scanOptions() {
switch ($this->scanType()) {
case self::SCAN_TYPE_QUICK:
return self::quickScanTypeOptions();
case self::SCAN_TYPE_LIMITED:
return self::limitedScanTypeOptions();
case self::SCAN_TYPE_STANDARD:
return self::standardScanTypeOptions();
case self::SCAN_TYPE_HIGH_SENSITIVITY:
return self::highSensitivityScanTypeOptions();
case self::SCAN_TYPE_CUSTOM:
return self::customScanTypeOptions();
}
}
/**
* Returns the array of jobs for the scan type.
*
* @return array
*/
public function jobs() {
$options = $this->scanOptions();
$preferredOrder = array(
'checkSpamvertized' => array('spamvertizeCheck'),
'checkSpamIP' => array('checkSpamIP'),
'checkGSB' => array('scansEnabled_checkGSB'),
'checkHowGetIPs' => array('scansEnabled_checkHowGetIPs'),
'diskSpace' => array('scansEnabled_diskSpace'),
'wafStatus' => array('scansEnabled_wafStatus'),
'geoipSupport' => array('scansEnabled_geoipSupport'),
'checkSkippedFiles' => ($this->scanType() != self::SCAN_TYPE_QUICK), //Always runs except for quick
'knownFiles' => ($this->scanType() != self::SCAN_TYPE_QUICK), //Always runs except for quick, options are scansEnabled_core, scansEnabled_themes, scansEnabled_plugins, scansEnabled_coreUnknown, scansEnabled_malware
'checkReadableConfig' => array('scansEnabled_checkReadableConfig'),
'fileContents' => ($this->scanType() != self::SCAN_TYPE_QUICK), //Always runs except for quick, options are scansEnabled_fileContents and scansEnabled_fileContentsGSB
'suspectedFiles' => array('scansEnabled_suspectedFiles'),
'posts' => array('scansEnabled_posts'),
'comments' => array('scansEnabled_comments'),
'passwds' => array('scansEnabled_passwds'),
'oldVersions' => array('scansEnabled_oldVersions'),
'suspiciousAdminUsers' => array('scansEnabled_suspiciousAdminUsers'),
'suspiciousOptions' => array('scansEnabled_suspiciousOptions'),
);
$jobs = array();
foreach ($preferredOrder as $job => $enabler) {
if ($enabler === true) {
$jobs[] = $job;
}
else if (is_array($enabler)) {
foreach ($enabler as $o) {
if ($options[$o]) {
$jobs[] = $job;
break;
}
}
}
}
return $jobs;
}
/**
* Returns whether or not the scanner should use its low resource mode.
*
* @return bool
*/
public function useLowResourceScanning() {
$options = $this->scanOptions();
return $options['lowResourceScansEnabled'];
}
/**
* Returns the array of user-defined malware signatures for use by the scanner.
*
* @return array
*/
public function userScanSignatures() {
$options = $this->scanOptions();
$value = $options['scan_include_extra'];
$signatures = array();
if (!empty($value)) {
$regexs = explode("\n", $value);
$id = 1000001;
foreach ($regexs as $r) {
$r = rtrim($r, "\r");
if (preg_match('/' . $r . '/i', "") !== false) {
$signatures[] = array($id++, time(), $r, __('User defined scan pattern', 'wordfence'));
}
}
}
return $signatures;
}
/**
* Returns whether or not the scanner should check files outside of the WordPress installation.
*
* @return bool
*/
public function scanOutsideWordPress() {
$options = $this->scanOptions();
return $options['other_scanOutside'];
}
/**
* Returns the cleaned up array of user-excluded scan paths and patterns.
*
* @return array
*/
public function userExclusions() {
$options = $this->scanOptions();
$value = $options['scan_exclude'];
return explode("\n", wfUtils::cleanupOneEntryPerLine($value));
}
/**
* Fetches the scan summary items into the internal cache.
*/
private function _fetchSummaryItems() {
if ($this->_summary !== false) {
return;
}
$this->_summary = wfConfig::get_ser('wf_summaryItems', array());
}
/**
* Writes the scan summary cache to permanent storage.
*/
private function _saveSummaryItems() {
if ($this->_summary !== false && $this->_dirty) {
$this->_summary['lastUpdate'] = time();
wfConfig::set_ser('wf_summaryItems', $this->_summary);
}
$this->_dirty = false;
}
/**
* Saves the scan summary cache if it has been more than two seconds since the last update.
*
* @return bool Whether or not it saved.
*/
private function _maybeSaveSummaryItems() {
if ($this->_summary !== false && $this->_summary['lastUpdate'] < (time() - 2)) {
$this->_saveSummaryItems();
return true;
}
return false;
}
/**
* Populates the scan summary with the default counters.
*/
public function resetSummaryItems() {
global $wpdb;
$this->_summary = array();
$this->_summary[self::SUMMARY_SCANNED_POSTS] = 0;
$this->_summary[self::SUMMARY_SCANNED_COMMENTS] = 0;
$this->_summary[self::SUMMARY_SCANNED_FILES] = 0;
$this->_summary[self::SUMMARY_SCANNED_PLUGINS] = 0;
$this->_summary[self::SUMMARY_SCANNED_THEMES] = 0;
$this->_summary[self::SUMMARY_SCANNED_USERS] = 0;
$this->_summary[self::SUMMARY_SCANNED_URLS] = 0;
$this->_dirty = true;
$this->_saveSummaryItems();
}
/**
* Forces a save of the scan summary cache.
*/
public function flushSummaryItems() {
$this->_saveSummaryItems();
}
/**
* Returns the corresponding summary value for $key or $default if not found.
*
* @param $key
* @param mixed $default The value returned if there is no value for $key.
* @return mixed
*/
public function getSummaryItem($key, $default = false) {
$this->_fetchSummaryItems();
if (isset($this->_summary[$key])) {
return $this->_summary[$key];
}
return $default;
}
/**
* Sets the summary item $key as $value.
*
* @param $key
* @param $value
*/
public function setSummaryItem($key, $value) {
$this->_fetchSummaryItems();
$this->_summary[$key] = $value;
$this->_dirty = true;
if (!$this->_maybeSaveSummaryItems() && !$this->_destructRegistered) {
register_shutdown_function(array($this, 'flushSummaryItems'));
$this->_destructRegistered = true;
}
}
/**
* Atomically increments the summary item under $key by $value.
*
* @param $key
* @param int $value
*/
public function incrementSummaryItem($key, $value = 1) {
if ($value == 0) { return; }
$this->_fetchSummaryItems();
if (isset($this->_summary[$key])) {
$this->_summary[$key] += $value;
$this->_dirty = true;
if (!$this->_maybeSaveSummaryItems() && !$this->_destructRegistered) {
register_shutdown_function(array($this, 'flushSummaryItems'));
$this->_destructRegistered = true;
}
}
}
/**
* Schedules a single scan for the given time. If $originalTime is provided, it will be associated with the cron.
*
* @param $futureTime
* @param bool|int $originalTime
*/
public function scheduleSingleScan($futureTime, $originalTime = false) {
if (is_main_site()) { // Removed ability to activate on network site in v5.3.12
if ($originalTime === false) {
$originalTime = $futureTime;
}
wp_schedule_single_event($futureTime, 'wordfence_start_scheduled_scan', array((int) $originalTime));
//Saving our own copy of the schedule because the wp-cron functions all require the args list to act
$allScansScheduled = wfConfig::get_ser('allScansScheduled', array());
$allScansScheduled[] = array('timestamp' => $futureTime, 'args' => array((int) $originalTime));
wfConfig::set_ser('allScansScheduled', $allScansScheduled);
}
}
/**
* Clears all scheduled scan cron jobs and re-creates them.
*/
public function scheduleScans() {
$this->unscheduleAllScans();
if (!$this->isEnabled()) {
return;
}
if ($this->schedulingMode() == wfScanner::SCAN_SCHEDULING_MODE_MANUAL) {
//Generate a two-week schedule
$manualType = $this->manualSchedulingType();
$preferredHour = $this->manualSchedulingStartHour();
switch ($manualType) {
case self::MANUAL_SCHEDULING_ONCE_DAILY:
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
$day[$preferredHour] = 1;
}
break;
case self::MANUAL_SCHEDULING_TWICE_DAILY:
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
$day[$preferredHour] = 1;
$day[($preferredHour + 12) % 24] = 1;
}
break;
case self::MANUAL_SCHEDULING_EVERY_OTHER_DAY:
$baseDay = floor(time() / 86400);
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
if (($baseDay + $dayNumber) % 2) {
$day[$preferredHour] = 1;
}
}
break;
case self::MANUAL_SCHEDULING_WEEKDAYS:
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
if ($dayNumber > 0 && $dayNumber < 6) {
$day[$preferredHour] = 1;
}
}
break;
case self::MANUAL_SCHEDULING_WEEKENDS:
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
if ($dayNumber == 0 || $dayNumber == 6) {
$day[$preferredHour] = 1;
}
}
break;
case self::MANUAL_SCHEDULING_ODD_DAYS_WEEKENDS:
$schedule = array_fill(0, 14, array_fill(0, 24, 0));
foreach ($schedule as $dayNumber => &$day) {
if ($dayNumber == 0 || $dayNumber == 6 || ($dayNumber % 2)) {
$day[$preferredHour] = 1;
}
}
break;
case self::MANUAL_SCHEDULING_CUSTOM:
$oneWeekSchedule = $this->customSchedule();
$schedule = array();
foreach ($oneWeekSchedule as $day) { $schedule[] = $day; }
foreach ($oneWeekSchedule as $day) { $schedule[] = $day; }
break;
}
$now = time();
$tzOffset = wfUtils::formatLocalTime('Z', $now);
//Apply the time zone shift so the start times align to the server's time zone
$shiftedSchedule = array_fill(0, 14, array());
foreach ($schedule as $dayNumber => $day) {
foreach ($day as $hourNumber => $enabled) {
if ($enabled) {
$effectiveHour = round(($hourNumber * 3600 - $tzOffset) / 3600, 2); //round() rather than floor() to account for fractional time zones
$wrappedHour = ($effectiveHour + 24) % 24;
if ($effectiveHour < 0) {
if ($dayNumber > 0) {
$shiftedSchedule[$dayNumber - 1][$wrappedHour] = 1;
}
}
else if ($effectiveHour > 23) {
if ($dayNumber < count($schedule) - 1) {
$shiftedSchedule[$dayNumber + 1][$wrappedHour] = 1;
}
}
else {
$shiftedSchedule[$dayNumber][$effectiveHour] = 1;
}
}
}
}
$schedule = $shiftedSchedule;
//Trim out all but an 8-day period
$currentDayOfWeekUTC = date('w', $now);
$currentHourUTC = date('G', $now);
$periodStart = floor($now / 86400) * 86400 - $currentDayOfWeekUTC * 86400;
$schedule = array_slice($schedule, $currentDayOfWeekUTC, null, true);
$schedule = array_slice($schedule, 0, 8, true);
//Schedule them
foreach ($schedule as $dayNumber => $day) {
foreach ($day as $hourNumber => $enabled) {
if ($enabled) {
if ($dayNumber == $currentDayOfWeekUTC && $currentHourUTC > $hourNumber) { //It's today and we've already passed its hour, skip it
continue;
}
else if ($dayNumber > 6 && ($dayNumber % 7) == $currentDayOfWeekUTC && $currentHourUTC <= $hourNumber) { //It's one week from today but beyond the current hour, skip it this cycle
continue;
}
$scanTime = $periodStart + $dayNumber * 86400 + $hourNumber * 3600 + wfWAFUtils::random_int(0, 3600);
wordfence::status(4, 'info', sprintf(
/* translators: 1. Day of week. 2. Hour of day. 3. Localized date. */
__("Scheduled time for day %s hour %s is: %s", 'wordfence'),
$dayNumber,
$hourNumber,
wfUtils::formatLocalTime('l jS \of F Y h:i:s A P', $scanTime)
));
$this->scheduleSingleScan($scanTime);
}
}
}
}
else {
$noc1ScanSchedule = wfConfig::get_ser('noc1ScanSchedule', array());
foreach ($noc1ScanSchedule as $timestamp) {
$timestamp = wfUtils::denormalizedTime($timestamp);
if ($timestamp > time()) {
$this->scheduleSingleScan($timestamp);
}
}
}
}
public function unscheduleAllScans() {
$allScansScheduled = wfConfig::get_ser('allScansScheduled', array());
foreach ($allScansScheduled as $entry) {
wp_unschedule_event($entry['timestamp'], 'wordfence_start_scheduled_scan', $entry['args']);
}
wp_clear_scheduled_hook('wordfence_start_scheduled_scan');
wfConfig::set_ser('allScansScheduled', array());
}
public function nextScheduledScanTime() {
$nextTime = false;
$cron = _get_cron_array();
foreach($cron as $key => $val){
if(isset($val['wordfence_start_scheduled_scan'])){
$nextTime = $key;
break;
}
}
return $nextTime;
}
public function lastScanTime() {
return wfConfig::get('scanTime');
}
public function recordLastScanTime() {
wfConfig::set('scanTime', microtime(true));
}
public function lastQuickScanTime() {
return wfConfig::get('lastQuickScan', 0);
}
public function recordLastQuickScanTime() {
wfConfig::set('lastQuickScan', microtime(true));
}
} common/wfTab.php 0000644 00000001515 14720676271 0007626 0 ustar 00 _id = $id;
$this->_a = $a;
$this->_tabTitle = $tabTitle;
$this->_pageTitle = $pageTitle;
$this->_active = $active;
}
public function __get($name) {
switch ($name) {
case 'id':
return $this->_id;
case 'a':
return $this->_a;
case 'tabTitle':
return $this->_tabTitle;
case 'pageTitle':
return $this->_pageTitle;
case 'active':
return $this->_active;
}
throw new OutOfBoundsException('Invalid key: ' . $name);
}
} page/wfPage.php 0000644 00000016060 14720676271 0007421 0 ustar 00 label();
}
/**
* Convenience function for returning the canonical URL for the given page.
*
* @param string $identifier
* @param string|bool $source The source page identifier to append to the URL if wanted.
* @return string
*/
public static function pageURL($identifier, $source = false) {
$page = new wfPage($identifier);
return $page->url($source);
}
public function __construct($identifier) {
$this->_identifier = $identifier;
}
public function __get($key) {
switch ($key) {
case 'identifier':
return $this->_identifier;
}
throw new OutOfBoundsException("{$key} is not a valid property");
}
public function __isset($key) {
switch ($key) {
case 'identifier':
return true;
}
return false;
}
/**
* Returns the user-displayable label for the page.
*
* @return bool|string
*/
public function label() {
switch ($this->identifier) {
case self::PAGE_DASHBOARD:
return __('Dashboard', 'wordfence');
case self::PAGE_DASHBOARD_OPTIONS:
return __('Global Options', 'wordfence');
case self::PAGE_FIREWALL:
return __('Firewall', 'wordfence');
case self::PAGE_FIREWALL_OPTIONS:
return __('Firewall Options', 'wordfence');
case self::PAGE_BLOCKING:
return __('Blocking', 'wordfence');
case self::PAGE_BLOCKING_OPTIONS:
return __('Blocking Options', 'wordfence');
case self::PAGE_SCAN:
return __('Scan', 'wordfence');
case self::PAGE_SCAN_OPTIONS:
return __('Scan Options', 'wordfence');
case self::PAGE_TOOLS_2FA:
return __('Two-Factor Authentication', 'wordfence');
case self::PAGE_TOOLS_LIVE_TRAFFIC:
return __('Live Traffic', 'wordfence');
case self::PAGE_TOOLS_IMPORT_EXPORT:
return __('Import/Export Options', 'wordfence');
case self::PAGE_TOOLS_WHOIS:
return __('Whois Lookup', 'wordfence');
case self::PAGE_TOOLS_DIAGNOSTICS:
return __('Diagnostics', 'wordfence');
case self::PAGE_SUPPORT:
return __('Support', 'wordfence');
}
return false;
}
/**
* Returns the canonical URL for the page.
*
* @param string|bool $source The source page identifier to append to the URL if wanted.
* @return string
*/
public function url($source = false) {
$page = '';
$subpage = '';
$hash = '';
switch ($this->identifier) {
case self::PAGE_DASHBOARD:
$page = 'Wordfence';
break;
case self::PAGE_DASHBOARD_OPTIONS:
$page = 'Wordfence';
$subpage = 'global_options';
break;
case self::PAGE_FIREWALL:
$page = 'WordfenceWAF';
break;
case self::PAGE_FIREWALL_OPTIONS:
$page = 'WordfenceWAF';
$subpage = 'waf_options';
break;
case self::PAGE_BLOCKING:
$page = 'WordfenceWAF';
$hash = '#top#blocking';
break;
case self::PAGE_BLOCKING_OPTIONS:
$page = 'WordfenceWAF';
$subpage = 'blocking_options';
break;
case self::PAGE_SCAN:
$page = 'WordfenceScan';
break;
case self::PAGE_SCAN_OPTIONS:
$page = 'WordfenceScan';
$subpage = 'scan_options';
break;
case self::PAGE_TOOLS_2FA:
$page = 'WordfenceTools';
$subpage = 'twofactor';
break;
case self::PAGE_TOOLS_LIVE_TRAFFIC:
$page = 'WordfenceTools';
$subpage = 'livetraffic';
break;
case self::PAGE_TOOLS_IMPORT_EXPORT:
$page = 'WordfenceTools';
$subpage = 'importexport';
break;
case self::PAGE_TOOLS_WHOIS:
$page = 'WordfenceTools';
$subpage = 'whois';
break;
case self::PAGE_TOOLS_DIAGNOSTICS:
$page = 'WordfenceTools';
$subpage = 'diagnostics';
break;
case self::PAGE_SUPPORT:
$page = 'WordfenceSupport';
break;
}
$baseURL = 'admin.php?';
$baseURL .= 'page=' . rawurlencode($page);
if (!empty($subpage)) { $baseURL .= '&subpage=' . rawurlencode($subpage); }
if (self::isValidPage($source)) { $baseURL .= '&source=' . rawurlencode($source); }
if (!empty($hash)) { $baseURL .= $this->_hashURLEncode($hash); }
if (function_exists('network_admin_url') && is_multisite()) {
return network_admin_url($baseURL);
}
return admin_url($baseURL);
}
/**
* Splits a URI hash component and URL-encodes its members.
*
* @param string $hash
* @return string
*/
private function _hashURLEncode($hash) {
$components = explode('#', $hash);
foreach ($components as &$c) {
$c = rawurlencode($c);
}
return implode('#', $components);
}
/**
* Returns an ordered array of the pages required to reach this page, this page being the last entry in the array.
*
* @return array
*/
public function breadcrumbs() {
switch ($this->identifier) {
case self::PAGE_DASHBOARD:
return array($this);
case self::PAGE_DASHBOARD_OPTIONS:
return array(new wfPage(wfPage::PAGE_DASHBOARD), $this);
case self::PAGE_FIREWALL:
return array($this);
case self::PAGE_FIREWALL_OPTIONS:
return array(new wfPage(wfPage::PAGE_FIREWALL), $this);
case self::PAGE_BLOCKING:
return array($this);
case self::PAGE_BLOCKING_OPTIONS:
return array(new wfPage(wfPage::PAGE_BLOCKING), $this);
case self::PAGE_SCAN:
return array($this);
case self::PAGE_SCAN_OPTIONS:
return array(new wfPage(wfPage::PAGE_SCAN), $this);
case self::PAGE_TOOLS_2FA:
return array($this);
case self::PAGE_TOOLS_LIVE_TRAFFIC:
return array($this);
case self::PAGE_TOOLS_IMPORT_EXPORT:
return array($this);
case self::PAGE_TOOLS_WHOIS:
return array($this);
case self::PAGE_TOOLS_DIAGNOSTICS:
return array($this);
case self::PAGE_SUPPORT:
return array($this);
}
return array();
}
}
firewall/wfFirewall.php 0000644 00000040156 14720676271 0011206 0 ustar 00 firewallMode(); }
if ($protection === null) { $protection = $this->protectionMode(); }
switch ($status) {
case self::FIREWALL_MODE_ENABLED:
$statusText = __('Enabled', 'wordfence');
break;
case self::FIREWALL_MODE_LEARNING:
$statusText = __('Learning Mode', 'wordfence');
break;
default:
return __('Disabled', 'wordfence');
}
switch ($protection) {
case self::PROTECTION_MODE_EXTENDED:
$protectionText = __('Extended Protection', 'wordfence');
break;
default:
$protectionText = __('Basic Protection', 'wordfence');
break;
}
return sprintf('%s (%s)', $statusText, $protectionText);
}
/**
* Syncs the status from WAF to the wfConfig table if $toDatabase is true, the reverse if false.
*
* @param bool $toDatabase
*/
public function syncStatus($toDatabase = true) {
if ($toDatabase) {
try {
$status = wfWAF::getInstance()->getStorageEngine()->getConfig('wafStatus');
if (in_array($status, array(self::FIREWALL_MODE_DISABLED, self::FIREWALL_MODE_LEARNING, self::FIREWALL_MODE_ENABLED))) {
wfConfig::set('waf_status', $status);
}
}
catch (Exception $e) {
//Ignore
}
}
else {
try {
$status = wfConfig::get('waf_status');
if (in_array($status, array(self::FIREWALL_MODE_DISABLED, self::FIREWALL_MODE_LEARNING, self::FIREWALL_MODE_ENABLED))) {
wfWAF::getInstance()->getStorageEngine()->setConfig('wafStatus', $status);
}
}
catch (Exception $e) {
//Ignore
}
}
}
/**
* Tests the WAF configuration and returns true if successful.
*
* @return bool
*/
public function testConfig() {
try {
wfWAF::getInstance()->getStorageEngine()->isDisabled();
}
catch (Exception $e) {
return false;
}
return true;
}
/**
* Returns a normalized percentage (i.e., in the range [0, 1]) to the corresponding display percentage
* based on license type.
*
* @param float $percentage
* @param bool $adjust Whether or not to adjust the range to [0, 0.7]
* @return float
*/
protected function _normalizedPercentageToDisplay($percentage, $adjust = true) {
if (wfConfig::get('isPaid') || !$adjust) {
return round($percentage, 2);
}
return round($percentage * 0.70, 2);
}
/**
* Returns the percentage calculation of the overall firewall status, which is displayed under "Firewall"
* on the Dashboard page.
*
* @return float
*/
public function overallStatus() {
try {
$wafStatus = $this->wafStatus();
$bruteForceStatus = $this->bruteForceStatus();
$percentage = 0.0;
$percentage += $wafStatus * 0.80;
$percentage += $bruteForceStatus * 0.20;
return $this->_normalizedPercentageToDisplay($percentage, false);
}
catch (Exception $e) {
//Ignore, return 0%
}
return 0.0;
}
public function statusList($section = null) {
$statusList = array();
$wafStatusList = $this->wafStatusList($section);
$bruteForceStatusList = $this->bruteForceStatusList();
foreach ($wafStatusList as $entry) {
$entry['percentage'] *= 0.8;
$statusList[] = $entry;
}
foreach ($bruteForceStatusList as $entry) {
$entry['percentage'] *= 0.2;
$statusList[] = $entry;
}
return array_filter($statusList);
}
/**
* Returns the percentage calculation of the WAF status, which is displayed under "Web Application
* Firewall" on the Firewall page.
*
* @return float
*/
public function wafStatus() {
try {
$ruleStatus = $this->ruleStatus(true);
$blacklistStatus = $this->blacklistStatus();
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
$extendedProtection = $wafEnabled && WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL;
$rateLimitingAdvancedBlockingEnabled = wfConfig::get('firewallEnabled', 1);
if (!$wafEnabled) {
return 0.0;
}
$percentage = 0.0;
$percentage += $this->_normalizedPercentageToDisplay($ruleStatus * 0.35, true);
$percentage += $blacklistStatus * 0.35;
$percentage += ($extendedProtection ? 0.20 : 0.0);
$percentage += ($rateLimitingAdvancedBlockingEnabled ? 0.10 : 0.0);
return $this->_normalizedPercentageToDisplay($percentage, false);
}
catch (Exception $e) {
//Ignore, return 0%
}
return 0.0;
}
public function wafStatusList($section = null) {
$statusList = array();
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
if (!$wafEnabled) {
return array(
array(
'percentage' => 1.0,
'title' => __('Enable firewall.', 'wordfence'),
),
);
}
// Get percent of rules enabled.
$ruleStatus = $this->ruleStatusDescription(true);
$premiumStatus = array();
if (!wfConfig::get('isPaid')) {
$premiumStatus = array(
'percentage' => 0.30,
'title' => __('Enable Premium Rules.', 'wordfence'),
);
}
if ($section === 'rules') {
if ($ruleStatus) {
$ruleStatus['percentage'] = $this->_normalizedPercentageToDisplay($ruleStatus['percentage']);
}
return array_filter(array($ruleStatus, $premiumStatus));
}
if ($premiumStatus) {
$premiumStatus['percentage'] *= 0.35;
$premiumStatus['percentage'] = $this->_normalizedPercentageToDisplay($premiumStatus['percentage'], false);
}
if ($ruleStatus) {
$ruleStatus['percentage'] *= 0.35;
$ruleStatus['percentage'] = $this->_normalizedPercentageToDisplay($ruleStatus['percentage']);
}
$statusList = array_merge($statusList, array($ruleStatus), array($premiumStatus));
$blacklistStatus = $this->blacklistStatusDescription();
if ($section === 'blacklist') {
return array_filter(array($blacklistStatus));
}
if ($blacklistStatus) {
$blacklistStatus['percentage'] *= 0.35;
$blacklistStatus['percentage'] = $this->_normalizedPercentageToDisplay($blacklistStatus['percentage'], false);
}
$statusList = array_merge($statusList, array($blacklistStatus));
$extendedProtection = $wafEnabled && WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL;
if (!$extendedProtection) {
$statusList[] = array(
'percentage' => $this->_normalizedPercentageToDisplay(0.20, false),
'title' => __('Optimize the Wordfence Firewall.', 'wordfence'),
);
}
$rateLimitingAdvancedBlockingEnabled = wfConfig::get('firewallEnabled', 1);
if (!$rateLimitingAdvancedBlockingEnabled) {
$statusList[] = array(
'percentage' => $this->_normalizedPercentageToDisplay(0.10, false),
'title' => __('Enable Rate Limiting and Advanced Blocking.', 'wordfence'),
);
}
return array_filter($statusList);
}
catch (Exception $e) {
//Ignore, return 0%
}
if (!WFWAF_OPERATIONAL) {
return array(array('percentage' => 1.0, 'title' => __('Repair the Wordfence Firewall configuration.', 'wordfence')));
}
return array();
}
/**
* Returns the status of the WAF.
*
* @return string
*/
public function firewallMode() {
try {
return (!WFWAF_ENABLED ? 'disabled' : wfWAF::getInstance()->getStorageEngine()->getConfig('wafStatus'));
}
catch (Exception $e) {
//Ignore
}
return self::FIREWALL_MODE_DISABLED;
}
/**
* Returns the current protection mode configured for the WAF.
*
* @return string
*/
public function protectionMode() {
if (defined('WFWAF_AUTO_PREPEND') && WFWAF_AUTO_PREPEND) {
return self::PROTECTION_MODE_EXTENDED;
}
return self::PROTECTION_MODE_BASIC;
}
/**
* Returns whether or not this installation is in a subdirectory of another WordPress site with the WAF already optimized.
*
* @return bool
*/
public function isSubDirectoryInstallation() {
if (defined('WFWAF_SUBDIRECTORY_INSTALL') && WFWAF_SUBDIRECTORY_INSTALL) {
return true;
}
return false;
}
/**
* Returns the percentage calculation of the firewall rule status, which is displayed under "Firewall Rules" on the
* Firewall page.
*
* The calculation is the number of rules enabled divided by the total number of rules. If the WAF is in learning
* mode, no rules are enforced, so it's clamped to 0%.
*
* @param bool $round Round the percentage (in the range [0, 1]) to be only whole percentages.
* @return float
*/
public function ruleStatus($round = false) {
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
if (!$wafEnabled) {
return 0.0;
}
/*$learningMode = !!wfWAF::getInstance()->isInLearningMode();
if ($learningMode) {
return 0.0;
}*/
$rules = wfWAF::getInstance()->getRules();
$disabledRules = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
/** @var wfWAFRule $rule */
$enabledCount = 0;
foreach ($rules as $ruleID => $rule) {
if (isset($disabledRules[$ruleID]) && $disabledRules[$ruleID]) {
continue;
}
$enabledCount++;
}
$percentEnabled = (count($rules) == 0 ? 0 : $enabledCount / count($rules));
if ($round) {
return round($percentEnabled, 2);
}
return $this->_normalizedPercentageToDisplay($percentEnabled);
}
catch (Exception $e) {
//Ignore, return 0%
}
return 0.0;
}
/**
* @param bool $round
* @return array
*/
public function ruleStatusDescription($round = false) {
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
if (!$wafEnabled) {
return array(
'percentage' => 1.0,
'title' => __('Enable firewall.', 'wordfence'),
);
}
/*$learningMode = !!wfWAF::getInstance()->isInLearningMode();
if ($learningMode) {
return 0.0;
}*/
$rules = wfWAF::getInstance()->getRules();
$disabledRules = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
/** @var wfWAFRule $rule */
$enabledCount = 0;
foreach ($rules as $ruleID => $rule) {
if (isset($disabledRules[$ruleID]) && $disabledRules[$ruleID]) {
continue;
}
$enabledCount++;
}
$percentEnabled = 1.0 - ((float) (count($rules) == 0 ? 0 : $enabledCount / count($rules)));
if ($percentEnabled === 0.0) {
return array();
}
$reenbleCount = count($rules) - $enabledCount;
return array(
'percentage' => ($round ? round($percentEnabled, 2) : $percentEnabled),
'title' => sprintf(_n('Re-enable %d firewall rule.', 'Re-enable %d firewall rules.', $reenbleCount, 'wordfence'), number_format_i18n($reenbleCount)),
);
}
catch (Exception $e) {
//Ignore, return 0%
}
return array(
'percentage' => 1.0,
'title' => __('Enable firewall.', 'wordfence'),
);
}
/**
* Returns the rule feed that is in use.
*
* @return string
*/
public function ruleMode() {
if (wfConfig::get('isPaid')) {
return self::RULE_MODE_PREMIUM;
}
return self::RULE_MODE_COMMUNITY;
}
/**
* Returns 100% if the blacklist is enabled, 0% if not.
*
* @return float
*/
public function blacklistStatus() {
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
if (!$wafEnabled) {
return 0.0;
}
return $this->blacklistMode() == self::BLACKLIST_MODE_ENABLED ? 1.0 : 0.0;
}
catch (Exception $e) {
//Ignore, return 0%
}
return 0.0;
}
/**
* Returns 100% if the blacklist is enabled, 0% if not.
*
* @return array
*/
public function blacklistStatusDescription() {
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
if (!$wafEnabled) {
return array(
'percentage' => 1.0,
'title' => __('Enable Firewall.', 'wordfence'),
);
}
if ($this->blacklistMode() == self::BLACKLIST_MODE_ENABLED) {
return array();
}
return array(
'percentage' => 1.0,
'title' => __('Enable Real-Time IP Blocklist.', 'wordfence'),
);
}
catch (Exception $e) {
//Ignore, return 0%
}
return array(
'percentage' => 1.0,
'title' => __('Enable Real-Time IP Blocklist.', 'wordfence'),
);
}
/**
* Returns the blacklist mode.
*
* @return string
*/
public function blacklistMode() {
$blacklistEnabled = false;
try {
$wafEnabled = !(!WFWAF_ENABLED || wfWAF::getInstance()->getStorageEngine()->isDisabled());
$blacklistEnabled = $wafEnabled && !wfWAF::getInstance()->getStorageEngine()->getConfig('disableWAFBlacklistBlocking');
}
catch (Exception $e) {
//Do nothing
}
if (wfConfig::get('isPaid') && $blacklistEnabled) {
return self::BLACKLIST_MODE_ENABLED;
}
return self::BLACKLIST_MODE_DISABLED;
}
/**
* Returns a percentage rating for the brute force protection status. This includes both the WFSN enabled status
* and the status of individual login security options. These options are available to all, so they are always
* in the range [0,1].
*
* @return float
*/
public function bruteForceStatus() {
$networkBruteForceEnabled = !!wfConfig::get('other_WFNet');
$localBruteForceEnabled = !!wfConfig::get('loginSecurityEnabled');
$percentage = 0.0;
if ($localBruteForceEnabled) {
$percentage += 0.1;
if ($networkBruteForceEnabled) {
$percentage += 0.5;
}
if (wfConfig::get('loginSec_strongPasswds_enabled') && (wfConfig::get('loginSec_strongPasswds') == 'pubs' || wfConfig::get('loginSec_strongPasswds') == 'all')) {
$percentage += 0.1;
}
if (wfConfig::get('loginSec_maskLoginErrors')) {
$percentage += 0.1;
}
if (wfConfig::get('loginSec_blockAdminReg')) {
$percentage += 0.1;
}
if (wfConfig::get('loginSec_disableAuthorScan')) {
$percentage += 0.1;
}
}
return round($percentage, 2);
}
/**
* Returns the status of the WAF's learning mode.
*
* @return bool|int Returns true if enabled without an automatic switchover, a timestamp if enabled with one, and false if not in learning mode.
*/
public function learningModeStatus() {
if ($this->firewallMode() != self::FIREWALL_MODE_LEARNING) {
return false;
}
try {
$config = wfWAF::getInstance()->getStorageEngine();
if ($config->getConfig('learningModeGracePeriodEnabled')) {
return (int) $config->getConfig('learningModeGracePeriod');
}
return true;
}
catch (Exception $e) {
//Ignore, return false
}
return false;
}
/**
* @return array
*/
public function bruteForceStatusList() {
$networkBruteForceEnabled = !!wfConfig::get('other_WFNet');
$localBruteForceEnabled = !!wfConfig::get('loginSecurityEnabled');
$status = array();
if ($localBruteForceEnabled) {
if (!$networkBruteForceEnabled) {
$status[] = array(
'percentage' => 0.5,
'title' => __('Enable Real-Time Wordfence Security Network.', 'wordfence'),
);
}
if (!wfConfig::get('loginSec_strongPasswds_enabled')) {
$status[] = array(
'percentage' => 0.1,
'title' => __('Enforce Strong Passwords.', 'wordfence'),
);
}
if (!wfConfig::get('loginSec_maskLoginErrors')) {
$status[] = array(
'percentage' => 0.1,
'title' => __('Enable Mask Login Errors.', 'wordfence'),
);
}
if (!wfConfig::get('loginSec_blockAdminReg')) {
$status[] = array(
'percentage' => 0.1,
'title' => __('Enable Block Admin Registration.', 'wordfence'),
);
}
if (!wfConfig::get('loginSec_disableAuthorScan')) {
$status[] = array(
'percentage' => 0.1,
'title' => __('Disable Author Scanning.', 'wordfence'),
);
}
} else {
$status[] = array(
'percentage' => 1.0,
'title' => __('Enable Brute Force Protection.', 'wordfence'),
);
}
return array_filter($status);
}
}
block/wfBlock.php 0000644 00000200077 14720676271 0007760 0 ustar 00 isIPInRange($IP)) {
return true;
}
} elseif (wfUtils::subnetContainsIP($subnet, $IP)) {
if ($forcedWhitelistEntry !== null) {
$forcedWhitelistEntry = true;
}
return true;
}
}
return false;
}
/**
* Validates the payload for block creation. Returns true if valid, otherwise it'll return the first error found.
*
* @param $payload
* @return bool|string
*/
public static function validate($payload) {
if (!isset($payload['type']) || array_search($payload['type'], array('ip-address', 'country', 'custom-pattern')) === false) { return __('Invalid block type.', 'wordfence'); }
if (!isset($payload['duration']) || intval($payload['duration']) < 0) { return __('Invalid block duration.', 'wordfence'); }
if (!isset($payload['reason']) || empty($payload['reason'])) { return __('A block reason must be provided.', 'wordfence'); }
if ($payload['type'] == 'ip-address') {
if (!isset($payload['ip']) || !filter_var(trim($payload['ip']), FILTER_VALIDATE_IP) || @wfUtils::inet_pton(trim($payload['ip'])) === false) { return __('Invalid IP address.', 'wordfence'); }
if (self::isWhitelisted(trim($payload['ip']))) { return wp_kses(sprintf(/* translators: Support URL */ __('This IP address is in a range of addresses that Wordfence does not block. The IP range may be internal or belong to a service that is always allowed. Allowlisting of external services can be disabled. Learn More (opens in new tab)', 'wordfence'), wfSupportController::supportURL(wfSupportController::ITEM_FIREWALL_WAF_OPTION_WHITELISTED_SERVICES)), array('a'=>array('href'=>array(), 'target'=>array(), 'rel'=>array()), 'span'=>array('class'=>array()))); }
}
else if ($payload['type'] == 'country') {
if (!isset($payload['blockLogin']) || !isset($payload['blockSite'])) { return __('Nothing selected to block.', 'wordfence'); }
if (!$payload['blockLogin'] && !$payload['blockSite']) { return __('Nothing selected to block.', 'wordfence'); }
if (!isset($payload['countries']) || empty($payload['countries']) || !is_array($payload['countries'])) { return __('No countries selected.', 'wordfence'); }
require(WORDFENCE_PATH . 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
foreach ($payload['countries'] as $code) {
if (!isset($wfBulkCountries[$code])) {
return __('An invalid country was selected.', 'wordfence');
}
}
}
else if ($payload['type'] == 'custom-pattern') {
$hasOne = false;
if (isset($payload['ipRange']) && !empty($payload['ipRange'])) {
$ipRange = new wfUserIPRange($payload['ipRange']);
if ($ipRange->isValidRange()) {
if ($ipRange->isMixedRange()) {
return __('Ranges mixing IPv4 and IPv6 addresses are not supported.', 'wordfence');
}
$hasOne = true;
}
else {
return __('Invalid IP range.', 'wordfence');
}
}
if (isset($payload['hostname']) && !empty($payload['hostname'])) {
if (preg_match('/^[a-z0-9\.\*\-]+$/i', $payload['hostname'])) {
$hasOne = true;
}
else {
return __('Invalid hostname.', 'wordfence');
}
}
if (isset($payload['userAgent']) && !empty($payload['userAgent'])) { $hasOne = true; }
if (isset($payload['referrer']) && !empty($payload['referrer'])) { $hasOne = true; }
if (!$hasOne) { return __('No block parameters provided.', 'wordfence'); }
}
return true;
}
/**
* Creates the block. The $payload value is expected to have been validated prior to calling this.
*
* @param $payload
*/
public static function create($payload) {
$type = $payload['type'];
$duration = max((int) $payload['duration'], 0);
$reason = $payload['reason'];
if ($type == 'ip-address') {
$ip = trim($payload['ip']);
wfBlock::createIP($reason, $ip, $duration);
}
else if ($type == 'country') {
$blockLogin = !!$payload['blockLogin'];
$blockSite = !!$payload['blockSite'];
$countries = array_unique($payload['countries']);
wfBlock::createCountry($reason, $blockLogin, $blockSite, $countries, $duration);
}
else if ($type == 'custom-pattern') {
$ipRange = '';
if (isset($payload['ipRange']) && !empty($payload['ipRange'])) {
$ipRange = new wfUserIPRange($payload['ipRange']);
$ipRange = $ipRange->getIPString();
}
$hostname = (isset($payload['hostname']) && !empty($payload['hostname'])) ? $payload['hostname'] : '';
$userAgent = (isset($payload['userAgent']) && !empty($payload['userAgent'])) ? $payload['userAgent'] : '';
$referrer = (isset($payload['referrer']) && !empty($payload['referrer'])) ? $payload['referrer'] : '';
wfBlock::createPattern($reason, $ipRange, $hostname, $userAgent, $referrer, $duration);
}
}
/**
* Creates an IP block if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ip
* @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createIP($reason, $ip, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false, $type = self::TYPE_IP_MANUAL) {
global $wpdb;
if (self::isWhitelisted($ip)) { return; }
if ($blockedTime === false) {
$blockedTime = time();
}
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$blocksTable = wfBlock::blocksTable();
$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), $type));
if (!$hasExisting) {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", $type, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
wfConfig::inc('totalIPsBlocked');
}
if ($type == self::TYPE_IP_MANUAL || $type == self::TYPE_IP_AUTOMATIC_PERMANENT) {
/**
* Fires when an IP/Pattern block is created.
*
* @since 8.0.0
*
* @param string $type The type of block.
* @param string $reason The reason for the block.
* @param string|array $parameters The IP address being blocked for IP blocks or the pattern for pattern blocks.
*/
do_action('wordfence_created_ip_pattern_block', $type, $reason, $ip);
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates an IP block for a WFSN response if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ip
* @param int $duration This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createWFSN($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if (self::isWhitelisted($ip)) { return; }
if ($blockedTime === false) {
$blockedTime = time();
}
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$blocksTable = wfBlock::blocksTable();
$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_WFSN_TEMPORARY));
if (!$hasExisting) {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_WFSN_TEMPORARY, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
wfConfig::inc('totalIPsBlocked');
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates an IP block for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ip
* @param int $duration This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createRateBlock($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if (self::isWhitelisted($ip)) { return; }
if ($blockedTime === false) {
$blockedTime = time();
}
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$blocksTable = wfBlock::blocksTable();
$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_RATE_BLOCK));
if (!$hasExisting) {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_RATE_BLOCK, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
wfConfig::inc('totalIPsBlocked');
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates an IP throttle for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ip
* @param int $duration This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createRateThrottle($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if (self::isWhitelisted($ip)) { return; }
if ($blockedTime === false) {
$blockedTime = time();
}
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$blocksTable = wfBlock::blocksTable();
$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_RATE_THROTTLE));
if (!$hasExisting) {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_RATE_THROTTLE, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
wfConfig::inc('totalIPsBlocked');
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates a lockout if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ip
* @param int $duration This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createLockout($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if (self::isWhitelisted($ip)) { return; }
if ($blockedTime === false) {
$blockedTime = time();
}
$blocksTable = wfBlock::blocksTable();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_LOCKOUT));
if (!$hasExisting) {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_LOCKOUT, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
wfConfig::inc('totalIPsLocked');
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates a country block. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $blockLogin
* @param string $blockSite
* @param string $countries
* @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createCountry($reason, $blockLogin, $blockSite, $countries, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if ($blockedTime === false) {
$blockedTime = time();
}
$parameters = array(
'blockLogin' => $blockLogin ? 1 : 0,
'blockSite' => $blockSite ? 1 : 0,
'countries' => $countries,
);
$blocksTable = wfBlock::blocksTable();
$existing = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `type` = %d LIMIT 1", self::TYPE_COUNTRY), ARRAY_A);
$before = array(
'parameters' => null,
'bypass' => array(
'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked', false),
'cbl_action' => wfConfig::get('cbl_action'),
'cbl_redirURL' => wfConfig::get('cbl_redirURL', ''),
'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL', ''),
'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest', ''),
'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL', ''),
),
);
$after = $before;
$after['parameters'] = $parameters;
if ($existing) {
$before['parameters'] = @json_decode($existing['parameters'], true);
$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `parameters` = %s WHERE `id` = %d", $reason, json_encode($parameters), $existing['id']));
}
else {
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)", self::TYPE_COUNTRY, self::MARKER_COUNTRY, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration), json_encode($parameters)));
}
/**
* Fires when the country blocking rule is updated.
*
* @since 8.0.0
*
* @param array $before {
* The country block configuration before the change
*
* @type array $parameters The parameters of the country block.
* @type array $bypass {
* The assorted bypass settings for country blocking.
*
* @type bool $cbl_loggedInBlocked Block countries even if there is a valid logged-in user for the request
* @type string $cbl_action Action taken when a request is received from a blocked country
* @type string $cbl_redirURL URL destination when $cbl_action is `redir`
* @type string $cbl_bypassRedirURL If a visitor hits this URL
* @type string $cbl_bypassRedirDest then redirect to this URL and set a cookie that will bypass all country blocking
* @type string $cbl_bypassViewURL If a user currently not blocked hits this URL, then set a cookie that will bypass country blocking in the future even if visiting from a blocked country
* }
* }
* @param array $after The country block configuration after the change, same structure as $before
*/
do_action('wordfence_updated_country_blocking', $before, $after);
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Creates a pattern block. The parameters are expected to have been validated and sanitized prior to calling this.
*
* @param string $reason
* @param string $ipRange
* @param string $hostname
* @param string $userAgent
* @param string $referrer
* @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
* @param bool|int $blockedTime Optional. Defaults to the current timestamp.
* @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
* @param bool|int $blockedHits Optional. Defaults to 0.
*/
public static function createPattern($reason, $ipRange, $hostname, $userAgent, $referrer, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
global $wpdb;
if ($blockedTime === false) {
$blockedTime = time();
}
$parameters = array(
'ipRange' => $ipRange,
'hostname' => $hostname,
'userAgent' => $userAgent,
'referrer' => $referrer,
);
$blocksTable = wfBlock::blocksTable();
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)", self::TYPE_PATTERN, self::MARKER_PATTERN, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration), json_encode($parameters)));
/**
* @see wfBlock::createIP()
*/
do_action('wordfence_created_ip_pattern_block', self::TYPE_PATTERN, $reason, $parameters);
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Removes all expired blocks.
*/
public static function vacuum() {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$wpdb->query("DELETE FROM `{$blocksTable}` WHERE `expiration` <= UNIX_TIMESTAMP() AND `expiration` != " . self::DURATION_FOREVER);
}
/**
* Imports all valid blocks in $blocks. If $replaceExisting is true, this will remove all permanent blocks prior to the import.
*
* @param array $blocks
* @param bool $replaceExisting
*/
public static function importBlocks($blocks, $replaceExisting = true) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
if ($replaceExisting) {
$removing = self::_recordsFromRows($wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `expiration` = " . self::DURATION_FOREVER, ARRAY_A));
self::removeMultiple($removing, true);
}
foreach ($blocks as $b) {
self::_importBlock($b);
}
if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
}
}
/**
* Validates the block import record and inserts it if valid. This validation is identical to what is applied to adding one through the UI.
*
* @param array $b
* @return bool
*/
private static function _importBlock($b) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
if (!isset($b['type']) || !isset($b['IP']) || !isset($b['blockedTime']) || !isset($b['reason']) || !isset($b['lastAttempt']) || !isset($b['blockedHits'])) { return false; }
if (empty($b['IP']) || empty($b['reason'])) { return false; }
$ip = @wfUtils::inet_ntop(wfUtils::hex2bin($b['IP']));
if (!wfUtils::isValidIP($ip)) { return false; }
switch ($b['type']) {
case self::TYPE_IP_MANUAL:
case self::TYPE_IP_AUTOMATIC_TEMPORARY:
case self::TYPE_IP_AUTOMATIC_PERMANENT:
case self::TYPE_WFSN_TEMPORARY:
case self::TYPE_RATE_BLOCK:
case self::TYPE_RATE_THROTTLE:
case self::TYPE_LOCKOUT:
if (self::isWhitelisted($ip)) { return false; }
if ($b['type'] == self::TYPE_IP_MANUAL || $b['type'] == self::TYPE_IP_AUTOMATIC_PERMANENT) {
/**
* @see wfBlock::createIP()
*/
do_action('wordfence_created_ip_pattern_block', $b['type'], $b['reason'], $ip);
}
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", (int) $b['type'], (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER)) !== false;
case self::TYPE_COUNTRY:
if (!isset($b['parameters'])) { return false; }
if (wfUtils::inet_pton($ip) != self::MARKER_COUNTRY) { return false; }
$parameters = @json_decode($b['parameters'], true);
if (!isset($parameters['blockLogin']) || !isset($parameters['blockSite']) || !isset($parameters['countries'])) { return false; }
$parameters['blockLogin'] = wfUtils::truthyToInt($parameters['blockLogin']);
$parameters['blockSite'] = wfUtils::truthyToInt($parameters['blockSite']);
require(WORDFENCE_PATH . 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
foreach ($parameters['countries'] as $code) {
if (!isset($wfBulkCountries[$code])) {
return false;
}
}
$parameters = array('blockLogin' => $parameters['blockLogin'], 'blockSite' => $parameters['blockSite'], 'countries' => $parameters['countries']);
$before = array(
'parameters' => null,
'bypass' => array(
'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked', false),
'cbl_action' => wfConfig::get('cbl_action'),
'cbl_redirURL' => wfConfig::get('cbl_redirURL', ''),
'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL', ''),
'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest', ''),
'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL', ''),
),
);
$after = $before;
$after['parameters'] = $parameters;
/**
* @see wfBlock::createCountry()
*/
do_action('wordfence_updated_country_blocking', $before, $after);
$ipHex = wfDB::binaryValueToSQLHex(self::MARKER_COUNTRY);
return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)", self::TYPE_COUNTRY, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER, json_encode($parameters))) !== false;
case self::TYPE_PATTERN:
if (!isset($b['parameters'])) { return false; }
if (wfUtils::inet_pton($ip) != self::MARKER_PATTERN) { return false; }
$parameters = @json_decode($b['parameters'], true);
if (!isset($parameters['ipRange']) || !isset($parameters['hostname']) || !isset($parameters['userAgent']) || !isset($parameters['referrer'])) { return false; }
$hasOne = false;
if (!empty($parameters['ipRange'])) {
$ipRange = new wfUserIPRange($parameters['ipRange']);
if ($ipRange->isValidRange()) {
if ($ipRange->isMixedRange()) {
return false;
}
$hasOne = true;
}
else {
return false;
}
}
if (!empty($parameters['hostname'])) {
if (preg_match('/^[a-z0-9\.\*\-]+$/i', $parameters['hostname'])) {
$hasOne = true;
}
else {
return false;
}
}
if (!empty($parameters['userAgent'])) { $hasOne = true; }
if (!empty($parameters['referrer'])) { $hasOne = true; }
if (!$hasOne) { return false; }
$ipRange = '';
if (!empty($parameters['ipRange'])) {
$ipRange = new wfUserIPRange($parameters['ipRange']);
$ipRange = $ipRange->getIPString();
}
$parameters = array(
'ipRange' => $ipRange,
'hostname' => $parameters['hostname'],
'userAgent' => $parameters['userAgent'],
'referrer' => $parameters['referrer'],
);
/**
* @see wfBlock::createIP()
*/
do_action('wordfence_created_ip_pattern_block', $b['type'], $b['reason'], $parameters);
$ipHex = wfDB::binaryValueToSQLHex(self::MARKER_PATTERN);
return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)", self::TYPE_PATTERN, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER, json_encode($parameters))) !== false;
}
return false;
}
/**
* Returns an array suitable for JSON output of all permanent blocks.
*
* @return array
*/
public static function exportBlocks() {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$query = "SELECT `type`, HEX(`IP`) AS `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `parameters` FROM `{$blocksTable}` WHERE `expiration` = " . self::DURATION_FOREVER;
$rows = $wpdb->get_results($query, ARRAY_A);
return $rows;
}
/**
* Returns all unexpired blocks (including lockouts by default), optionally only of the specified types. These are sorted descending by the time created.
*
* @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
* @param array $ofTypes An optional array of block types to restrict the returned array of blocks to.
* @param int $offset The offset to start the result fetch at.
* @param int $limit The maximum number of results to return. -1 for all.
* @param string $sortColumn The column to sort by.
* @param string $sortDirection The direction to sort.
* @param string $filter An optional value to filter by.
* @return wfBlock[]
*/
public static function allBlocks($prefetch = false, $ofTypes = array(), $offset = 0, $limit = -1, $sortColumn = 'type', $sortDirection = 'ascending', $filter = '') {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$columns = '`id`';
if ($prefetch) {
$columns = '*';
}
$sort = 'typeSort';
switch ($sortColumn) { //Match the display table column to the corresponding schema column
case 'type':
//Use default;
break;
case 'detail':
$sort = 'detailSort';
break;
case 'ruleAdded':
$sort = 'blockedTime';
break;
case 'reason':
$sort = 'reason';
break;
case 'expiration':
$sort = 'expiration';
break;
case 'blockCount':
$sort = 'blockedHits';
break;
case 'lastAttempt':
$sort = 'lastAttempt';
break;
}
$order = 'ASC';
if ($sortDirection == 'descending') {
$order = 'DESC';
}
$query = "SELECT {$columns}, CASE
WHEN `type` = " . self::TYPE_COUNTRY . " THEN 0
WHEN `type` = " . self::TYPE_PATTERN . " THEN 1
WHEN `type` = " . self::TYPE_LOCKOUT . " THEN 2
WHEN `type` = " . self::TYPE_RATE_THROTTLE . " THEN 3
WHEN `type` = " . self::TYPE_RATE_BLOCK . " THEN 4
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_PERMANENT . " THEN 5
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_TEMPORARY . " THEN 6
WHEN `type` = " . self::TYPE_WFSN_TEMPORARY . " THEN 7
WHEN `type` = " . self::TYPE_IP_MANUAL . " THEN 8
ELSE 9999
END AS `typeSort`, CASE
WHEN `type` = " . self::TYPE_COUNTRY . " THEN `parameters`
WHEN `type` = " . self::TYPE_PATTERN . " THEN `parameters`
WHEN `type` = " . self::TYPE_IP_MANUAL . " THEN `IP`
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_PERMANENT . " THEN `IP`
WHEN `type` = " . self::TYPE_RATE_BLOCK . " THEN `IP`
WHEN `type` = " . self::TYPE_RATE_THROTTLE . " THEN `IP`
WHEN `type` = " . self::TYPE_LOCKOUT . " THEN `IP`
WHEN `type` = " . self::TYPE_WFSN_TEMPORARY . " THEN `IP`
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_TEMPORARY . " THEN `IP`
ELSE 9999
END AS `detailSort`
FROM `{$blocksTable}` WHERE ";
if (!empty($ofTypes)) {
$sanitizedTypes = array_map('intval', $ofTypes);
$query .= "`type` IN (" . implode(', ', $sanitizedTypes) . ') AND ';
}
$query .= '(`expiration` = ' . self::DURATION_FOREVER . " OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `{$sort}` {$order}, `id` DESC";
if ($limit > -1) {
$offset = (int) $offset;
$limit = (int) $limit;
$query .= " LIMIT {$offset},{$limit}";
}
$rows = $wpdb->get_results($query, ARRAY_A);
$result = array();
foreach ($rows as $r) {
if ($prefetch) {
if ($r['type'] == self::TYPE_COUNTRY || $r['type'] == self::TYPE_PATTERN) {
$ip = null;
}
else {
$ip = wfUtils::inet_ntop($r['IP']);
}
$parameters = null;
if ($r['type'] == self::TYPE_PATTERN || $r['type'] == self::TYPE_COUNTRY) {
$parameters = @json_decode($r['parameters'], true);
}
$result[] = new wfBlock($r['id'], $r['type'], $ip, $r['blockedTime'], $r['reason'], $r['lastAttempt'], $r['blockedHits'], $r['expiration'], $parameters);
}
else {
$result[] = new wfBlock($r['id']);
}
}
return $result;
}
/**
* Functions identically to wfBlock::allBlocks except that it filters the result. The filtering is done within PHP rather than MySQL, so this will impose a performance penalty and should only
* be used when filtering is actually wanted.
*
* @param bool $prefetch
* @param array $ofTypes
* @param int $offset
* @param int $limit
* @param string $sortColumn
* @param string $sortDirection
* @param string $filter
* @return wfBlock[]
*/
public static function filteredBlocks($prefetch = false, $ofTypes = array(), $offset = 0, $limit = -1, $sortColumn = 'type', $sortDirection = 'ascending', $filter = '') {
$filter = trim($filter);
$matchType = '';
$matchValue = '';
if (empty($filter)) {
return self::allBlocks($prefetch, $ofTypes, $offset, $limit, $sortColumn, $sortDirection);
}
else if (wfUtils::isValidIP($filter)) { //e.g., 4.5.6.7, ffe0::, ::0
$matchType = 'ip';
$matchValue = wfUtils::inet_ntop(wfUtils::inet_pton($filter));
}
if (empty($matchType) && preg_match('/^(?:[0-9]+|\*)\.(?:(?:[0-9]+|\*)\.(?!$))*(?:(?:[0-9]+|\*))?$/', trim($filter, '.'))) { //e.g., possible wildcard IPv4 like 4.5.*
$components = explode('.', trim($filter, '.'));
if (count($components) <= 4) {
$components = array_pad($components, 4, '*');
$matchType = 'ipregex';
$matchValue = '^';
foreach ($components as $c) {
if (empty($c) || $c == '*') {
$matchValue .= '\d+';
}
else {
$matchValue .= (int) $c;
}
$matchValue .= '\.';
}
$matchValue = substr($matchValue, 0, -2);
$matchValue .= '$';
}
}
if (empty($matchType) && preg_match('/^(?:[0-9a-f]+\:)(?:[0-9a-f]+\:|\*){1,2}(?:[0-9a-f]+|\*)?$/i', $filter)) { //e.g., possible wildcard IPv6 like ffe0:*
$components = explode(':', $filter);
$matchType = 'ipregex';
$matchValue = '^';
for ($i = 0; $i < 4; $i++) {
if (isset($components[$i])) {
$matchValue .= strtoupper(str_pad(dechex($components[$i]), 4, '0', STR_PAD_LEFT));
}
else {
$matchValue .= '[0-9a-f]{4}';
}
$matchValue .= ':';
}
$matchValue = substr($matchValue, 0, -1);
$matchValue .= '$';
}
if (empty($matchType)) {
$matchType = 'literal';
$matchValue = $filter;
}
$offsetProcessed = 0;
$limitProcessed = 0;
$returnBlocks = array();
for ($i = 0; true; $i += WORDFENCE_BLOCKED_IPS_PER_PAGE) {
$blocks = wfBlock::allBlocks(true, $ofTypes, $i, WORDFENCE_BLOCKED_IPS_PER_PAGE, $sortColumn, $sortDirection);
if (empty($blocks)) {
break;
}
foreach ($blocks as $b) {
$include = false;
if (stripos($b->reason, $filter) !== false) {
$include = true;
}
if (!$include && $b->type == self::TYPE_PATTERN) {
if (stripos($b->hostname, $filter) !== false) { $include = true; }
else if (stripos($b->userAgent, $filter) !== false) { $include = true; }
else if (stripos($b->referrer, $filter) !== false) { $include = true; }
else if (stripos($b->ipRange, $filter) !== false) { $include = true; }
}
if (!$include && stripos(self::nameForType($b->type), $filter) !== false) {
$include = true;
}
if (!$include) {
switch ($matchType) {
case 'ip':
if ($b->matchRequest($matchValue, '', '') != self::MATCH_NONE) {
$include = true;
}
else if ($b->type == self::TYPE_LOCKOUT && wfUtils::inet_pton($matchValue) == wfUtils::inet_pton($b->ip)) {
$include = true;
}
break;
case 'ipregex':
if (preg_match('/' . $matchValue . '/i', $b->ip)) {
$include = true;
}
break;
case 'literal':
//Already checked above
break;
}
}
if ($include) {
if ($offsetProcessed < $offset) { //Still searching for the start offset
$offsetProcessed++;
continue;
}
$returnBlocks[] = $b;
$limitProcessed++;
}
if ($limit != -1 && $limitProcessed >= $limit) {
return $returnBlocks;
}
}
}
return $returnBlocks;
}
/**
* Returns all unexpired blocks of types wfBlock::TYPE_IP_MANUAL, wfBlock::TYPE_IP_AUTOMATIC_TEMPORARY, wfBlock::TYPE_IP_AUTOMATIC_PERMANENT, wfBlock::TYPE_WFSN_TEMPORARY, wfBlock::TYPE_RATE_BLOCK, and wfBlock::TYPE_RATE_THROTTLE.
*
* @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
* @return wfBlock[]
*/
public static function ipBlocks($prefetch = false) {
return self::allBlocks($prefetch, array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE));
}
/**
* Finds an IP block matching the given IP, returning it if found. Returns false if none are found.
*
* @param string $ip
* @return bool|wfBlock
*/
public static function findIPBlock($ip) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$query = "SELECT * FROM `{$blocksTable}` WHERE ";
$ofTypes = array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE);
$query .= "`type` IN (" . implode(', ', $ofTypes) . ') AND ';
$query .= "`IP` = {$ipHex} AND ";
$query .= '(`expiration` = ' . self::DURATION_FOREVER . ' OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `blockedTime` DESC LIMIT 1';
$r = $wpdb->get_row($query, ARRAY_A);
if (is_array($r)) {
$ip = wfUtils::inet_ntop($r['IP']);
return self::_recordFromRow($r);
}
return false;
}
/**
* Returns all unexpired blocks of type wfBlock::TYPE_COUNTRY.
*
* @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
* @return wfBlock[]
*/
public static function countryBlocks($prefetch = false) {
return self::allBlocks($prefetch, array(self::TYPE_COUNTRY));
}
/**
* Returns whether or not there is a country block rule.
*
* @return bool
*/
public static function hasCountryBlock() {
$countryBlocks = self::countryBlocks();
return !empty($countryBlocks);
}
/**
* Returns the value for the country blocking bypass cookie.
*
* @return string
*/
public static function countryBlockingBypassCookieValue() {
$val = wfConfig::get('cbl_cookieVal', false);
if (!$val) {
$val = uniqid();
wfConfig::set('cbl_cookieVal', $val);
}
return $val;
}
/**
* Returns all unexpired blocks of type wfBlock::TYPE_PATTERN.
*
* @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
* @return wfBlock[]
*/
public static function patternBlocks($prefetch = false) {
return self::allBlocks($prefetch, array(self::TYPE_PATTERN));
}
/**
* Returns all unexpired lockouts (type wfBlock::TYPE_LOCKOUT).
*
* @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
* @return wfBlock[]
*/
public static function lockouts($prefetch = false) {
return self::allBlocks($prefetch, array(self::TYPE_LOCKOUT));
}
/**
* Returns the lockout record for the given IP if it exists.
*
* @param string $ip
* @return bool|wfBlock
*/
public static function lockoutForIP($ip) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d AND (`expiration` = %d OR `expiration` > UNIX_TIMESTAMP())", self::TYPE_LOCKOUT, self::DURATION_FOREVER), ARRAY_A);
if ($row) {
return self::_recordFromRow($row);
}
return false;
}
/**
* Removes all blocks whose ID is in the given array.
*
* @param array $blockIDs
* @param bool $retrieve if true, fetch and return the deleted rows
* @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
* @return bool|array true(or an array of blocks, if $retrieve is specified) or false on failure
*/
public static function removeBlockIDs($blockIDs, $retrieve = false, $notify = true) {
$blockIDs = array_map('intval', $blockIDs);
$blocks = self::_recordsFromRows($blockIDs);
$result = self::removeMultiple($blocks, $notify);
if ($retrieve && $result) {
return $result;
}
return !!$result;
}
/**
* Removes all IP blocks (i.e., manual, wfsn, or rate limited)
*
* @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
*/
public static function removeAllIPBlocks($notify = true) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" . implode(', ', array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE, self::TYPE_LOCKOUT)) . ")", ARRAY_A);
$blocks = self::_recordsFromRows($rows);
self::removeMultiple($blocks, $notify);
}
/**
* Removes all country blocks
*
* @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
*/
public static function removeAllCountryBlocks($notify = true) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" . implode(', ', array(self::TYPE_COUNTRY)) . ")", ARRAY_A);
$blocks = self::_recordsFromRows($rows);
self::removeMultiple($blocks, $notify);
}
/**
* Removes all blocks that were created by WFSN responses.
*
* Note: if this ever becomes called by a manual user action, it should be refactored to call
* self::removeMultiple() in order to dispatch the relevant event.
*/
public static function removeTemporaryWFSNBlocks() {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$wpdb->query($wpdb->prepare("DELETE FROM `{$blocksTable}` WHERE `type` = %d", self::TYPE_WFSN_TEMPORARY));
}
/**
* Converts all blocks to non-expiring whose ID is in the given array.
*
* @param array $blockIDs
*/
public static function makePermanentBlockIDs($blockIDs) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
//TODO: revise this if we support user-customizable durations
$supportedTypes = array(
self::TYPE_WFSN_TEMPORARY,
self::TYPE_RATE_BLOCK,
self::TYPE_RATE_THROTTLE,
self::TYPE_LOCKOUT,
self::TYPE_IP_AUTOMATIC_TEMPORARY,
);
$blockIDs = array_map('intval', $blockIDs);
$query = $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" . implode(', ', $blockIDs) . ") AND `type` IN (" . implode(', ', $supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())", self::DURATION_FOREVER, self::TYPE_IP_AUTOMATIC_PERMANENT);
$wpdb->query($query);
$supportedTypes = array(
self::TYPE_IP_MANUAL,
);
$blockIDs = array_map('intval', $blockIDs);
$query = $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" . implode(', ', $blockIDs) . ") AND `type` IN (" . implode(', ', $supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())", self::DURATION_FOREVER, self::TYPE_IP_MANUAL);
$wpdb->query($query);
}
/**
* Removes all specific IP blocks and lockouts that can result in the given IP being blocked.
*
* @param string $ip
* @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
*/
public static function unblockIP($ip, $notify = true) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex}", ARRAY_A);
$blocks = self::_recordsFromRows($rows);
self::removeMultiple($blocks, $notify);
}
/**
* Removes all lockouts that can result in the given IP being blocked.
*
* @param string $ip
* @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
*/
public static function unlockOutIP($ip, $notify = true) {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
$rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d", self::TYPE_LOCKOUT), ARRAY_A);
$blocks = self::_recordsFromRows($rows);
self::removeMultiple($blocks, $notify);
}
/**
* Internal function to convert a raw query row result into a populated wfBlock instance. $row is expected to be an
* associative array.
*
* @param array $row
* @return mixed
*/
private static function _recordFromRow($row) {
$records = self::_recordsFromRows(array($row));
return $records[0];
}
/**
* Internal function to convert an array of raw query row results to an array of populated wfBlock instances. $rows
* is expected to be an array of integer IDs or an array of associative arrays.
*
* @param array[]|int[] $rows
* @return array
*/
private static function _recordsFromRows($rows) {
$records = array();
foreach ($rows as $r) {
if ($r instanceof stdClass) {
$r = (array) $r;
}
if (is_array($r)) {
$b = new wfBlock($r['id']);
$b->_populateData($r);
}
else {
$b = new wfBlock($r);
}
$records[] = $b;
}
return $records;
}
/**
* Constructs a wfBlock instance. This _does not_ create a new record in the table, only fetches or updates an existing one.
*
* @param $id
* @param bool $type
* @param bool $ip
* @param bool $blockedTime
* @param bool $reason
* @param bool $lastAttempt
* @param bool $blockedHits
* @param bool $expiration
* @param bool $parameters
*/
public function __construct($id, $type = false, $ip = false, $blockedTime = false, $reason = false, $lastAttempt = false, $blockedHits = false, $expiration = false, $parameters = false) {
$this->_id = $id;
$this->_type = $type;
$this->_ip = $ip;
$this->_blockedTime = $blockedTime;
$this->_reason = $reason;
$this->_lastAttempt = $lastAttempt;
$this->_blockedHits = $blockedHits;
$this->_expiration = $expiration;
$this->_parameters = $parameters;
}
public function __get($key) {
switch ($key) {
case 'id':
return $this->_id;
case 'type':
if ($this->_type === false) { $this->_fetch(); }
return $this->_type;
case 'IP':
case 'ip':
if ($this->_type === false) { $this->_fetch(); }
return $this->_ip;
case 'blockedTime':
if ($this->_type === false) { $this->_fetch(); }
return $this->_blockedTime;
case 'reason':
if ($this->_type === false) { $this->_fetch(); }
return $this->_reason;
case 'lastAttempt':
if ($this->_type === false) { $this->_fetch(); }
return $this->_lastAttempt;
case 'blockedHits':
if ($this->_type === false) { $this->_fetch(); }
return $this->_blockedHits;
case 'expiration':
if ($this->_type === false) { $this->_fetch(); }
return $this->_expiration;
case 'parameters':
if ($this->_type === false) { $this->_fetch(); }
return $this->_parameters;
//Country
case 'blockLogin':
if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['blockLogin'];
case 'blockSite':
if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['blockSite'];
case 'countries':
if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['countries'];
//Pattern
case 'ipRange':
if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['ipRange'];
case 'hostname':
if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['hostname'];
case 'userAgent':
if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['userAgent'];
case 'referrer':
if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
return $this->parameters['referrer'];
}
throw new OutOfBoundsException("{$key} is not a valid property");
}
public function __isset($key) {
switch ($key) {
case 'id':
case 'type':
case 'ip':
case 'blockedTime':
case 'reason':
case 'lastAttempt':
case 'blockedHits':
case 'expiration':
return true;
case 'parameters':
if ($this->_type === false) { $this->_fetch(); }
return !empty($this->_parameters);
//Country
case 'blockLogin':
if ($this->type != self::TYPE_COUNTRY) { return false; }
return !empty($this->parameters['blockLogin']);
case 'blockSite':
if ($this->type != self::TYPE_COUNTRY) { return false; }
return !empty($this->parameters['blockSite']);
case 'countries':
if ($this->type != self::TYPE_COUNTRY) { return false; }
return !empty($this->parameters['countries']);
//Pattern
case 'ipRange':
if ($this->type != self::TYPE_PATTERN) { return false; }
return !empty($this->parameters['ipRange']);
case 'hostname':
if ($this->type != self::TYPE_PATTERN) { return false; }
return !empty($this->parameters['hostname']);
case 'userAgent':
if ($this->type != self::TYPE_PATTERN) { return false; }
return !empty($this->parameters['userAgent']);
case 'referrer':
if ($this->type != self::TYPE_PATTERN) { return false; }
return !empty($this->parameters['referrer']);
}
return false;
}
/**
* Fetches the record for the block from the database and populates the instance variables.
*/
private function _fetch() {
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `id` = %d", $this->id), ARRAY_A);
if ($row !== null) {
$this->_populateData($row);
}
}
/**
* Populates the instance data from the given $row.
*
* @param array $row
*/
private function _populateData($row) {
$this->_type = $row['type'];
$ip = $row['IP'];
if ($ip == self::MARKER_COUNTRY || $ip == self::MARKER_PATTERN) {
$this->_ip = null;
}
else {
$this->_ip = wfUtils::inet_ntop($ip);
}
$this->_blockedTime = $row['blockedTime'];
$this->_reason = $row['reason'];
$this->_lastAttempt = $row['lastAttempt'];
$this->_blockedHits = $row['blockedHits'];
$this->_expiration = $row['expiration'];
$parameters = $row['parameters'];
if ($parameters === null) {
$this->_parameters = null;
}
else {
$this->_parameters = @json_decode($parameters, true);
}
}
/**
* Tests the block parameters against the given request. If matched, this will return the corresponding wfBlock::MATCH_
* constant. If not, it will return wfBlock::MATCH_NONE.
*
* @param $ip
* @param $userAgent
* @param $referrer
* @return int
*/
public function matchRequest($ip, $userAgent, $referrer) {
switch ($this->type) {
case self::TYPE_IP_MANUAL:
case self::TYPE_IP_AUTOMATIC_TEMPORARY:
case self::TYPE_IP_AUTOMATIC_PERMANENT:
case self::TYPE_WFSN_TEMPORARY:
case self::TYPE_RATE_BLOCK:
case self::TYPE_RATE_THROTTLE:
if (wfUtils::inet_pton($ip) == wfUtils::inet_pton($this->ip))
{
return self::MATCH_IP;
}
break;
case self::TYPE_PATTERN:
$match = (!empty($this->ipRange) || !empty($this->hostname) || !empty($this->userAgent) || !empty($this->referrer));
if (!empty($this->ipRange)) {
$range = new wfUserIPRange($this->ipRange);
$match = $match && $range->isIPInRange($ip);
}
if (!empty($this->hostname)) {
$hostname = wfUtils::reverseLookup($ip);
$match = $match && preg_match(wfUtils::patternToRegex($this->hostname), $hostname);
}
if (!empty($this->userAgent)) {
$match = $match && fnmatch($this->userAgent, $userAgent, FNM_CASEFOLD);
}
if (!empty($this->referrer)) {
$match = $match && fnmatch($this->referrer, $referrer, FNM_CASEFOLD);
}
if ($match) {
return self::MATCH_PATTERN;
}
break;
case self::TYPE_COUNTRY:
if (!wfConfig::get('isPaid')) {
return self::MATCH_NONE;
}
//Bypass Redirect URL Hit
$bareRequestURI = wfUtils::extractBareURI($_SERVER['REQUEST_URI']);
$bareBypassRedirURI = wfUtils::extractBareURI(wfConfig::get('cbl_bypassRedirURL', ''));
if ($bareBypassRedirURI && $bareRequestURI == $bareBypassRedirURI) {
$bypassRedirDest = wfConfig::get('cbl_bypassRedirDest', '');
if ($bypassRedirDest) {
wfUtils::setcookie('wfCBLBypass', wfBlock::countryBlockingBypassCookieValue(), time() + (86400 * 365), '/', null, wfUtils::isFullSSL(), true);
return self::MATCH_COUNTRY_REDIR_BYPASS;
}
}
//Bypass View URL Hit
$bareBypassViewURI = wfUtils::extractBareURI(wfConfig::get('cbl_bypassViewURL', ''));
if ($bareBypassViewURI && $bareBypassViewURI == $bareRequestURI) {
wfUtils::setcookie('wfCBLBypass', wfBlock::countryBlockingBypassCookieValue(), time() + (86400 * 365), '/', null, wfUtils::isFullSSL(), true);
return self::MATCH_NONE;
}
//Early exit checks
if ($this->_shouldBypassCountryBlocking()) { //Has valid bypass cookie
return self::MATCH_NONE;
}
if ($this->blockLogin) {
add_filter('authenticate', array($this, '_checkForBlockedCountryFilter'), 1, 1);
}
if (!$this->blockLogin && $this->_isAuthRequest()) { //Not blocking login and this is a login request
return self::MATCH_NONE;
}
else if (!$this->blockSite && !$this->_isAuthRequest()) { //Not blocking site and this may be a site request
return self::MATCH_NONE;
}
else if (is_user_logged_in() && !wfConfig::get('cbl_loggedInBlocked', false)) { //Not blocking logged in users and a login session exists
return self::MATCH_NONE;
}
//Block everything
if ($this->blockSite && $this->blockLogin) {
return $this->_checkForBlockedCountry();
}
//Block the login form itself and any attempt to authenticate
if ($this->blockLogin && $this->_isAuthRequest()) {
return $this->_checkForBlockedCountry();
}
//Block requests that aren't to the login page, xmlrpc.php, or a user already logged in
if ($this->blockSite && !$this->_isAuthRequest() && !defined('XMLRPC_REQUEST')) {
return $this->_checkForBlockedCountry();
}
//XMLRPC is inaccesible when public portion of the site and auth is disabled
if ($this->blockLogin && $this->blockSite && defined('XMLRPC_REQUEST')) {
return $this->_checkForBlockedCountry();
}
break;
}
return self::MATCH_NONE;
}
/**
* Returns whether or not the current request should be treated as an auth request.
*
* @return bool
*/
private function _isAuthRequest() {
if ((strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false)) {
return true;
}
return false;
}
/**
* Tests whether or not the country blocking bypass cookie is set and valid.
*
* @return bool
*/
private function _shouldBypassCountryBlocking() {
if (isset($_COOKIE['wfCBLBypass']) && $_COOKIE['wfCBLBypass'] == wfBlock::countryBlockingBypassCookieValue()) {
return true;
}
return false;
}
/**
* Checks the country block against the requesting IP, returning the action to take.
*
* @return int
*/
private function _checkForBlockedCountry() {
$blockedCountries = $this->countries;
$bareRequestURI = untrailingslashit(wfUtils::extractBareURI($_SERVER['REQUEST_URI']));
$IP = wfUtils::getIP();
if ($country = wfUtils::IP2Country($IP)) {
foreach ($blockedCountries as $blocked) {
if (strtoupper($blocked) == strtoupper($country)) { //At this point we know the user has been blocked
if (wfConfig::get('cbl_action') == 'redir') {
$redirURL = wfConfig::get('cbl_redirURL');
$eRedirHost = wfUtils::extractHostname($redirURL);
$isExternalRedir = false;
if ($eRedirHost && $eRedirHost != wfUtils::extractHostname(home_url())) { //It's an external redirect...
$isExternalRedir = true;
}
if ((!$isExternalRedir) && untrailingslashit(wfUtils::extractBareURI($redirURL)) == $bareRequestURI) { //Is this the URI we want to redirect to, then don't block it
return self::MATCH_NONE;
}
else {
return self::MATCH_COUNTRY_REDIR;
}
}
else {
return self::MATCH_COUNTRY_BLOCK;
}
}
}
}
return self::MATCH_NONE;
}
/**
* Filter hook for the country blocking check. Does nothing if not blocked, otherwise presents the block page and exits.
*
* Note: Must remain `public` for callback to work.
*/
public function _checkForBlockedCountryFilter($user) {
$block = $this->_checkForBlockedCountry();
if ($block == self::MATCH_NONE) {
return $user;
}
$log = wfLog::shared();
$log->getCurrentRequest()->actionDescription = __('blocked access via country blocking', 'wordfence');
wfConfig::inc('totalCountryBlocked');
wfActivityReport::logBlockedIP(wfUtils::getIP(), null, 'country');
$log->do503(3600, __('Access from your area has been temporarily limited for security reasons', 'wordfence')); //exits
}
/**
* Adds $quantity to the blocked count and sets the timestamp for lastAttempt.
*
* @param int $quantity
* @param bool|int $timestamp
*/
public function recordBlock($quantity = 1, $timestamp = false) {
if ($timestamp === false) {
$timestamp = time();
}
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `blockedHits` = `blockedHits` + %d, `lastAttempt` = GREATEST(`lastAttempt`, %d) WHERE `id` = %d", $quantity, $timestamp, $this->id));
$this->_type = false; //Trigger a re-fetch next access
}
/**
* Returns an array suitable for JSON of the values needed to edit the block.
*
* @return array
*/
public function editValues() {
switch ($this->type) {
case self::TYPE_COUNTRY:
return array(
'blockLogin' => wfUtils::truthyToInt($this->blockLogin),
'blockSite' => wfUtils::truthyToInt($this->blockSite),
'countries' => $this->countries,
'reason' => $this->reason,
'expiration' => $this->expiration,
);
}
return array();
}
/**
* Removes this block record. May trigger an additional query to fetch the notification data if $notify is true and
* the record is ID-only.
*
* @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
* @return null|wfBlock null if a failure occurs, otherwise the block
*/
public function remove($notify = false) {
$result = self::removeMultiple(array($this), $notify);
if (is_array($result)) {
return $result[0];
}
return null;
}
/**
* Deletes the given block, optionally dispatching the `wordfence_deleted_block` action for each block. May trigger
* an additional query to fetch the notification data if $notify is true and any record is ID-only.
*
* @param wfBlock[] $blocks
* @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
* @return null|wfBlock[] null if a failure occurs, otherwise the blocks
*/
public static function removeMultiple($blocks, $notify = false) {
if (empty($blocks)) { return array(); }
global $wpdb;
$blocksTable = wfBlock::blocksTable();
$blockIDs = array_map(function($b) { return intval($b->id); }, $blocks);
$inClause = implode(', ', $blockIDs);
if ($notify) {
$blockIDsToPopulate = array_filter(array_map(function($b) { return ($b->_type === false ? intval($b->id) : null); }, $blocks));
if (!empty($blockIDsToPopulate)) {
$populateInClause = implode(', ', $blockIDsToPopulate);
$data = wfUtils::array_kmap(function($r) { return array($r['id'] => $r); }, $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `id` IN ({$populateInClause})", ARRAY_A));
foreach ($blocks as $b) { /** @var wfBlock $b */
if (isset($data[$b->id])) {
$b->_populateData($data[$b->id]);
}
}
}
}
$query = "DELETE FROM `{$blocksTable}` WHERE `id` IN (" . $inClause . ")";
if ($wpdb->query($query) !== false) {
$payload = array();
if ($notify) {
foreach ($blocks as $b) {
$type = $b->type;
$reason = $b->reason;
$parameters = (($type != self::TYPE_COUNTRY && $type != self::TYPE_PATTERN) ? $b->ip : $b->parameters);
/**
* Fires when a blocking rule is deleted by manual action.
*
* @since 8.0.0
*
* @param string $type The type of block.
* @param string $reason The reason of the block.
* @param array|null $parameters The parameters of the block if needed for disambiguation (e.g., the country block returns null because there is only one rule at most)
*/
do_action('wordfence_deleted_block', $type, $reason, $parameters);
}
}
return $blocks;
}
return null;
}
} block/wfRateLimit.php 0000644 00000017332 14720676271 0010620 0 ustar 00 queryWrite("DELETE FROM {$table} WHERE eMin < FLOOR((UNIX_TIMESTAMP() - 60) / 60)");
}
public static function globalRateLimit() {
static $_cachedGlobal = null;
if ($_cachedGlobal === null) {
$_cachedGlobal = new wfRateLimit(self::TYPE_GLOBAL);
}
return $_cachedGlobal;
}
public static function crawlerViewsRateLimit() {
static $_cachedCrawlerViews = null;
if ($_cachedCrawlerViews === null) {
$_cachedCrawlerViews = new wfRateLimit(self::TYPE_CRAWLER_VIEWS);
}
return $_cachedCrawlerViews;
}
public static function crawler404sRateLimit() {
static $_cachedCrawler404s = null;
if ($_cachedCrawler404s === null) {
$_cachedCrawler404s = new wfRateLimit(self::TYPE_CRAWLER_404S);
}
return $_cachedCrawler404s;
}
public static function humanViewsRateLimit() {
static $_cachedHumanViews = null;
if ($_cachedHumanViews === null) {
$_cachedHumanViews = new wfRateLimit(self::TYPE_HUMAN_VIEWS);
}
return $_cachedHumanViews;
}
public static function human404sRateLimit() {
static $_cachedHuman404s = null;
if ($_cachedHuman404s === null) {
$_cachedHuman404s = new wfRateLimit(self::TYPE_HUMAN_404S);
}
return $_cachedHuman404s;
}
/**
* Returns whether or not humans and bots have the same rate limits configured.
*
* @return bool
*/
public static function identicalHumanBotRateLimits() {
$humanViews = self::humanViewsRateLimit();
$crawlerViews = self::crawlerViewsRateLimit();
if ($humanViews->isEnabled() != $crawlerViews->isEnabled()) {
return false;
}
if ($humanViews->limit() != $crawlerViews->limit()) {
return false;
}
$human404s = self::human404sRateLimit();
$crawler404s = self::crawler404sRateLimit();
if ($human404s->isEnabled() != $crawler404s->isEnabled()) {
return false;
}
if ($human404s->limit() != $crawler404s->limit()) {
return false;
}
return true;
}
public static function mightRateLimit($hitType) {
if (!wfConfig::get('firewallEnabled')) {
return false;
}
$IP = wfUtils::getIP();
if (wfBlock::isWhitelisted($IP)) {
return false;
}
if (wfConfig::get('neverBlockBG') == 'neverBlockUA' && wfCrawl::isGoogleCrawler()) {
return false;
}
if (wfConfig::get('neverBlockBG') == 'neverBlockVerified' && wfCrawl::isVerifiedGoogleCrawler()) {
return false;
}
if ($hitType == '404') {
$allowed404s = wfConfig::get('allowed404s');
if (is_string($allowed404s)) {
$allowed404s = array_filter(preg_split("/[\r\n]+/", $allowed404s));
$allowed404sPattern = '';
foreach ($allowed404s as $allowed404) {
$allowed404sPattern .= preg_replace('/\\\\\*/', '.*?', preg_quote($allowed404, '/')) . '|';
}
$uri = $_SERVER['REQUEST_URI'];
if (($index = strpos($uri, '?')) !== false) {
$uri = substr($uri, 0, $index);
}
if ($allowed404sPattern && preg_match('/^' . substr($allowed404sPattern, 0, -1) . '$/i', $uri)) {
return false;
}
}
}
if (self::globalRateLimit()->isEnabled()) {
return true;
}
$visitorType = self::visitorType();
if ($visitorType == self::VISITOR_TYPE_CRAWLER) {
if ($hitType == self::HIT_TYPE_NORMAL) {
if (self::crawlerViewsRateLimit()->isEnabled()) {
return true;
}
}
else {
if (self::crawler404sRateLimit()->isEnabled()) {
return true;
}
}
}
else {
if ($hitType == self::HIT_TYPE_NORMAL) {
if (self::humanViewsRateLimit()->isEnabled()) {
return true;
}
}
else {
if (self::human404sRateLimit()->isEnabled()) {
return true;
}
}
}
return false;
}
public static function countHit($hitType, $ip) {
$table = self::table();
$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
wfDB::shared()->queryWrite("INSERT INTO {$table} (eMin, IP, hitType, hits) VALUES (FLOOR(UNIX_TIMESTAMP() / 60), {$ipHex}, %s, @wfcurrenthits := 1) ON DUPLICATE KEY UPDATE hits = IF(@wfcurrenthits := hits + 1, hits + 1, hits + 1)", $hitType);
}
/**
* Returns one of the VISITOR_TYPE_ constants for the purposes of determining which rate limit to apply.
*
* @return string
*/
public static function visitorType() {
static $_cachedVisitorType = null;
if ($_cachedVisitorType === null) {
$_cachedVisitorType = ((isset($_SERVER['HTTP_USER_AGENT']) && wfCrawl::isCrawler($_SERVER['HTTP_USER_AGENT'])) || empty($_SERVER['HTTP_USER_AGENT']) ? wfRateLimit::VISITOR_TYPE_CRAWLER : wfRateLimit::VISITOR_TYPE_HUMAN);
}
return $_cachedVisitorType;
}
protected function __construct($type) {
$this->_type = $type;
}
/**
* Returns whether or not this rate limit is configured in a way where it would run.
*
* @return bool
*/
public function isEnabled() {
switch ($this->_type) {
case self::TYPE_GLOBAL:
return wfConfig::get('maxGlobalRequests') != 'DISABLED' && wfConfig::getInt('maxGlobalRequests') > 0;
case self::TYPE_CRAWLER_VIEWS:
return wfConfig::get('maxRequestsCrawlers') != 'DISABLED' && wfConfig::getInt('maxRequestsCrawlers') > 0;
case self::TYPE_CRAWLER_404S:
return wfConfig::get('max404Crawlers') != 'DISABLED' && wfConfig::getInt('max404Crawlers') > 0;
case self::TYPE_HUMAN_VIEWS:
return wfConfig::get('maxRequestsHumans') != 'DISABLED' && wfConfig::getInt('maxRequestsHumans') > 0;
case self::TYPE_HUMAN_404S:
return wfConfig::get('max404Humans') != 'DISABLED' && wfConfig::getInt('max404Humans') > 0;
}
return true;
}
public function limit() {
switch ($this->_type) {
case self::TYPE_GLOBAL:
return wfConfig::getInt('maxGlobalRequests');
case self::TYPE_CRAWLER_VIEWS:
return wfConfig::getInt('maxRequestsCrawlers');
case self::TYPE_CRAWLER_404S:
return wfConfig::getInt('max404Crawlers');
case self::TYPE_HUMAN_VIEWS:
return wfConfig::getInt('maxRequestsHumans');
case self::TYPE_HUMAN_404S:
return wfConfig::getInt('max404Humans');
}
return -1;
}
public function shouldEnforce($hitType) {
switch ($this->_type) {
case self::TYPE_GLOBAL:
return $this->isEnabled() && $this->_hitCount() > max(wfConfig::getInt('maxGlobalRequests'), 1);
case self::TYPE_CRAWLER_VIEWS:
return self::visitorType() == self::VISITOR_TYPE_CRAWLER && $hitType == self::HIT_TYPE_NORMAL && $this->isEnabled() && $this->_hitCount() > wfConfig::getInt('maxRequestsCrawlers');
case self::TYPE_CRAWLER_404S:
return self::visitorType() == self::VISITOR_TYPE_CRAWLER && $hitType == self::HIT_TYPE_404 && $this->isEnabled() && $this->_hitCount() > wfConfig::getInt('max404Crawlers');
case self::TYPE_HUMAN_VIEWS:
return self::visitorType() == self::VISITOR_TYPE_HUMAN && $hitType == self::HIT_TYPE_NORMAL && $this->isEnabled() && $this->_hitCount() > wfConfig::getInt('maxRequestsHumans');
case self::TYPE_HUMAN_404S:
return self::visitorType() == self::VISITOR_TYPE_HUMAN && $hitType == self::HIT_TYPE_404 && $this->isEnabled() && $this->_hitCount() > wfConfig::getInt('max404Humans');
}
return false;
}
/**
* Returns the hit count corresponding to the current request type.
*
* @return int
*/
protected function _hitCount() {
if (self::$_hitCount === false) {
self::$_hitCount = (int) wfDB::shared()->querySingle("SELECT @wfcurrenthits");
}
return self::$_hitCount;
}
} .htaccess 0000644 00000000542 14720676271 0006357 0 ustar 00
RewriteEngine On
RewriteCond %{REQUEST_URI} \.php$
RewriteRule .* - [F,L,NC]
Require all denied
Order deny,allow
Deny from all