Server IP : 172.67.157.199 / Your IP : 18.219.212.91 [ 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/controller/ |
Upload File : |
<?php /** * Handles IP and country blacklisting functionalities. * * @package WP_Defender\Controller */ namespace WP_Defender\Controller; use PharData; use Exception; use Countable; use WP_Defender\Traits\IP; use WP_Defender\Controller; use Calotes\Component\Request; use Calotes\Component\Response; use WP_Defender\Traits\Country; use WP_Defender\Behavior\WPMUDEV; use WP_Defender\Model\Lockout_Ip; use WP_Defender\Traits\Continent; use WP_Defender\Component\Blacklist_Lockout; use MaxMind\Db\Reader\InvalidDatabaseException; use WP_Defender\Integrations\MaxMind_Geolocation; use WP_Defender\Component\Config\Config_Hub_Helper; use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout; /** * Handles IP and country blacklisting functionalities. */ class Blacklist extends Controller { use IP; use Country; use Continent; /** * The slug identifier for this controller. * * @var string */ protected $slug = 'wdf-ip-lockout'; /** * The model for handling the data. * * @var Model_Blacklist_Lockout */ protected $model; /** * Service for handling logic. * * @var Blacklist_Lockout */ protected $service; /** * Initializes the model and service, registers routes, and sets up scheduled events if the model is active. */ public function __construct() { $this->register_routes(); add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); $this->model = wd_di()->get( Model_Blacklist_Lockout::class ); $this->service = wd_di()->get( Blacklist_Lockout::class ); add_action( 'wd_blacklist_this_ip', array( &$this, 'blacklist_an_ip' ) ); // Update MaxMind's DB. if ( ! empty( $this->model->maxmind_license_key ) ) { if ( ! wp_next_scheduled( 'wpdef_update_geoip' ) ) { wp_schedule_event( strtotime( 'next Thursday' ), 'weekly', 'wpdef_update_geoip' ); } // @since 2.8.0 Allows update or remove the database of MaxMind automatic and periodically (MaxMind's TOS). $bind_updater = (bool) apply_filters( 'wd_update_maxmind_database', true ); // Bind to the scheduled updater action. if ( $bind_updater ) { add_action( 'wpdef_update_geoip', array( &$this, 'update_database' ) ); } } } /** * Add an IP into blacklist. * * @param string $ip IP address to be blacklisted. * * @return void */ public function blacklist_an_ip( string $ip ): void { $this->model->add_to_list( $ip, 'blocklist' ); } /** * Enqueues scripts and styles for this page. * Only enqueues assets if the page is active. * * @throws InvalidDatabaseException|Exception When unexpected data is found in the database. */ public function enqueue_assets() { if ( ! $this->is_page_active() ) { return; } wp_localize_script( 'def-iplockout', 'blacklist', $this->data_frontend() ); } /** * Provides data for the frontend. * * @return array An array of data for the frontend. * @throws InvalidDatabaseException|Exception When unexpected data is found in the database. */ public function data_frontend(): array { $user_ip = $this->get_user_ip(); $arr_model = $this->model->export(); $exist_geodb = $this->service->is_geodb_downloaded(); // If MaxMind GeoIP DB is downloaded then display the required data. if ( $exist_geodb ) { $country_list = $this->countries_list(); $blacklist_countries = array_merge( array( 'all' => esc_html__( 'Block all', 'defender-security' ) ), $country_list ); $whitelist_countries = array_merge( array( 'all' => esc_html__( 'Allow all', 'defender-security' ) ), $country_list ); $countries_with_continents_list = $this->get_countries_with_continents(); } else { $blacklist_countries = array(); $whitelist_countries = array(); $countries_with_continents_list = array(); } $current_country = array(); foreach ( $user_ip as $ip ) { $current_country[] = $this->get_current_country( $ip ); } return array_merge( array( 'model' => $arr_model, 'misc' => array( 'user_ip' => implode( ',', $user_ip ), 'is_geodb_downloaded' => $exist_geodb, 'blacklist_countries' => $blacklist_countries, 'whitelist_countries' => $whitelist_countries, 'current_country' => $current_country, 'no_ips' => '' === $arr_model['ip_blacklist'] && '' === $arr_model['ip_whitelist'], 'countries_with_continents_list' => $countries_with_continents_list, 'geodb_license_key' => $this->mask_license_key( $this->model->maxmind_license_key ), ), ), $this->dump_routes_and_nonces() ); } /** * Masks a license key with asterisks. * * @param mixed $maxmind_license_key The license key to be masked. * * @return string The masked license key. */ private function mask_license_key( $maxmind_license_key ): string { if ( ! is_string( $maxmind_license_key ) || empty( $maxmind_license_key ) ) { return $maxmind_license_key; } // Get the length of the license key. $key_length = strlen( $maxmind_license_key ); // Decide how many characters to reveal. Revealing at least 4 characters or 25% of the key, whichever is greater. $reveal_chars = max( 4, intval( $key_length / 5 ) ); // Calculate the number of asterisks to replace the hidden characters. $num_asterisks = $key_length - $reveal_chars; // Generate masked key. return substr( $maxmind_license_key, 0, $reveal_chars ) . str_repeat( '*', $num_asterisks ); } /** * Save settings. * * @param Request $request The request object containing new settings data. * * @return Response * @defender_route */ public function save_settings( Request $request ) { $data = $request->get_data( array( 'country_blacklist' => array( 'type' => 'array', ), 'country_whitelist' => array( 'type' => 'array', ), 'ip_blacklist' => array( 'type' => 'string', 'sanitize' => 'sanitize_textarea_field', ), 'ip_whitelist' => array( 'type' => 'string', 'sanitize' => 'sanitize_textarea_field', ), 'ip_lockout_message' => array( 'type' => 'string', 'sanitize' => 'sanitize_textarea_field', ), 'http_ip_header' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'trusted_proxies_ip' => array( 'type' => 'string', 'sanitize' => 'sanitize_textarea_field', ), ) ); $this->model->import( $data ); if ( $this->model->validate() ) { $this->model->save(); Config_Hub_Helper::set_clear_active_flag(); return new Response( true, array_merge( array( 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ), 'auto_close' => true, ), $this->data_frontend() ) ); } return new Response( false, array_merge( array( 'message' => $this->model->get_formatted_errors() ), $this->data_frontend() ) ); } /** * Download the GEODB IP from Maxmind. * * @param Request $request The request object containing the license key. * * @return Response * @defender_route * @throws InvalidDatabaseException|Exception When unexpected data is found in the database. */ public function download_geodb( Request $request ) { $data = $request->get_data( array( 'license_key' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $license_key = $data['license_key']; $service_geo = wd_di()->get( MaxMind_Geolocation::class ); $tmp = $service_geo->get_downloaded_url( $license_key ); if ( ! is_wp_error( $tmp ) ) { $phar = new PharData( $tmp ); $path = $service_geo->get_db_base_path(); if ( ! is_dir( $path ) ) { wp_mkdir_p( $path ); } $phar->extractTo( $path, null, true ); // Todo: move logic for the path to MaxMind_Geolocation class. $this->model->geodb_path = $path . DIRECTORY_SEPARATOR . $phar->current()->getFileName() . DIRECTORY_SEPARATOR . $service_geo->get_db_full_name(); // Save because we'll check for a saved path. $this->model->save(); if ( file_exists( $tmp ) ) { wp_delete_file( $tmp ); } foreach ( $this->get_user_ip() as $ip ) { $country = $this->get_current_country( $ip ); if ( ! empty( $country ) && ! empty( $country['iso'] ) ) { $this->model = $this->service->add_default_whitelisted_country( $this->model, $country['iso'] ); } } $this->model->maxmind_license_key = $license_key; $this->model->save(); return new Response( true, array( 'message' => esc_html__( 'You have successfully downloaded Geo IP Database. You can now use this feature to ban any countries to access any area of your website.', 'defender-security' ), 'is_geodb_downloaded' => $this->service->is_geodb_downloaded(), ) ); } else { $this->log( 'Error from MaxMind: ' . $tmp->get_error_message() ); $string = sprintf( /* translators: 1. License key with link. */ esc_html__( 'You have entered an invalid %1$s. If you just created the key, please wait 5 minutes before trying to activate it.', 'defender-security' ), '<a target="_blank" href="https://www.maxmind.com/en/accounts/current/license-key">' . esc_html__( 'license key', 'defender-security' ) . '</a>' ); if ( ( new WPMUDEV() )->show_support_links() ) { $string .= defender_support_ticket_text(); } return new Response( false, array( 'invalid_text' => $string ) ); } } /** * Delete the Maxmind License key from the settings. * * @return Response * @defender_route * @throws InvalidDatabaseException When unexpected data is found in the database. */ public function delete_geodb(): Response { $this->model->maxmind_license_key = ''; $this->model->geodb_path = ''; $this->model->save(); return new Response( true, array( 'message' => esc_html__( 'Maxmind GeoLite2 database license successfully disconnected.', 'defender-security' ), 'is_geodb_deleted' => $this->service->is_geodb_downloaded(), ) ); } /** * Export IPs * This method exports the IP addresses from the blocklist and allowlist * and generates a CSV file for download. * * @defender_route */ public function export_ips(): void { $data = array(); foreach ( $this->model->get_list( 'blocklist' ) as $ip ) { $data[] = array( 'ip' => $ip, 'type' => 'blocklist', ); } foreach ( $this->model->get_list( 'allowlist' ) as $ip ) { $data[] = array( 'ip' => $ip, 'type' => 'allowlist', ); } // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode. $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen foreach ( $data as $fields ) { fputcsv( $fp, $fields ); } $filename = 'wdf-ips-export-' . wp_date( 'ymdHis' ) . '.csv'; fseek( $fp, 0 ); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="' . $filename . '";' ); // Make php send the generated csv lines to the browser. fpassthru( $fp ); exit(); } /** * Perform IP blocking or unblocking actions. * * @param Request $request Request object. * * @return void * @throws Exception If table is not defined. * @defender_route */ public function ip_action( Request $request ): void { $data = $request->get_data( array( 'ip' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'behavior' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $ip = $data['ip']; $action = $data['behavior']; $models = Lockout_Ip::get( $ip, $action, true ); foreach ( $models as $model ) { if ( 'unban' === $action ) { $model->status = Lockout_Ip::STATUS_NORMAL; $model->save(); } elseif ( 'ban' === $action ) { $model->status = Lockout_Ip::STATUS_BLOCKED; $model->save(); } } $this->query_locked_ips( $request ); } /** * Bulk ban or unban IPs. * * @param Request $request Request object. * * @return Response * @throws Exception If table is not defined. * @defender_route */ public function bulk_ip_action( Request $request ) { $data = $request->get_data( array( 'behavior' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ips' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $status = 'unban' === $data['behavior'] ? Lockout_Ip::STATUS_BLOCKED : Lockout_Ip::STATUS_NORMAL; $ips = null; $bulk_ips = null; $limit = 50; if ( ! empty( $data['ips'] ) ) { $ips = json_decode( $data['ips'] ); $first_nth_ips = array_slice( $ips, 0, $limit ); $bulk_ips = wp_list_pluck( $first_nth_ips, 'ip' ); } try { $models = Lockout_Ip::get_bulk( $status, $bulk_ips, $limit ); foreach ( $models as $model ) { $model->status = ( 'unban' === $data['behavior'] ) ? Lockout_Ip::STATUS_NORMAL : Lockout_Ip::STATUS_BLOCKED; $model->save(); } // While bulk banning the IPs, needs to slice the IPs array for next iteration. if ( 'ban' === $data['behavior'] ) { $ips = array_slice( $ips, $limit ); } // If the queried models are less than the limit it means we are on the last set of IPs. if ( ( is_array( $models ) || $models instanceof Countable ? count( $models ) : 0 ) < $limit ) { return new Response( true, array( 'status' => 'done', ) ); } } catch ( Exception $e ) { return new Response( true, array( 'status' => 'error', ) ); } return new Response( true, array( 'status' => 'continue', 'ips' => $ips, ) ); } /** * Query locked IPs and return the results as a Response object. * * @param Request $request The request object. * * @return Response * @defender_route */ public function query_locked_ips( Request $request ) { $results = Lockout_Ip::query_locked_ip(); $locked_ips = array(); if ( ! empty( $results ) ) { foreach ( $results as $key => $locked_ip ) { $locked_ips[] = array( 'id' => $locked_ip['id'], 'ip' => $locked_ip['ip'], 'status' => $locked_ip['status'], ); } } return new Response( true, array( 'ips' => $locked_ips, ) ); } /** * Get Listed IPs. * * @param Request $request Request object. * * @return Response * @defender_route * @throws Exception If table is not defined. */ public function get_listed_ips( Request $request ): Response { return new Response( true, $this->model->export() ); } /** * Converts the current state of the object to an array. * * @return array Returns an associative array of object properties. */ public function to_array(): array { return array(); } /** * Adapt the given data array by adding additional fields if necessary. * * @param array $data The data array to adapt. * * @return array The adapted data array. */ private function adapt_data( array $data ): array { $adapted_data = array( 'ip_blacklist' => $data['ip_blacklist'], 'ip_whitelist' => $data['ip_whitelist'], 'ip_lockout_message' => $data['ip_lockout_message'], ); if ( isset( $data['geoIP_db'] ) && file_exists( $data['geoIP_db'] ) ) { $adapted_data['geodb_path'] = $data['geoIP_db']; if ( isset( $data['country_blacklist'] ) ) { $adapted_data['country_blacklist'] = $data['country_blacklist']; } if ( isset( $data['country_whitelist'] ) ) { $adapted_data['country_whitelist'] = $data['country_whitelist']; } } return array_merge( $data, $adapted_data ); } /** * Imports data into the model. * * @param array $data Data to be imported into the model. * * @throws Exception If table is not defined. */ public function import_data( array $data ) { if ( ! empty( $data ) ) { // Upgrade for old versions. $data = $this->adapt_data( $data ); $model = $this->model; $model->import( $data ); if ( $model->validate() ) { $model->save(); } } } /** * Removes settings for all submodules. */ public function remove_settings() { } /** * Delete all the data & the cache. */ public function remove_data() { } /** * Exports strings. * * @return array An array of strings. */ public function export_strings() { return array(); } /** * Importing IPs from exporter. * * @param Request $request The request object containing the data. * * @defender_route * @return Response */ public function import_ips( Request $request ) { $data = $request->get_data( array( 'id' => array( 'type' => 'int', ), ) ); $attached_id = $data['id']; if ( ! is_object( get_post( $attached_id ) ) ) { return new Response( false, array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ), ) ); } $file = get_attached_file( $attached_id ); if ( ! is_file( $file ) ) { return new Response( false, array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ), ) ); } $data = $this->service->verify_import_file( $file ); if ( ! $data ) { return new Response( false, array( 'message' => esc_html__( 'Your file content is invalid!', 'defender-security' ), ) ); } // All good, start to import. foreach ( $data as $line ) { $this->model->add_to_list( $line[0], $line[1] ); } return new Response( true, array( 'message' => esc_html__( 'Your allowlist/blocklist has been successfully imported.', 'defender-security' ), 'interval' => 1, ) ); } /** * Update the geolocation database. * * @return void * @throws Exception If table is not defined. * @since 2.8.0 */ public function update_database() { if ( empty( $this->model->maxmind_license_key ) ) { return; } $service_geo = wd_di()->get( MaxMind_Geolocation::class ); $service_geo->delete_database(); $tmp = $service_geo->get_downloaded_url( $this->model->maxmind_license_key ); if ( is_wp_error( $tmp ) ) { $this->log( 'CRON error downloading from MaxMind: ' . $tmp->get_error_message() ); return; } $geodb_path = $service_geo->extract_db_archive( $tmp ); if ( is_wp_error( $geodb_path ) ) { $this->log( 'CRON error extracting MaxMind archive: ' . $geodb_path->get_error_message() ); return; } $this->model->geodb_path = $geodb_path; $this->model->save(); } }