Server IP : 172.67.157.199 / Your IP : 18.118.30.33 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/chroot/var/www/wp-content/plugins/defender-security/src/component/ |
Upload File : |
<?php /** * Handles operations related to IP and country-based blacklisting and * whitelisting, as well as managing firewall settings and logs. * * @package WP_Defender\Component */ namespace WP_Defender\Component; use Exception; use WPMUDEV_Dashboard; use WP_Defender\Component; use WP_Defender\Model\Lockout_Ip; use WP_Defender\Behavior\WPMUDEV; use WP_Defender\Model\Lockout_Log; use WP_Defender\Model\Onboard; use MaxMind\Db\Reader\InvalidDatabaseException; use WP_Defender\Model\Setting\Firewall as Model_Firewall; use WP_Defender\Component\Trusted_Proxy_Preset\Cloudflare_Proxy; use WP_Defender\Component\Trusted_Proxy_Preset\Trusted_Proxy_Preset; use WP_Defender\Controller\Firewall as Firewall_Controller; use WP_Defender\Component\Smart_Ip_Detection; /** * Handles operations related to IP and country-based blacklisting and whitelisting, * managing firewall settings, logs, and automatic detection of proxy configurations. */ class Firewall extends Component { /** * The notice slug if there is switching IP Detection option to Cloudflare (CF). */ public const IP_DETECTION_CF_SHOW_SLUG = 'wd_show_ip_detection_cf_notice'; /** * The notice slug if CF IP Detection notice is rejected. */ public const IP_DETECTION_CF_DISMISS_SLUG = 'wd_dismiss_ip_detection_cf_notice'; /** * The notice slug if there is switching IP Detection option to X-Forwarded-For (XFF). */ public const IP_DETECTION_XFF_SHOW_SLUG = 'wd_show_ip_detection_xff_notice'; /** * The notice slug if CF IP Detection notice is rejected. */ public const IP_DETECTION_XFF_DISMISS_SLUG = 'wd_dismiss_ip_detection_xff_notice'; /** * Check if the first commencing request is proper staff remote access. * * @param array $access The access details including key. * * @return bool */ private function is_commencing_staff_access( $access ): bool { $action = defender_get_data_from_request( 'action', 'g' ); $wdpunkey = defender_get_data_from_request( 'wdpunkey', 'p' ); return wp_doing_ajax() && 'wdpunauth' === $action && hash_equals( $wdpunkey, $access['key'] ); } /** * Check is the access from authenticated staff. * * @return bool */ private function is_authenticated_staff_access(): bool { return '1' === defender_get_data_from_request( 'wpmudev_is_staff', 'c' ); } /** * Check if the access is from our staff access. * * @return bool */ private function is_a_staff_access(): bool { if ( defined( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) && true === constant( 'WPMUDEV_DISABLE_REMOTE_ACCESS' ) ) { return false; } $wpmu_dev = new WPMUDEV(); $is_remote_access = $wpmu_dev->get_apikey() && true === WPMUDEV_Dashboard::$api->remote_access_details( 'enabled' ); if ( $is_remote_access ) { $access = $wpmu_dev->get_remote_access(); if ( $this->is_authenticated_staff_access() || $this->is_commencing_staff_access( $access ) ) { $this->log( $access, Firewall_Controller::FIREWALL_LOG ); return true; } } return false; } /** * Cron for delete old log. * * @throws Exception On failure. */ public function firewall_clean_up_logs() { $settings = new Model_Firewall(); $storage_days = apply_filters( 'ip_lockout_logs_store_backward', $settings->storage_days ); if ( ! is_numeric( $storage_days ) ) { return; } $time_string = '-' . $storage_days . ' days'; $timestamp = $this->local_to_utc( $time_string ); Lockout_Log::remove_logs( $timestamp, 50 ); } /** * Cron for clean up temporary IP block list. */ public function firewall_clean_up_temporary_ip_blocklist() { $models = Lockout_Ip::get_bulk( Lockout_Ip::STATUS_BLOCKED ); foreach ( $models as $model ) { $model->status = Lockout_Ip::STATUS_NORMAL; $model->save(); } } /** * Update temporary IP block list of Firewall, clear cron job. * The interval settings value is updated once. * * @param string $new_interval The new interval to set. */ public function update_cron_schedule_interval( $new_interval ) { $settings = new Model_Firewall(); // If a new interval is different from the saved value, we need to clear the cron job. if ( $new_interval !== $settings->ip_blocklist_cleanup_interval ) { update_site_option( 'wpdef_clear_schedule_firewall_cleanup_temp_blocklist_ips', true ); } } /** * Skip priority lockout checks for a given IP. * * @param string $ip The IP address to check. * * @return bool True if the IP should be skipped from lockout checks, false otherwise. * @throws InvalidDatabaseException Thrown for unexpected data is found in DB. */ public function skip_priority_lockout_checks( string $ip ): bool { /** * Retrieve Global_IP instance. * * @var IP\Global_IP $global_ip */ $global_ip = wd_di()->get( IP\Global_IP::class ); if ( $global_ip->is_global_ip_enabled() && $global_ip->is_ip_allowed( $ip ) ) { return true; } /** * Retrieve Blacklist_Lockout instance. * * @var Blacklist_Lockout $service */ $service = wd_di()->get( Blacklist_Lockout::class ); $model = Lockout_Ip::get( $ip ); $is_lockout_ip = is_object( $model ) && $model->is_locked(); $is_country_whitelisted = ! $service->is_blacklist( $ip ) && $service->is_country_whitelist( $ip ) && ! $is_lockout_ip; // If this IP is whitelisted, so we don't need to blacklist this. if ( $service->is_ip_whitelisted( $ip ) || $is_country_whitelisted ) { return true; } // Green light if access staff is enabled. if ( $this->is_a_staff_access() ) { return true; } return false; } /** * Check if an IP is blacklisted. * * @param string $ip The IP address to check. * * @return array An array containing the reason and result of the blacklist check. * @throws InvalidDatabaseException Thrown for unexpected data is found in DB. */ public function is_blocklisted_ip( string $ip ): array { $array = array( 'reason' => '', 'result' => false, ); /** * Retrieve Blacklist_Lockout instance. * * @var Blacklist_Lockout $service */ $service = wd_di()->get( Blacklist_Lockout::class ); if ( $service->is_blacklist( $ip ) ) { return array( 'reason' => 'local_ip', 'result' => true, ); } if ( $service->is_country_blacklist( $ip ) ) { return array( 'reason' => 'country', 'result' => true, ); } /** * Retrieve Global_IP instance. * * @var IP\Global_IP $global_ip */ $global_ip = wd_di()->get( IP\Global_IP::class ); if ( $global_ip->is_global_ip_enabled() && $global_ip->is_ip_blocked( $ip ) ) { return array( 'reason' => 'global_ip', 'result' => true, ); } return $array; } /** * Get the limit of Lockout records. * * @return int * @since 3.7.0 Get the limit of Lockout records. */ public function get_lockout_record_limit() { return (int) apply_filters( 'wd_lockout_record_limit', 10000 ); } /** * Cron for deleting unwanted lockout records. * * @return void * @since 3.8.0 */ public function firewall_clean_up_lockout(): void { global $wpdb; $current_timestamp = time(); $limit = $this->get_lockout_record_limit(); $table = $wpdb->base_prefix . ( new Lockout_Ip() )->get_table(); do { $affected_rows = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->prepare( "DELETE FROM {$table} WHERE (release_time = 0 OR release_time < %d) AND meta IN (%s, %s, %s, %s, %s) ORDER BY id LIMIT %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $current_timestamp, '[]', '{"nf":[]}', '{"login":[]}', '{"nf":[],"login":[]}', '{"login":[],"nf":[]}', $limit ) ); } while ( $affected_rows === $limit ); } /** * Gather IP(s) from headers. * * @return array * @since 4.4.2 */ public function gather_ips(): array { $ip_headers = array( 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR', ); $gathered_ips = array(); $server = defender_get_data_from_request( null, 's' ); foreach ( $ip_headers as $header ) { if ( ! empty( $server[ $header ] ) ) { // Handle multiple IP addresses. $ips = array_map( 'trim', explode( ',', $server[ $header ] ) ); foreach ( $ips as $ip ) { if ( $this->validate_ip( $ip ) ) { $gathered_ips[] = $ip; } } } } /** * Filter the gathered IPs before checking the lockout records. * * @param array $gathered_ips IPs gathered from request headers. * * @since 4.5.1 */ $gathered_ips = (array) apply_filters( 'wpdef_firewall_gathered_ips', array_unique( $gathered_ips ) ); return $this->filter_user_ips( $gathered_ips ); } /** * Check if the current request is recognized as coming from Cloudflare. * * @return bool */ public function is_cloudflare_request(): bool { $is_cloudflare = true; $server = defender_get_data_from_request( null, 's' ); if ( ! ( isset( $server['HTTP_CF_CONNECTING_IP'] ) || isset( $server['HTTP_CF_IPCOUNTRY'] ) || isset( $server['HTTP_CF_RAY'] ) || isset( $server['HTTP_CF_VISITOR'] ) ) ) { $is_cloudflare = false; } return $is_cloudflare; } /** * Auto-detect proxy server and switch to appropriate IP Detection option. * * @return void */ public function auto_switch_ip_detection_option(): void { $model = wd_di()->get( Model_Firewall::class ); if ( $this->is_cloudflare_request() ) { if ( 'HTTP_CF_CONNECTING_IP' !== $model->http_ip_header && ! self::is_switched_ip_detection_notice( self::IP_DETECTION_CF_SHOW_SLUG ) ) { $model->http_ip_header = 'HTTP_CF_CONNECTING_IP'; update_site_option( self::IP_DETECTION_CF_SHOW_SLUG, true ); } $model->trusted_proxy_preset = Cloudflare_Proxy::PROXY_SLUG; $model->save(); // Fetch trusted proxy ips. $this->update_trusted_proxy_preset_ips(); } } /** * Update trusted proxy preset IPs. * * @return void */ public function update_trusted_proxy_preset_ips(): void { $model = wd_di()->get( Model_Firewall::class ); if ( ! empty( $model->trusted_proxy_preset ) ) { /** * Retrieve Trusted_Proxy_Preset instance. * * @var Trusted_Proxy_Preset $trusted_proxy_preset */ $trusted_proxy_preset = wd_di()->get( Trusted_Proxy_Preset::class ); $trusted_proxy_preset->set_proxy_preset( $model->trusted_proxy_preset ); $trusted_proxy_preset->update_ips(); } } /** * Show a notice if wrong IP Detection option is configured for the site. * * @return void */ public function maybe_show_misconfigured_ip_detection_option_notice(): void { $model = wd_di()->get( Model_Firewall::class ); $xff = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' ); if ( 'HTTP_X_FORWARDED_FOR' !== $model->http_ip_header && ( is_string( $xff ) && 0 < strlen( $xff ) ) && ! $this->is_cloudflare_request() && ! self::is_xff_notice_ready() ) { update_site_option( self::IP_DETECTION_XFF_SHOW_SLUG, true ); } } /** * Check if a switched IP detection notice has been shown. * * @param string $key The notice key to check. * * @return bool True if the notice has been shown, false otherwise. */ public static function is_switched_ip_detection_notice( string $key ): bool { return (bool) get_site_option( $key ); } /** * Check if the XFF notice is ready to be shown. * * @return bool */ public static function is_xff_notice_ready(): bool { return ! Onboard::maybe_show_onboarding() && self::is_switched_ip_detection_notice( self::IP_DETECTION_XFF_SHOW_SLUG ) && ! self::is_switched_ip_detection_notice( self::IP_DETECTION_XFF_DISMISS_SLUG ) && ! wd_di()->get( Smart_Ip_Detection::class )->is_smart_ip_detection_enabled() && 'HTTP_X_FORWARDED_FOR' !== wd_di()->get( Model_Firewall::class )->http_ip_header; } /** * Check if the CF notice is ready to be shown. * * @return bool */ public static function is_cf_notice_ready(): bool { return self::is_switched_ip_detection_notice( self::IP_DETECTION_CF_SHOW_SLUG ) && ! self::is_switched_ip_detection_notice( self::IP_DETECTION_CF_DISMISS_SLUG ) && ! wd_di()->get( Smart_Ip_Detection::class )->is_smart_ip_detection_enabled(); } /** * Delete all notice slugs related to IP detection switching. */ public static function delete_slugs(): void { delete_site_option( self::IP_DETECTION_CF_SHOW_SLUG ); delete_site_option( self::IP_DETECTION_CF_DISMISS_SLUG ); delete_site_option( self::IP_DETECTION_XFF_SHOW_SLUG ); delete_site_option( self::IP_DETECTION_XFF_DISMISS_SLUG ); } /** * Get the first blocked IP. * * @param array $ips The array of IPs to check. * * @return string * @throws InvalidDatabaseException Thrown for unexpected data is found in DB. */ public function get_blocked_ip( $ips ): string { $blocked_ip = ''; foreach ( $ips as $ip ) { $is_blocklisted = $this->is_blocklisted_ip( $ip ); if ( $is_blocklisted['result'] ) { $blocked_ip = $ip; break; } } // Do not continue if there is not a single blocked IP. if ( '' === $blocked_ip ) { // Maybe IP(-s) in Active lockouts? if ( count( $ips ) > 1 ) { $models = Lockout_Ip::get_bulk( Lockout_Ip::STATUS_BLOCKED, $ips ); foreach ( $models as $model ) { $blocked_ip = $model->ip; break; } } elseif ( null !== Lockout_Ip::is_blocklisted_ip( $ips[0] ) ) { $blocked_ip = $ips[0]; } } return $blocked_ip; } /** * Get custom HTTP headers used for IP detection. * * @return array List of custom HTTP headers. */ public static function custom_http_headers(): array { return array( 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CF_CONNECTING_IP', ); } /** * Get trusted proxy presets. * * @return array List of trusted proxy presets. */ public static function trusted_proxy_presets(): array { return array( Cloudflare_Proxy::PROXY_SLUG => esc_html__( 'Cloudflare', 'defender-security' ), ); } /** * Dismiss the CF notice if the IP Detection is set to automatic. */ public function maybe_dismiss_cf_notice(): void { if ( wd_di()->get( Smart_Ip_Detection::class )->is_smart_ip_detection_enabled() && self::is_switched_ip_detection_notice( self::IP_DETECTION_CF_SHOW_SLUG ) && ! self::is_switched_ip_detection_notice( self::IP_DETECTION_CF_DISMISS_SLUG ) ) { update_site_option( self::IP_DETECTION_CF_DISMISS_SLUG, true ); } } }