User Agent: ' . htmlspecialchars($_SERVER['HTTP_USER_AGENT']) . '
Timestamp: ' . date('Y-m-d H:i:s') . ' UTC
Referrer: ' . htmlspecialchars($_SERVER['HTTP_REFERER'] ?? 'Direct') . '
0, 'time' => time()]; } $data = $_SESSION[$key]; // Reset counter if more than 1 hour has passed if (time() - $data['time'] > 3600) { $_SESSION[$key] = ['count' => 0, 'time' => time()]; $data = $_SESSION[$key]; } // Allow max 5 submissions per hour if ($data['count'] >= 5) { return false; } return true; } // Input validation and sanitization function validateInput($data, $type = 'text') { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8'); // Prevent header injection $data = str_replace(array("\r", "\n", "%0a", "%0d"), '', $data); switch ($type) { case 'email': $email = filter_var($data, FILTER_VALIDATE_EMAIL); // Additional email validation to prevent header injection if ($email && !preg_match('/[\r\n]/', $email)) { return $email; } return false; case 'phone': return preg_match('/^[\+]?[0-9\s\-\(\)]+$/', $data) ? $data : false; case 'text': return strlen($data) > 0 ? $data : false; case 'message': return strlen($data) >= 10 ? $data : false; default: return $data; } } // Response function function sendResponse($success, $message, $data = null) { $response = [ 'success' => $success, 'message' => $message ]; if ($data !== null) { $response['data'] = $data; } echo json_encode($response); exit; } // Handle POST requests only if ($_SERVER['REQUEST_METHOD'] !== 'POST') { sendResponse(false, 'Invalid request method'); } // Check referer to prevent external form submissions $allowed_referers = ['ukdataservices.co.uk', 'www.ukdataservices.co.uk', 'localhost']; $referer_valid = false; if (isset($_SERVER['HTTP_REFERER'])) { $referer_host = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); foreach ($allowed_referers as $allowed) { if ($referer_host === $allowed || strpos($referer_host, $allowed) !== false) { $referer_valid = true; break; } } } // Allow direct access for testing but log it if (!$referer_valid && !isset($_SERVER['HTTP_REFERER'])) { error_log("Contact form accessed without referer from IP: " . $_SERVER['REMOTE_ADDR']); } // Check for blocked IPs function checkBlockedIP() { $ip = $_SERVER['REMOTE_ADDR']; $blockFile = 'logs/blocked-ips.txt'; if (file_exists($blockFile)) { $blockedIPs = file($blockFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($blockedIPs as $blockedEntry) { $parts = explode('|', $blockedEntry); if (isset($parts[0]) && $parts[0] === $ip) { $blockTime = isset($parts[1]) ? (int)$parts[1] : 0; // Block for 24 hours if (time() - $blockTime < 86400) { return false; } } } } return true; } // Check for blocked IPs first if (!checkBlockedIP()) { sendResponse(false, 'Access temporarily restricted'); } // Check rate limiting if (!checkRateLimit()) { sendResponse(false, 'Too many requests. Please try again later.'); } // Verify reCAPTCHA v3 if (isset($_POST['recaptcha_response'])) { $recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify'; $recaptcha_secret = RECAPTCHA_SECRET_KEY; $recaptcha_response = $_POST['recaptcha_response']; $recaptcha_data = array( 'secret' => $recaptcha_secret, 'response' => $recaptcha_response, 'remoteip' => $_SERVER['REMOTE_ADDR'] ); $recaptcha_options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($recaptcha_data) ) ); $recaptcha_context = stream_context_create($recaptcha_options); $recaptcha_result = @file_get_contents($recaptcha_url, false, $recaptcha_context); if ($recaptcha_result === false) { // Log reCAPTCHA API failure but don't block submission error_log("reCAPTCHA API call failed for IP: " . $_SERVER['REMOTE_ADDR']); } else { $recaptcha_json = json_decode($recaptcha_result, true); // Temporarily disable reCAPTCHA check for test keys if (!$recaptcha_json['success']) { // Log suspicious activity but don't block for test keys $logEntry = date('Y-m-d H:i:s') . " - RECAPTCHA WARNING: " . json_encode($recaptcha_json) . " from " . $_SERVER['REMOTE_ADDR'] . "\n"; file_put_contents('logs/contact-errors.log', $logEntry, FILE_APPEND | LOCK_EX); } // Only block if score is extremely low and not using test keys if (isset($recaptcha_json['score']) && $recaptcha_json['score'] < 0.1 && RECAPTCHA_SITE_KEY !== '6LcdAtUUAAAAAPX-5YJaWKJmeq7QIMjeLTS7qy6s') { $logEntry = date('Y-m-d H:i:s') . " - RECAPTCHA BLOCKED: Score " . $recaptcha_json['score'] . " from " . $_SERVER['REMOTE_ADDR'] . "\n"; file_put_contents('logs/contact-errors.log', $logEntry, FILE_APPEND | LOCK_EX); sendResponse(false, 'Security verification failed. Please try again.'); } } } else { // No reCAPTCHA response - likely a bot sendResponse(false, 'Security verification required.'); } // Validate and sanitize inputs $name = validateInput($_POST['name'] ?? '', 'text'); $email = validateInput($_POST['email'] ?? '', 'email'); $company = validateInput($_POST['company'] ?? '', 'text'); $service = validateInput($_POST['service'] ?? '', 'text'); $message = validateInput($_POST['message'] ?? '', 'message'); // Validation $errors = []; if (!$name || strlen($name) < 2) { $errors[] = 'Please enter a valid name (minimum 2 characters)'; } if (!$email) { $errors[] = 'Please enter a valid email address'; } if (!$message) { $errors[] = 'Please provide project details (minimum 10 characters)'; } if (!empty($errors)) { sendResponse(false, implode('. ', $errors)); } // Spam protection - simple honeypot and content filtering if (isset($_POST['website']) && !empty($_POST['website'])) { // Honeypot field filled - likely spam sendResponse(false, 'Spam detected'); } // Enhanced spam protection - expanded keyword list $spamKeywords = [ 'viagra', 'cialis', 'casino', 'lottery', 'bitcoin', 'forex', 'loan', 'debt', 'pharmacy', 'click here', 'act now', 'limited time', 'risk free', 'guarantee', 'no obligation', 'free money', 'make money fast', 'work from home', 'get rich', 'investment opportunity', 'credit repair', 'refinance', 'consolidate debt', 'weight loss', 'miracle cure', 'lose weight', 'adult content', 'porn', 'sex', 'dating', 'singles', 'webcam', 'escort', 'massage', 'crypto', 'nft', 'blockchain', 'earn money', 'passive income', 'financial freedom', 'mlm', 'network marketing', 'online casino', 'gambling', 'betting', 'binary options' ]; // Check for disposable email domains $disposableEmailDomains = [ 'mailinator.com', 'guerrillamail.com', '10minutemail.com', 'temp-mail.org', 'throwawaymail.com', 'yopmail.com', 'mailnesia.com', 'trashmail.com', 'maildrop.cc', 'mailcatch.com', 'tempmail.com', 'email-fake.com', 'fakeinbox.com', 'sharklasers.com', 'guerrillamailblock.com' ]; $emailDomain = substr(strrchr($email, "@"), 1); if (in_array(strtolower($emailDomain), $disposableEmailDomains)) { sendResponse(false, 'Please use a valid business email address.'); } $messageContent = strtolower($message . ' ' . $name . ' ' . $company); foreach ($spamKeywords as $keyword) { if (strpos($messageContent, $keyword) !== false) { sendResponse(false, 'Invalid content detected'); } } // Bot detection - check for suspicious patterns $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $suspiciousAgents = ['curl', 'wget', 'python', 'bot', 'crawler', 'spider', 'scraper']; foreach ($suspiciousAgents as $agent) { if (stripos($userAgent, $agent) !== false) { sendResponse(false, 'Automated submissions not allowed'); } } // Check submission speed (too fast = likely bot) - More lenient timing if (isset($_SESSION['form_start_time'])) { $submissionTime = time() - $_SESSION['form_start_time']; if ($submissionTime < 2) { // Only block if under 2 seconds (very aggressive bots) sendResponse(false, 'Form submitted too quickly'); } } // Check for XMLHttpRequest header (JavaScript submission) if (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') { // Log direct POST attempt $logEntry = date('Y-m-d H:i:s') . " - DIRECT POST attempt from " . $_SERVER['REMOTE_ADDR'] . "\n"; file_put_contents('logs/contact-errors.log', $logEntry, FILE_APPEND | LOCK_EX); sendResponse(false, 'Invalid submission method'); } // Verify interaction token (human behavior verification) if (isset($_POST['interaction_token']) && !empty($_POST['interaction_token'])) { $tokenData = @json_decode(base64_decode($_POST['interaction_token']), true); if ($tokenData && isset($tokenData['score'])) { if ($tokenData["score"] < 3) { // Log low interaction score $logEntry = date('Y-m-d H:i:s') . " - LOW INTERACTION SCORE: " . $tokenData['score'] . " from " . $_SERVER['REMOTE_ADDR'] . "\n"; file_put_contents('logs/contact-errors.log', $logEntry, FILE_APPEND | LOCK_EX); sendResponse(false, 'Please complete the form normally'); } } } // Verify form timestamp (prevent replay attacks) - temporarily disabled for debugging // Timestamp validation is now very lenient - only blocks obviously invalid timestamps if (isset($_POST['form_timestamp']) && !empty($_POST['form_timestamp'])) { $formTimestamp = intval($_POST['form_timestamp']); // Only block if timestamp is 0 or clearly invalid (before year 2020) if ($formTimestamp > 0 && $formTimestamp < 1577836800000) { // Before Jan 1, 2020 $logEntry = date('Y-m-d H:i:s') . " - INVALID TIMESTAMP: " . $formTimestamp . " from " . $_SERVER['REMOTE_ADDR'] . "\n"; @file_put_contents('logs/contact-errors.log', $logEntry, FILE_APPEND | LOCK_EX); // Don't block, just log } } // Log all form submissions for debugging $debugLog = date('Y-m-d H:i:s') . " - DEBUG: timestamp=" . ($_POST['form_timestamp'] ?? 'NOT SET') . ", IP=" . $_SERVER['REMOTE_ADDR'] . "\n"; @file_put_contents('logs/contact-debug.log', $debugLog, FILE_APPEND | LOCK_EX); // Update rate limit counter $ip = $_SERVER['REMOTE_ADDR']; $key = 'contact_' . md5($ip); $_SESSION[$key]['count']++; // Prepare email content $to = 'info@ukdataservices.co.uk'; $subject = 'New Contact Form Submission - UK Data Services'; // Create HTML email $emailHTML = '
UK Data Services