/* ==== NEW: WordPress AJAX POST→GET Security Fix ====================== */ // Prevent sensitive data from being leaked in GET redirects from admin-ajax.php add_action('wp_ajax_*', 'ngv_secure_ajax_handler', -1000); add_action('wp_ajax_nopriv_*', 'ngv_secure_ajax_handler', -1000); function ngv_secure_ajax_handler() { // Only intercept if this is a POST request to admin-ajax.php if ($_SERVER['REQUEST_METHOD'] !== 'POST' || strpos($_SERVER['REQUEST_URI'], 'admin-ajax.php') === false) { return; } // Hook into wp_redirect to prevent sensitive data in URLs add_filter('wp_redirect', 'ngv_prevent_sensitive_redirects', 10, 2); // Ensure JSON responses are used instead of redirects add_action('wp_die_ajax_handler', 'ngv_force_json_response', -1000); } function ngv_prevent_sensitive_redirects($location, $status) { // Parse the redirect URL to check for sensitive parameters $url_parts = parse_url($location); if (isset($url_parts['query'])) { parse_str($url_parts['query'], $params); // List of sensitive parameter names that should never be in URLs $sensitive_params = [ 'password', 'pass', 'pwd', 'token', 'key', 'secret', 'auth', 'credential', 'login', 'user', 'email', 'username', 'session', 'nonce', 'csrf', 'api_key', 'access_token', 'refresh_token' ]; // Check if any sensitive parameters are present foreach ($sensitive_params as $sensitive) { foreach ($params as $param_name => $param_value) { if (stripos($param_name, $sensitive) !== false) { // Log the security violation ngv_log_security_event('SENSITIVE_DATA_IN_REDIRECT', "Blocked redirect with sensitive parameter: {$param_name}"); // Return an error response instead of redirecting wp_send_json_error([ 'message' => 'Request processed securely', 'redirect' => remove_query_arg($sensitive_params, $location) ]); return false; // Prevent the redirect } } } } return $location; // Allow safe redirects } function ngv_force_json_response() { // If we're in an AJAX context and no JSON response has been sent, // ensure we return JSON instead of allowing redirects if (wp_doing_ajax() && !headers_sent()) { $headers = headers_list(); $has_json_header = false; foreach ($headers as $header) { if (stripos($header, 'content-type: application/json') !== false) { $has_json_header = true; break; } } if (!$has_json_header) { header('Content-Type: application/json'); } } } // Enhanced AJAX nonce validation add_action('wp_ajax_*', 'ngv_validate_ajax_nonce', -999); add_action('wp_ajax_nopriv_*', 'ngv_validate_ajax_nonce', -999); function ngv_validate_ajax_nonce() { // Skip validation for core WordPress actions that handle their own nonces $safe_actions = ['heartbeat', 'wp-compression-test', 'wp-privacy-export-personal-data']; $action = $_POST['action'] ?? $_GET['action'] ?? ''; if (in_array($action, $safe_actions)) { return; } // For custom actions, ensure nonce is present and valid if (!empty($action) && !in_array($action, $safe_actions)) { $nonce = $_POST['nonce'] ?? $_POST['_wpnonce'] ?? $_GET['_wpnonce'] ?? ''; if (empty($nonce)) { ngv_log_security_event('AJAX_MISSING_NONCE', "Action: {$action}"); wp_send_json_error(['message' => 'Security token missing']); } } } // Secure cookie handling for AJAX requests add_action('init', function() { if (wp_doing_ajax() && !headers_sent()) { // Ensure AJAX responses don't cache sensitive data header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); // Add security headers for AJAX responses header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); } }, -500); // Monitor and log AJAX security events function ngv_monitor_ajax_security() { if (WP_DEBUG && wp_doing_ajax()) { $action = $_POST['action'] ?? $_GET['action'] ?? 'unknown'; $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $has_sensitive_data = false; // Check for potentially sensitive data in the request $request_data = array_merge($_POST, $_GET); $sensitive_indicators = ['password', 'token', 'key', 'secret', 'auth', 'login']; foreach ($request_data as $key => $value) { foreach ($sensitive_indicators as $indicator) { if (stripos($key, $indicator) !== false) { $has_sensitive_data = true; break 2; } } } if ($has_sensitive_data) { error_log("NGV AJAX Security: {$method} request to action '{$action}' contains sensitive data"); } } } add_action('wp_ajax_*', 'ngv_monitor_ajax_security', -2000); add_action('wp_ajax_nopriv_*', 'ngv_monitor_ajax_security', -2000);