he 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_active() &&
$global_ip->is_ip_blocked( $ip )
) {
return array(
'reason' => 'global_ip',
'result' => true,
);
}
/**
* Retrieve Antibot_Global_Firewall instance.
*
* @var IP\Antibot_Global_Firewall $antibot
*/
$antibot = wd_di()->get( IP\Antibot_Global_Firewall::class );
if (
$antibot->is_active() &&
$antibot->is_ip_blocked( $ip ) &&
'plugin' === $antibot->get_managed_by()
) {
return array(
'reason' => IP\Antibot_Global_Firewall::REASON_SLUG,
'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.
*
* @since 4.4.2
* @deprecated 5.1.0 This method and the 'wpdef_firewall_gathered_ips' filter are no longer in use.
* @return array
*/
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
* @deprecated 5.1.0 No longer used and will be removed in a future version.
*/
$gathered_ips = (array) apply_filters_deprecated(
'wpdef_firewall_gathered_ips',
array_unique( $gathered_ips ),
'5.1.0',
'',
'This filter will be removed in a future version. If you are using it, update your implementation.'
);
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 );
}
}
/**
* Is whitelist server public IP enabled.
*
* @return bool
* @since 5.0.2
*/
public function is_whitelist_server_public_ip_enabled(): bool {
/**
* Filter to enable/disable fetching server public IP.
*
* @param bool $enable True to enable whitelist server public IP, false otherwise.
*
* @since 5.0.2
*/
return (bool) apply_filters( 'wpdef_firewall_whitelist_server_public_ip_enabled', true );
}
/**
* Set whitelist server public IP.
*
* @return bool
* @since 5.0.2
*/
public function set_whitelist_server_public_ip(): bool {
if ( ! $this->is_whitelist_server_public_ip_enabled() ) {
return false;
}
$ip = wd_di()->get( Smart_Ip_Detection::class )->get_server_public_ip();
if ( empty( $ip ) ) {
$this->log( 'Failed to whitelist server public IP.', Firewall_Controller::FIREWALL_LOG );
return false;
}
$stored_ip = $this->get_whitelist_server_public_ip();
if ( $stored_ip !== $ip ) {
update_site_option( self::WHITELIST_SERVER_PUBLIC_IP_OPTION, $ip );
}
$this->log( 'Server public IP whitelisted successfully.', Firewall_Controller::FIREWALL_LOG );
return true;
}
/**
* Get whitelist server public IP.
*
* @return string
* @since 5.0.2
*/
public function get_whitelist_server_public_ip(): string {
return $this->is_whitelist_server_public_ip_enabled() ?
get_site_option( self::WHITELIST_SERVER_PUBLIC_IP_OPTION, '' ) :
'';
}
}