PK!OAß߬¬scanner/wfScanner.phpnu„[µü¤ $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)); } }PK!“ÅFTMMcommon/wfTab.phpnu„[µü¤_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); } }PK!Õb>›00page/wfPage.phpnu„[µü¤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(); } } PK! ¨Â-n@n@firewall/wfFirewall.phpnu„[µü¤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); } } PK!íñw“??block/wfBlock.phpnu„[µü¤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; } }PK!â)§°ÚÚblock/wfRateLimit.phpnu„[µü¤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; } }PK!o‡bb .htaccessnu„[µü¤ RewriteEngine On RewriteCond %{REQUEST_URI} \.php$ RewriteRule .* - [F,L,NC] Require all denied Order deny,allow Deny from all PK!OAß߬¬scanner/wfScanner.phpnu„[µü¤PK!“ÅFTMM]¬common/wfTab.phpnu„[µü¤PK!Õb>›00ê¯page/wfPage.phpnu„[µü¤PK! ¨Â-n@n@YÌfirewall/wfFirewall.phpnu„[µü¤PK!íñw“?? block/wfBlock.phpnu„[µü¤PK!â)§°ÚÚŽ block/wfRateLimit.phpnu„[µü¤PK!o‡bb ­,.htaccessnu„[µü¤PK:H.