Server IP : 104.21.14.48 / Your IP : 3.145.154.250 [ 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/www/wp-content/plugins/defender-security/src/controller/ |
Upload File : |
<?php /** * Handles firewall logs and interactions with Block list API service. * * @package WP_Defender\Controller */ namespace WP_Defender\Controller; use DateTime; use Exception; use Countable; use Valitron\Validator; use Calotes\Helper\HTTP; use WP_Defender\Controller; use Calotes\Component\Request; use Calotes\Component\Response; use WP_Defender\Traits\Formats; use WP_Defender\Behavior\WPMUDEV; use WP_Defender\Model\Lockout_Log; use WP_Defender\Component\User_Agent; use WP_Defender\Component\IP\Global_IP; use WP_Defender\Component\Table_Lockout; use WP_Defender\Integrations\Blocklist_Client; use WP_Defender\Model\Setting\Blacklist_Lockout; use WP_Defender\Model\Setting\User_Agent_Lockout; use WP_Defender\Component\Firewall_Logs as Firewall_Logs_Component; /** * Responsible for managing firewall logs, including bulk actions, exporting logs to CSV, * toggling IP addresses and user agents, querying logs, and sending logs to the Block list API. */ class Firewall_Logs extends Controller { use Formats; /** * The slug identifier for this controller. * * @var string */ protected $slug = 'wdf-ip-lockout'; /** * The WPMUDEV instance used for interacting with WPMUDEV services. * * @var WPMUDEV */ private $wpmudev; /** * The client for interacting with the Blocklist API service. * * @var Blocklist_Client */ private $blocklist_client; /** * Constructor for the class. * * @param Blocklist_Client $blocklist_client The client for interacting with the Block list API service. */ public function __construct( Blocklist_Client $blocklist_client ) { $this->register_routes(); add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); $this->wpmudev = wd_di()->get( WPMUDEV::class ); $this->blocklist_client = $blocklist_client; /** * Send Firewall logs to Blocklist API. */ if ( ! wp_next_scheduled( 'wpdef_firewall_send_compact_logs_to_api' ) ) { wp_schedule_event( time() + 15, 'twicedaily', 'wpdef_firewall_send_compact_logs_to_api' ); } add_action( 'wpdef_firewall_send_compact_logs_to_api', array( $this, 'send_compact_logs_to_api' ) ); } /** * Bulk action handler for lockout logs. * * @param Request $request The request object containing the data. * * @return Response The response object with the result of the bulk action. * @defender_route */ public function bulk( Request $request ) { $data = $request->get_data( array( 'action' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ids' => array( 'type' => 'array', ), ) ); $ids = $data['ids']; $ips = array(); $logs = array(); if ( is_array( $ids ) || $ids instanceof Countable ? count( $ids ) : 0 ) { foreach ( $ids as $id ) { $model = Lockout_Log::find_by_id( $id ); if ( is_object( $model ) ) { $bl = wd_di()->get( Blacklist_Lockout::class ); switch ( $data['action'] ) { case 'ban': $bl->remove_from_list( $model->ip, 'allowlist' ); $bl->add_to_list( $model->ip, 'blocklist' ); $ips[ $model->ip ] = $model->ip; $logs[] = $model; break; case 'allowlist': $bl->remove_from_list( $model->ip, 'blocklist' ); $bl->add_to_list( $model->ip, 'allowlist' ); $ips[ $model->ip ] = $model->ip; $logs[] = $model; break; case 'delete': $ips[ $model->ip ] = $model->ip; $model->delete(); break; default: break; } } } } if ( count( $logs ) > 0 ) { $logs = Lockout_Log::format_logs( $logs ); } switch ( $data['action'] ) { case 'allowlist': $messages = sprintf( /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */ esc_html__( 'IP %1$s has been added to your allowlist. You can control your allowlist in %2$s.', 'defender-security' ), implode( ', ', $ips ), '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>' ); break; case 'ban': $messages = sprintf( /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */ esc_html__( 'IP %1$s has been added to your blocklist. You can control your blocklist in %2$s.', 'defender-security' ), implode( ', ', $ips ), '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>' ); break; case 'delete': $messages = sprintf( /* translators: %s: IP Address(es) */ esc_html__( 'IP %s has been deleted', 'defender-security' ), implode( ', ', $ips ) ); break; default: $messages = ''; break; } return new Response( true, array( 'message' => $messages, 'logs' => $logs, ) ); } /** * Export logs to CSV * * @return void * @defender_route * @throws Exception On failure. */ public function export_as_csv(): void { $date_from = HTTP::get( 'date_from', strtotime( '-7 days midnight' ) ); $date_to = HTTP::get( 'date_to', strtotime( 'tomorrow' ) ); // Convert date using timezone. $timezone = wp_timezone(); $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp(); $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp(); $filters = array( 'from' => $date_from, 'to' => $date_to, 'type' => HTTP::get( 'term', '' ), 'ip' => HTTP::get( 'ip', '' ), 'ban_status' => HTTP::get( 'ban_status', '' ), ); if ( 'all' === $filters['type'] ) { $filters['type'] = ''; } if ( 'all' === $filters['ban_status'] ) { $filters['ban_status'] = ''; } // User can export the number of logs that are set. $per_page = (int) defender_get_data_from_request( 'per_page', 'g' ); if ( 0 === $per_page ) { $per_page = 20; } if ( - 1 === (int) $per_page ) { $per_page = false; } $paged = (int) defender_get_data_from_request( 'paged', 'g' ); if ( 0 === $paged ) { $paged = 1; } $logs = Lockout_Log::query_logs( $filters, $paged, 'date', 'desc', $per_page ); $tl_component = new Table_Lockout(); $ua_component = wd_di()->get( User_Agent::class ); $filename = 'wdf-lockout-logs-export-' . wp_date( 'ymdHis' ) . '.csv'; header( 'Expires: 0' ); header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); header( 'Cache-Control: private', false ); header( 'Content-Type: application/octet-stream' ); header( 'Content-Disposition: attachment; filename="' . $filename . '";' ); header( 'Content-Transfer-Encoding: binary' ); extension_loaded( 'zlib' ) ? ob_start( 'ob_gzhandler' ) : ob_start(); $fp = fopen( 'php://output', 'w' ); $headers = array( esc_html__( 'Log', 'defender-security' ), esc_html__( 'Date / Time', 'defender-security' ), esc_html__( 'Type', 'defender-security' ), esc_html__( 'IP address', 'defender-security' ), esc_html__( 'IP Status', 'defender-security' ), esc_html__( 'User Agent Status', 'defender-security' ), ); fputcsv( $fp, $headers ); $flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE; foreach ( $logs as $key => $log ) { $item = array( $log->log, $this->format_date_time( wp_date( 'Y-m-d H:i:s', $log->date ) ), $tl_component->get_type( $log->type ), $log->ip, $tl_component->get_ip_status_text( $log->ip ), $ua_component->get_status_text( $log->type, $log->tried ), ); fputcsv( $fp, $item ); if ( 0 === $key % $flush_limit ) { ob_flush(); flush(); } } fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose exit(); } /** * Toggles an IP address to or from a specified list. * * @param Request $request The HTTP request object. * * @return Response The HTTP response object. * @defender_route */ public function toggle_ip_to_list( Request $request ): Response { $data = $request->get_data( array( 'ip' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'list' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ban_status' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $ip = $data['ip']; $collection = $data['list']; $model = wd_di()->get( Blacklist_Lockout::class ); if ( $model->is_ip_in_list( $ip, $collection ) ) { $model->remove_from_list( $ip, $collection ); /* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */ $message = esc_html__( 'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.', 'defender-security' ); } else { $model->add_to_list( $ip, $collection ); $global_ip_service = wd_di()->get( Global_IP::class ); if ( $global_ip_service->can_blocklist_autosync() ) { $data = array( 'block_list' => array( $ip ), ); $global_ip_service->add_to_global_ip_list( $data ); } /* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */ $message = esc_html__( 'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.', 'defender-security' ); } $filter_data = $request->get_data( array( 'date_from' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'date_to' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ip_filter' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'type' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), 'per_page' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), ) ); $logs = Lockout_Log::get_logs_and_format( array( 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ), 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ), 'ip' => $filter_data['ip_filter'], // If this is all, then we set to null to exclude it from the filter. 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'], ), $filter_data['paged'], 'id', 'desc', $filter_data['per_page'] ); return new Response( true, array( 'message' => sprintf( $message, $data['ip'], $data['list'], $data['list'], '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>' ), 'logs' => $logs, ) ); } /** * Toggles a user agent to/from a specified list based on the given request data. * * @param Request $request The request object containing the data for toggling the user agent. * * @return Response The response object indicating the success or failure of the toggle operation. * @defender_route */ public function toggle_ua_to_list( Request $request ): Response { $data = $request->get_data( array( 'ua' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'list' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'scenario' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $ua = $data['ua']; $collection = $data['list']; $action = $data['scenario']; $model = wd_di()->get( User_Agent_Lockout::class ); if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) { $model->remove_from_list( $ua, $collection ); /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */ $message = esc_html__( 'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.', 'defender-security' ); } elseif ( 'add' === $action ) { /** * Possible scenario on regex blocklist. For e.g. UA term `run` present in allowlist & `r.n` regex in blocklist then remove `run` to block `run` user agent using regex `r.n`. */ if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) { $model->remove_from_list( $ua, 'allowlist' ); } if ( ! $model->is_ua_in_list( $ua, $collection ) ) { $model->add_to_list( $ua, $collection ); } /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */ $message = esc_html__( 'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.', 'defender-security' ); } else { return new Response( false, array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) ) ); } $filter_data = $request->get_data( array( 'date_from' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'date_to' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ip_filter' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'type' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), 'per_page' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), ) ); $logs = Lockout_Log::get_logs_and_format( array( 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ), 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ), 'ip' => $filter_data['ip_filter'], // If this is all, then we set to null to exclude it from the filter. 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'], ), $filter_data['paged'], 'id', 'desc', $filter_data['per_page'] ); return new Response( true, array( 'message' => sprintf( $message, '<strong>' . $data['ua'] . '</strong>', $data['list'], $data['list'], '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>' ), 'logs' => $logs, ) ); } /** * Query the logs and display on frontend. * * @param Request $request The request object containing filter parameters. * * @return Response * @defender_route * @throws Exception If an argument is not of the expected type. */ public function query_logs( Request $request ): Response { $data = $request->get_data( array( 'date_from' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'date_to' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ip' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'type' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), 'sort' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'ban_status' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); // Validate. $v = new Validator( $data, array() ); $v->rule( 'required', array( 'date_from', 'date_to' ) ); $v->rule( 'date', array( 'date_from', 'date_to' ) ); if ( ! $v->validate() ) { return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) ); } $sort = $data['sort'] ?? Table_Lockout::SORT_DESC; switch ( $sort ) { case 'ip': $order = 'desc'; $order_by = 'ip'; break; case 'oldest': $order = 'asc'; $order_by = 'id'; break; case 'user_agent': $order = 'asc'; $order_by = 'user_agent'; break; default: $order = 'desc'; $order_by = 'id'; break; } // Convert date using timezone. $timezone = wp_timezone(); $date_from = ( new DateTime( $data['date_from'], $timezone ) ) ->setTime( 0, 0, 0 ) ->getTimestamp(); $date_to = ( new DateTime( $data['date_to'], $timezone ) ) ->setTime( 23, 59, 59 ) ->getTimestamp(); $result = $this->retrieve_logs( array( 'from' => $date_from, 'to' => $date_to, 'ip' => $data['ip'], // If this is all, then we set to null to exclude it from the filter. 'type' => 'all' === $data['type'] ? '' : $data['type'], 'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'], ), $data['paged'], $order, $order_by ); return new Response( true, $result ); } /** * Enqueues scripts and styles for this page. * Only enqueues assets if the page is active. */ public function enqueue_assets() { if ( ! $this->is_page_active() ) { return; } wp_enqueue_script( 'def-momentjs', defender_asset_url( '/assets/js/vendor/moment/moment.min.js' ), array(), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-daterangepicker', defender_asset_url( '/assets/js/vendor/daterangepicker/daterangepicker.js' ), array(), DEFENDER_VERSION, true ); wp_localize_script( 'def-iplockout', 'lockout_logs', array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() ) ); } /** * Provides data for the frontend. * * @return array An array of data for the frontend. */ public function data_frontend(): array { $def_filters = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() ); $init_filters = array( 'from' => strtotime( '-30 days' ), 'to' => time(), 'type' => '', 'ip' => '', 'ban_status' => '', ); return array_merge( $this->retrieve_logs( $init_filters, 1 ), $def_filters ); } /** * Retrieves logs based on the given filters, paging, order, and order by. * * @param array $filters An array containing the following keys: * - 'from': The start date of the logs. * - 'to': The end date of the logs. * - 'type': The type of logs. * - 'ip': The IP address of the logs. * - 'ban_status': The ban status of the logs. * @param int $paged The page number of the logs to retrieve. Default is 1. * @param string $order The order of the logs. Default is 'desc'. * @param string $order_by The field to order the logs by. Default is 'id'. * * @return array An array containing the following keys: * - 'count': The total count of logs. * - 'logs': The retrieved logs. * - 'per_page': The number of logs per page. * - 'total_pages': The total number of pages. */ private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array { // User can set the number of logs to retrieve per page. $per_page = (int) defender_get_data_from_request( 'per_page', 'p' ); if ( 0 === $per_page ) { $per_page = 20; } $conditions = array( 'ban_status' => $filters['ban_status'] ); $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions ); $logs = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page ); if ( - 1 === (int) $per_page ) { $per_page = Lockout_Log::INFINITE_SCROLL_SIZE; } return array( 'count' => $count, 'logs' => $logs, 'per_page' => $per_page, 'total_pages' => ceil( $count / $per_page ), ); } /** * Converts the current object state to an array. * * @return array The array representation of the object. */ public function to_array(): array { return array(); } /** * Imports data into the model. * * @param array $data Data to be imported into the model. */ public function import_data( array $data ) { } /** * 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(): array { return array(); } /** * Send last 12 hours logs to Blocklist API. * If running for first time then grab 7 days of logs. * If last run difference is greater than 12 hours then grab 12+ hours of log but at most grab 7 days of logs. * * @return void */ public function send_compact_logs_to_api(): void { /** * Enable/disable sending Firewall logs to API. * * @param bool $status Status for sending logs. Send logs to API if true. * * @since 4.5.0 */ $send_logs = (bool) apply_filters( 'wpdef_firewall_send_logs_to_api', true ); if ( ! $send_logs || ! $this->wpmudev->is_dash_activated() || ! $this->wpmudev->is_site_connected_to_hub() ) { return; } $from = time() - ( 7 * DAY_IN_SECONDS ); $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' ); if ( $last_run_time ) { $time_difference = time() - $last_run_time; if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds $from = $last_run_time; } } update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() ); $service = wd_di()->get( Firewall_Logs_Component::class ); $logs = $service->get_compact_logs( $from ); if ( empty( $logs ) ) { return; } $offset = 0; $length = 1000; while ( $logs_chunk = array_slice( $logs, $offset, $length ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition $data = array( 'logs' => $logs_chunk, ); $response = $this->blocklist_client->send_reports( $data ); if ( is_wp_error( $response ) ) { $this->log( sprintf( 'IP Blocklist API Error: %s', $response->get_error_message() ), Firewall::FIREWALL_LOG ); } elseif ( isset( $response['status'] ) && 'error' === $response['status'] ) { $this->log( sprintf( 'IP Blocklist API Error: %s', $response['message'] ), Firewall::FIREWALL_LOG ); } $offset += $length; } $this->log( 'IP Blocklist API: Process for sending logs completed.', Firewall::FIREWALL_LOG ); } }