compare_v4_in_range( $ip, $first_in_range, $last_in_range ); } elseif ( $this->is_v6( $first_in_range ) && $this->is_v6( $last_in_range ) && $this->is_v6_support() ) { return $this->compare_v6_in_range( $ip, $first_in_range, $last_in_range ); } return false; } /** * @param $ip * @param $block * * @return bool */ public function compare_cidr( $ip, $block ): bool { [$subnet, $bits] = explode( '/', $block ); if ( $this->is_v4( $ip ) && $this->is_v4( $subnet ) ) { return $this->compare_cidrv4( $ip, $block ); } elseif ( $this->is_v6( $ip ) && $this->is_v6( $subnet ) && $this->is_v6_support() ) { return $this->compare_cidrv6( $ip, $block ); } return false; } /** * $ip an be single ip, or a range like xxx.xxx.xxx.xxx - xxx.xxx.xxx.xxx or CIDR. * @param string $ip * * @return bool */ public function validate_ip( $ip ): bool { if ( ! stristr( $ip, '-' ) && ! stristr( $ip, '/' ) && filter_var( $ip, FILTER_VALIDATE_IP ) ) { // Only ip, no '-', '/' symbols. return true; } elseif ( stristr( $ip, '-' ) ) { $ips = explode( '-', $ip ); foreach ( $ips as $ip_key ) { if ( ! filter_var( $ip_key, FILTER_VALIDATE_IP ) ) { return false; } } if ( $this->compare_ip( $ips[0], $ips[1] ) ) { return true; } } elseif ( stristr( $ip, '/' ) ) { [$ip, $bits] = explode( '/', $ip ); if ( filter_var( $ip, FILTER_VALIDATE_IP ) && filter_var( $bits, FILTER_VALIDATE_INT ) ) { if ( $this->is_v4( $ip ) && 0 <= $bits && $bits <= 32 ) { return true; } elseif ( $this->is_v6( $ip ) && 0 <= $bits && $bits <= 128 && $this->is_v6_support() ) { return true; } } } return false; } /** * Display a message if IP is non-valid. The cases: * 1) single IP (no '-', '/' symbols), * 2) IP range, * 3) CIDR. * Ignore cases with private, reserved ranges. * @src https://en.wikipedia.org/wiki/IPv4#Special-use_addresses * @src https://en.wikipedia.org/wiki/IPv6#Special-use_addresses * @param $ip * * @return array */ public function display_validation_message( $ip ): array { $errors = []; // Case1: single IP. if ( ! stristr( $ip, '-' ) && ! stristr( $ip, '/' ) && ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { $errors[] = sprintf( /* translators: %s: IP value. */ __( '%s – invalid format', 'wpdef' ), '' . $ip . '' ); // Case2: IP range. } elseif ( stristr( $ip, '-' ) ) { $ips = explode( '-', $ip ); foreach ( $ips as $ip_key ) { if ( ! filter_var( $ip_key, FILTER_VALIDATE_IP ) ) { $errors[] = sprintf( /* translators: %s: IP value. */ __( '%s – invalid format', 'wpdef' ), '' . $ip_key . '' ); } } if ( ! $this->compare_ip( $ips[0], $ips[1] ) ) { $errors[] = sprintf( /* translators: 1. IP value. 2. IP value. */ __( 'Can\'t compare %1$s with %2$s.', 'wpdef' ), '' . $ips[1] . '', '' . $ips[0] . '' ); } // Case3: CIDR. } elseif ( stristr( $ip, '/' ) ) { [$ip, $bits] = explode( '/', $ip ); if ( filter_var( $ip, FILTER_VALIDATE_IP ) && filter_var( $bits, FILTER_VALIDATE_INT ) ) { if ( $this->is_v4( $ip ) && 0 <= $bits && $bits <= 32 ) { // IPv4 is correct. } elseif ( $this->is_v6( $ip ) && 0 <= $bits && $bits <= 128 && $this->is_v6_support() ) { // IPv6 is correct. } else { $errors[] = sprintf( /* translators: %s: IP value. */ __( '%s – address out of range', 'wpdef' ), '' . $ip . '' ); } } else { $errors[] = sprintf( /* translators: %s: IP value. */ __( '%s – invalid format', 'wpdef' ), '' . $ip . '' ); } } // @since 2.6.3 return (array) apply_filters( 'wd_display_ip_validations', $errors ); } /** * Validate IP. * @param $ip * * @return bool */ public function check_validate_ip( $ip ): bool { // Validate the localhost IP address. if ( in_array( $ip, [ '127.0.0.1', '::1' ], true ) ) { return true; } $filter_flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; // @since 2.4.7 if ( apply_filters( 'wp_defender_filtered_internal_ip', false ) ) { // Todo: improve display of IP log when filtering reserved or private IPv4/IPv6 ranges. $filter_flags = $filter_flags | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; } if ( false === filter_var( $ip, FILTER_VALIDATE_IP, $filter_flags ) ) { return false; } return true; } /** * Get user IP. * * @return array */ public function get_user_ip(): array { /** * @var \WP_Defender\Component\Http\Remote_Address */ $remote_addr = wd_di()->get( \WP_Defender\Component\Http\Remote_Address::class ); $ips = (array) $remote_addr->get_ip_address(); return $this->filter_user_ips( $ips ); } /** * @param string $ip * @param array $arr_ips * * @return bool */ public function is_ip_in_format( $ip, $arr_ips ): bool { foreach ( $arr_ips as $wip ) { if ( false === strpos( $wip, '-' ) && false === strpos( $wip, '/' ) && trim( $wip ) === $ip ) { return true; } elseif ( false !== strpos( $wip, '-' ) ) { $ips = explode( '-', $wip ); if ( $this->compare_in_range( $ip, $ips[0], $ips[1] ) ) { return true; } } elseif ( false !== strpos( $wip, '/' ) && $this->compare_cidr( $ip, $wip ) ) { return true; } } return false; } /** * Filter user IPs. * * This function takes an array of user IPs, applies the 'defender_user_ip' * filter to each IP, and returns a unique array of filtered values. * * @param array $ips An array of user IPs. * * @since 4.4.2 * @return array An array of unique, filtered user IPs. */ public function filter_user_ips( array $ips ): array { $ips_filtered = []; foreach ( $ips as $ip ) { /** * Filters the user IP. * * @param string $ip The user IP. */ $ips_filtered[] = apply_filters( 'defender_user_ip', $ip ); } return array_unique( $ips_filtered ); } /** * Use $_SERVER['REMOTE_ADDR'] as the first protection layer to avoid spoofed headers. * * @param string $blocked_ip * * @return string */ public function check_ip_by_remote_addr( $blocked_ip ): string { return isset( $_SERVER['REMOTE_ADDR'] ) && ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : $blocked_ip; } }