::' * @return string */ public static function get_firewall_rule_location($rule) { //normalise key $rule = strtolower($rule); $basic_firewall = array( 'completely block xmlrpc::general' => 'page=aiowpsec_firewall', ); $additional_firewall = array( 'advanced character filter::general' => 'page=aiowpsec_firewall&tab=additional-firewall', 'bad query strings::general' => 'page=aiowpsec_firewall&tab=additional-firewall', 'proxy comment posting::general' => 'page=aiowpsec_firewall&tab=additional-firewall', ); $bruteforce = array( 'cookie based prevent bruteforce::bruteforce' => 'page=aiowpsec_brute_force&tab=cookie-based-brute-force-prevention', ); $blacklist = array( 'blocked ips::blacklist' => 'page=aiowpsec_blacklist', 'blocked user agents::blacklist' => 'page=aiowpsec_blacklist', ); $firewall_6g = array( 'block request methods::6g' => 'page=aiowpsec_firewall&tab=6g-firewall', 'block query strings::6g' => 'page=aiowpsec_firewall&tab=6g-firewall', 'block referrer strings::6g' => 'page=aiowpsec_firewall&tab=6g-firewall', 'block request strings::6g' => 'page=aiowpsec_firewall&tab=6g-firewall', 'block user-agents::6g' => 'page=aiowpsec_firewall&tab=6g-firewall', ); // merge all the locations to one $locations = array_merge($firewall_6g, $blacklist, $bruteforce, $basic_firewall, $additional_firewall); return isset($locations[$rule]) ? $locations[$rule] : ''; } /** * Get server detected visitor IP Address. * * @return String visitor IP Address. */ public static function get_server_detected_user_ip_address() { $aiowps_firewall_config = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONFIG); // check if user configured custom IP retrieval method $ip_method_id = empty($aiowps_firewall_config) ? '' : $aiowps_firewall_config->get_value('aios_ip_retrieve_method'); $visitor_ip = ''; $ip_retrieve_methods = AIOS_Abstracted_Ids::get_ip_retrieve_methods(); if (empty($ip_method_id) || !isset($ip_retrieve_methods[$ip_method_id])) { $ip_method_id = 0; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress API cannot be used here as it's loaded independently of WordPress $visitor_ip = isset($_SERVER[$ip_retrieve_methods[$ip_method_id]]) ? $_SERVER[$ip_retrieve_methods[$ip_method_id]] : ''; // Check if multiple IPs were given - these will be present as comma-separated list if (preg_match('/^([^,]+),/', $visitor_ip, $matches)) $visitor_ip = $matches[1]; // Now remove port portion if ipv4 address with port, for ipv6 it was making issue so using fiter_var valid ip checked first. if (!filter_var($visitor_ip, FILTER_VALIDATE_IP) && preg_match('/(.+):\d+$/', $visitor_ip, $matches)) $visitor_ip = $matches[1]; if (!filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && !filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress API cannot be used here as it's loaded independently of WordPress $visitor_ip = empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']; } return $visitor_ip; } /** * Get user IP Address. * * @return string User IP Address. */ public static function get_user_ip_address() { static $visitor_ip; if (isset($visitor_ip)) { //already set in the page request return $visitor_ip; } $visitor_ip = self::get_server_detected_user_ip_address(); if (in_array($visitor_ip, array('', '127.0.0.1', '::1'))) { $external_ip_address = self::get_external_ip_address(); if (false != $external_ip_address) { $visitor_ip = $external_ip_address; } } return $visitor_ip; } /** * Check user IP Address is with in list. * * @param array $ip_address_list * * @return boolean. */ public static function is_user_ip_address_within_list($ip_address_list) { if (!(include_once AIOWPS_FIREWALL_DIR.'/../../vendor/mlocati/ip-lib/ip-lib.php')) { throw new \Exception("AIOS_Helper::is_user_ip_address_within_list failed to load ip-lib.php"); } if (empty($ip_address_list)) return false; $user_ip = self::get_user_ip_address(); $user_ip_address = \IPLib\Factory::parseAddressString($user_ip); if (null != $user_ip_address) { $user_ip_address_type = $user_ip_address->getAddressType(); $user_ipv4_address_parsed = (6 == $user_ip_address_type) ? $user_ip_address->toIPv4() : $user_ip_address; // linear search used to compare ips list. foreach ($ip_address_list as $ip_address) { if (preg_match("/\/|\*/", $ip_address)) { // checks user ipadress with in range of ip range. $range = \IPLib\Factory::parseRangeString($ip_address); if (null != $range && $range->contains($user_ip_address)) return true; } elseif ($ip_address == $user_ip) { return true; } elseif (null != $user_ipv4_address_parsed) { // check for ip matches if ipv6 to ipv4 or vice versa $ip_address_parsed = \IPLib\Factory::parseAddressString($ip_address); if (null != $ip_address_parsed) { $ip_address_parsed_type = $ip_address_parsed->getAddressType(); $ipv4_address_parsed = (6 == $ip_address_parsed_type) ? $ip_address_parsed->toIPv4() : $ip_address_parsed; if ((string) $user_ipv4_address_parsed == (string) $ipv4_address_parsed) return true; } } } } return false; } /** * Get user IP Address using an external service. * This can be used as a fallback for users on localhost where * get_ip_address() will be a local IP and non-geolocatable. * * @return string|boolean external ip address if from one of lookup service gets response otherwise false. */ public static function get_external_ip_address() { $aiowps_firewall_constants = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONSTANTS); $external_ip_address = false; //Running from cronjob REQUEST_URI is empty or DOING_CRON, if from command line cli is PHP_SAPI, constant set by user if (empty($_SERVER['REQUEST_URI']) || (defined('DOING_CRON') && DOING_CRON) || 'cli' == PHP_SAPI || $aiowps_firewall_constants->AIOS_DISABLE_EXTERNAL_IP_ADDR) return $external_ip_address; $ip_lookup_services = AIOS_Abstracted_Ids::get_ip_lookup_services(); $ip_lookup_services_keys = array_keys($ip_lookup_services); shuffle($ip_lookup_services_keys); foreach ($ip_lookup_services_keys as $service_name) { $service_endpoint = $ip_lookup_services[$service_name]; $response = self::request_remote($service_endpoint); if (!empty($response) && filter_var($response, FILTER_VALIDATE_IP)) { $external_ip_address = $response; break; } } return $external_ip_address; } /** * Remote url request. * * @global \AIOWPS\Firewall\Constants $aiowps_firewall_constants * * @param string $url url to be requested. * @param array $args request args array. * * @return string response. */ public static function request_remote($url, $args = array()) { $aiowps_firewall_constants = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONSTANTS); $response = ''; $aiowps_firewall_utility = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::UTILITY); $root = $aiowps_firewall_utility::get_root_dir(); $includes = isset($aiowps_firewall_constants->WPINC) ? $aiowps_firewall_constants->WPINC : 'wp-includes'; //WP 6.2+ the request library updated to 2.0.5, class and interface files/directory moved and namespaced name used. if (!class_exists('WpOrg\Requests\Autoload')) { if (file_exists($root . $includes . '/Requests/src/Autoload.php')) { require_once($root . $includes . '/Requests/src/Autoload.php'); WpOrg\Requests\Autoload::register(); WpOrg\Requests\Requests::set_certificate_path($root . $includes . '/certificates/ca-bundle.crt'); } elseif (!class_exists('Requests') && file_exists($root . $includes . '/class-requests.php')) { require_once($root . $includes . '/class-requests.php'); Requests::register_autoloader(); Requests::set_certificate_path($root . $includes . '/certificates/ca-bundle.crt'); } } $timeout = isset($aiowps_firewall_constants->AIOS_REQUEST_TIMEOUT) ? $aiowps_firewall_constants->AIOS_REQUEST_TIMEOUT : 4; try { $headers = isset($args['headers']) ? $args['headers'] : array(); $data = isset($args['body']) ? $args['body'] : array(); $method = isset($args['method']) ? $args['method'] : 'GET'; $options = array('timeout' => $timeout); //WP 6.2+ the request library 2.0.5 namespaced name WpOrg\Requests\Requests instead just Requests if (class_exists('WpOrg\Requests\Requests')) { $request_response = WpOrg\Requests\Requests::request($url, $headers, $data, $method, $options); } elseif (class_exists('Requests')) { $request_response = Requests::request($url, $headers, $data, $method, $options); } $response = $request_response->body; } catch (\Exception $e) { $error_message = $e->getMessage(); // timed out exception ignore it. // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Part of AIOS error reporting. if (!preg_match('/timed out/i', $error_message)) error_log('AIOS_Helper::request_remote exception - ' . $error_message); } catch (\Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound -- this won't run on PHP 5.6 so we still want to catch it on other versions error_log('AIOS_Helper::request_remote error - ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Part of AIOS error reporting. } if (empty($response) && ini_get('allow_url_fopen')) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Cannot use WP API since Firewall is loaded outside WordPress. $response = @file_get_contents($url, false, stream_context_create(array('http' => array("timeout" => $timeout)))); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this to silence request failed warning for IP lookup services } return $response; } /** * Performs reverse IP lookup for the given IP address * * @param string $ip_address - IP address to perform reverse lookup * * @return array - Reverse lookup data */ public static function get_ip_reverse_lookup($ip_address) { global $aio_wp_security; $reverse_lookup_data = array(); $ip_reverse_lookup_services = AIOS_Abstracted_Ids::get_reverse_ip_lookup_services(); $ip_reverse_lookup_services_keys = array_keys($ip_reverse_lookup_services); shuffle($ip_reverse_lookup_services_keys); foreach ($ip_reverse_lookup_services_keys as $service_name) { $endpoint = $ip_reverse_lookup_services[$service_name]; $url = sprintf($endpoint, $ip_address); $response = wp_safe_remote_get($url, array( 'timeout' => 2 )); if (!is_wp_error($response) && $response['body']) { $data = json_decode($response['body'], true); if (!$data) { $aio_wp_security->debug_logger->log_debug("Error decoding IP lookup result", 4); return $reverse_lookup_data; } switch ($service_name) { case 'ip-api': $fields_to_copy = array('org', 'as'); foreach ($fields_to_copy as $field) { $reverse_lookup_data[$field] = empty($data[$field]) ? null : $data[$field]; } break; case 'ipinfo': $reverse_lookup_data['org'] = empty($data['org']) ? null : $data['org']; $reverse_lookup_data['as'] = $reverse_lookup_data['org']; break; default: break; } $reverse_lookup_data = apply_filters('aiowps_login_lockdown_lookup_result', $reverse_lookup_data, $data, $service_name); break; } } return $reverse_lookup_data; } /** * Gets hash of given string using auth Authentication scheme * * @param string $data - Plain text to hash. * * @return string - Hash of $data */ public static function get_hash($data) { $aiowps_firewall_constants = AIOS_Firewall_Resource::request(AIOS_Firewall_Resource::CONSTANTS); $salt = $aiowps_firewall_constants->AUTH_KEY.$aiowps_firewall_constants->AUTH_SALT; return hash_hmac('md5', $data, $salt); } /** * Set a message for a specific context. * * @param string $context The unique context identifier for the message. * @param string $message The message to store for the given context. * @param string $type The message type to store for the given context. * * @return void */ public static function set_message($context, $message, $type = 'info') { self::$messages[$context] = array('message' => $message, 'type' => $type); } /** * Get a message for a specific context. * * @param string $context The unique context identifier for the message. * * @return array|null The message for the given context, or null if not found. */ public static function get_message($context) { return isset(self::$messages[$context]) ? self::$messages[$context] : null; } /** * Clear messages (optional, for cleanup purposes). * * @return void */ public static function clear_messages() { self::$messages = array(); } /** * This function checks if the current request is an UpdraftCentral request by looking for a specific constant. * * @return boolean - True if the request is from UpdraftCentral, false otherwise. */ public static function is_updraft_central_request() { return defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND; } }