plugin = $plugin_instance; } /** * Verifies a JS challenge response if submitted. * Executes before block rules to process * a challenge response and set the verification cookie. */ public function verify_submission() { // phpcs:ignore WordPress.Security.NonceVerification.Missing if (!isset($_POST['_advaipbl_js_token'])) { return; } if (!empty($this->plugin->request_is_asn_whitelisted) || $this->plugin->is_whitelisted($this->plugin->get_client_ip()) || !empty($this->plugin->is_advanced_rule_allowed)) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing $challenge_type = sanitize_key($_POST['_advaipbl_challenge_type'] ?? 'signature'); $global_duration_hours = (int)($this->plugin->options['global_challenge_cookie_duration'] ?? 4); $cookie_duration = ($global_duration_hours > 0) ? $global_duration_hours * HOUR_IN_SECONDS : 0; if ($challenge_type === 'geo_challenge') { $duration_hours = (int)($this->plugin->options['geo_challenge_cookie_duration'] ?? 24); $cookie_duration = ($duration_hours > 0) ? $duration_hours * HOUR_IN_SECONDS : 0; } $this->verify_challenge('advaipbl_js_verified', $cookie_duration); } /** * Validates a cryptographic VIP Pass (HMAC Cookie) * @param string $ip * @return bool */ public function is_vip_pass_valid($ip = null) { if (!$ip) { $ip = $this->plugin->get_client_ip(); } $cookie_name = 'advaipbl_js_verified'; if (!isset($_COOKIE[$cookie_name])) { return false; } $cookie_value = sanitize_text_field(wp_unslash($_COOKIE[$cookie_name])); if ($cookie_value === '1') { return false; } $decoded = base64_decode($cookie_value); if ($decoded === false || strpos($decoded, '|') === false) { return false; } list($expiration, $signature) = explode('|', $decoded, 2); if ((int)$expiration !== 0 && time() > (int)$expiration) { return false; } $salt = function_exists('wp_salt') ? wp_salt('auth') : (defined('AUTH_SALT') ? AUTH_SALT : 'fallback_salt_advaipbl'); $modifier = get_option('advaipbl_vip_salt_modifier', ''); $expected_signature = hash_hmac('sha256', $ip . $expiration, $salt . $modifier); return hash_equals($expected_signature, $signature); } /** * Verifies the challenge token and sets the cookie if correct. * * @param string $cookie_name Name of the cookie to set. * @param int $cookie_duration Cookie duration in seconds. */ public function verify_challenge($cookie_name, $cookie_duration) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $token = sanitize_text_field(wp_unslash($_POST['_advaipbl_js_token'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Missing $response = (int) sanitize_text_field(wp_unslash($_POST['_advaipbl_js_response'] ?? 0)); $ip = $this->plugin->get_client_ip(); // phpcs:ignore WordPress.Security.NonceVerification.Missing $mode_reported = sanitize_key($_POST['_advaipbl_challenge_mode'] ?? 'managed'); $is_valid = false; $correct_answer = null; if (!empty($token)) { $decoded = base64_decode(strtr($token, '-_', '+/')); if ($decoded !== false && strpos($decoded, '::') !== false) { list($payload, $signature) = explode('::', $decoded, 2); $salt = function_exists('wp_salt') ? wp_salt('auth') : (defined('AUTH_SALT') ? AUTH_SALT : 'fallback_salt_advaipbl'); $expected_signature = hash_hmac('sha256', $payload, $salt); if (hash_equals($expected_signature, $signature)) { $parts = explode('|', $payload); if (count($parts) === 2) { $parsed_answer = (int) $parts[0]; $expiration = (int) $parts[1]; if (time() <= $expiration) { $correct_answer = $parsed_answer; } } } } } if ($correct_answer !== null && $response === $correct_answer) { if ($mode_reported === 'managed') { // phpcs:ignore WordPress.Security.NonceVerification.Missing if (isset($_POST['human_check'])) { $is_valid = true; } } else { $is_valid = true; } } if ($is_valid) { if (isset($this->plugin->challenge_metrics)) { $this->plugin->challenge_metrics->increment('passed'); } set_transient('advaipbl_grace_pass_' . md5($ip), true, 15); $this->set_vip_pass_cookie($cookie_duration, $ip); } else { if (isset($this->plugin->challenge_metrics)) { $this->plugin->challenge_metrics->increment('failed'); } $this->plugin->log_event("JS Challenge verification failed (IP: {$ip}).", 'warning', [ 'action' => 'js_challenge_failed' ]); } if (! defined('DONOTCACHEPAGE')) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound define('DONOTCACHEPAGE', true); } header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); $this->plugin->log_event('JS challenge verification failed.', 'warning', ['ip' => $ip, 'token' => $token, 'response' => $response]); wp_die( esc_html__('Verification failed. Please ensure JavaScript and cookies are enabled in your browser.', 'advanced-ip-blocker'), esc_html__('Access Denied', 'advanced-ip-blocker'), ['response' => 403] ); } /** * Generates and sets the VIP pass cookie. * @param int $cookie_duration * @param string|null $ip */ public function set_vip_pass_cookie($cookie_duration, $ip = null) { if (!$ip) { $ip = $this->plugin->get_client_ip(); } $cookie_name = 'advaipbl_js_verified'; $expiration = ($cookie_duration > 0) ? time() + $cookie_duration : 0; $request_uri = esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'] ?? '')); $salt = function_exists('wp_salt') ? wp_salt('auth') : (defined('AUTH_SALT') ? AUTH_SALT : 'fallback_salt_advaipbl'); $modifier = get_option('advaipbl_vip_salt_modifier', ''); $signature = hash_hmac('sha256', $ip . $expiration, $salt . $modifier); $cookie_value = base64_encode($expiration . '|' . $signature); if (defined('ADVAIPBL_EDGE_MODE') && ADVAIPBL_EDGE_MODE) { if (ob_get_level()) { ob_end_clean(); } $cookie_options = [ 'expires' => $expiration, 'path' => '/', 'domain' => defined('COOKIE_DOMAIN') && COOKIE_DOMAIN ? COOKIE_DOMAIN : '', 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax' ]; setcookie($cookie_name, $cookie_value, $cookie_options); wp_safe_redirect($request_uri, 303); exit; } else { setcookie($cookie_name, $cookie_value, $expiration, '/', defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '', is_ssl(), true); wp_safe_redirect($request_uri, 303); exit; } } public function serve_challenge($challenge_type, $challenge_mode = 'managed') { if (isset($this->plugin->challenge_metrics)) { $this->plugin->challenge_metrics->increment('served'); } $global_engine = $this->plugin->options['default_challenge_engine'] ?? 'js_managed'; if ($challenge_mode === 'default' || $challenge_mode === 'managed' || $challenge_mode === 'automatic') { $challenge_mode = $global_engine; } $engine = 'js_challenge'; $final_mode = 'managed'; if ($challenge_mode === 'js_managed') { $engine = 'js_challenge'; $final_mode = 'managed'; } elseif ($challenge_mode === 'js_automatic') { $engine = 'js_challenge'; $final_mode = 'automatic'; } elseif ($challenge_mode === 'turnstile') { $engine = 'turnstile'; $final_mode = 'managed'; } elseif ($challenge_mode === 'hcaptcha') { $engine = 'hcaptcha'; $final_mode = 'managed'; } if ($engine === 'turnstile' || $engine === 'hcaptcha') { $has_keys = false; if ($engine === 'turnstile') { $site_key = $this->plugin->options['turnstile_site_key'] ?? ''; $secret_key = $this->plugin->options['turnstile_secret_key'] ?? ''; $has_keys = !empty($site_key) && !empty($secret_key); } else { $site_key = $this->plugin->options['hcaptcha_site_key'] ?? ''; $secret_key = $this->plugin->options['hcaptcha_secret_key'] ?? ''; $has_keys = !empty($site_key) && !empty($secret_key); } if (!$has_keys) { $this->plugin->log_event( sprintf("Missing API keys for %s. Falling back to JS Automatic Challenge to prevent lockout.", ucfirst($engine)), 'warning', ['action' => 'captcha_fallback', 'module' => $challenge_type] ); $engine = 'js_challenge'; $final_mode = 'automatic'; } } if (($engine === 'turnstile' || $engine === 'hcaptcha') && isset($this->plugin->captcha_manager)) { $this->plugin->captcha_manager->serve_challenge($challenge_type, $engine, $final_mode); exit; } $challenge_mode = $final_mode; if (!defined('DONOTCACHEPAGE')) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Required constant name for caching plugins define('DONOTCACHEPAGE', true); } if (headers_sent()) { return; } header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0'); header('Pragma: no-cache'); header('Expires: Thu, 01 Jan 1970 00:00:01 GMT'); header('X-LiteSpeed-Cache-Control: no-cache'); header('Cloudflare-CDN-Cache-Control: no-store'); header('CDN-Cache-Control: no-store'); header('Surrogate-Control: no-store'); header('X-Accel-Expires: 0'); header('X-Cache-Enabled: False'); header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline';"); $num1 = wp_rand(1, 9); $num2 = wp_rand(1, 9); $answer = $num1 + $num2; $expiration = time() + 300; $payload = $answer . '|' . $expiration; $salt = function_exists('wp_salt') ? wp_salt('auth') : (defined('AUTH_SALT') ? AUTH_SALT : 'fallback_salt_advaipbl'); $signature = hash_hmac('sha256', $payload, $salt); $token_raw = $payload . '::' . $signature; $token = strtr(base64_encode($token_raw), '+/', '-_'); status_header(503); header('Content-Type: text/html; charset=utf-8'); header('Retry-After: 10'); $protocol = is_ssl() ? 'https://' : 'http://'; $host = sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'] ?? '')); $uri = esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'] ?? '/')); $action_url = esc_url($protocol . $host . $uri); $site_host = wp_parse_url(home_url(), PHP_URL_HOST); $site_msg = esc_html__('needs to review the security of your connection before proceeding.', 'advanced-ip-blocker'); $site_title = get_bloginfo('name', 'display'); $page_title = esc_html__('Verifying your connection...', 'advanced-ip-blocker'); $main_heading = esc_html__('Security Check Required', 'advanced-ip-blocker'); $timer_text = esc_html__('Time remaining: ', 'advanced-ip-blocker'); $noscript_text = esc_html__('Please enable JavaScript to continue.', 'advanced-ip-blocker'); $button_text = esc_html__('Verify and Continue', 'advanced-ip-blocker'); $checkbox_label = esc_html__('I am not a robot.', 'advanced-ip-blocker'); $expired_heading = esc_html__('Session Expired', 'advanced-ip-blocker'); $expired_message = esc_html__('The security challenge has expired. Click the button below to get a new challenge.', 'advanced-ip-blocker'); $reload_button_text = esc_html__('Start New Challenge', 'advanced-ip-blocker'); if ($challenge_mode === 'automatic') { $main_text = esc_html__('Please wait while we verify your connection. This process is automatic and protects the website from automated attacks.', 'advanced-ip-blocker'); $js_script = ""; $form_content = '
'; } else { $main_text = esc_html__('To proceed, please prove you are human. This verification protects the website from automated attacks.', 'advanced-ip-blocker'); $js_script = ""; $form_content = '
'; } $html = ' ' . $page_title . ' - ' . esc_attr($site_title) . '

' . $main_heading . '

' . esc_html($site_host) . ' ' . $site_msg . '

' . $main_text . '

' . $form_content . '

' . $expired_heading . '

' . $expired_message . '

Performance & security by Advanced IP Blocker

' . $js_script . ' '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $html; exit; } }