Implement comprehensive spam protection for contact forms
- Add CSRF protection to quote form - Add honeypot fields to both forms - Expand spam keyword detection (25+ terms) - Implement bot detection (user agent & timing validation) - Add IP-based blocking system (24-hour blocks) - Enhanced content filtering and validation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,32 @@ if (!$referer_valid && !isset($_SERVER['HTTP_REFERER'])) {
|
|||||||
error_log("Contact form accessed without referer from IP: " . $_SERVER['REMOTE_ADDR']);
|
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
|
// Check rate limiting
|
||||||
if (!checkRateLimit()) {
|
if (!checkRateLimit()) {
|
||||||
sendResponse(false, 'Too many requests. Please try again later.');
|
sendResponse(false, 'Too many requests. Please try again later.');
|
||||||
@@ -149,16 +175,42 @@ if (isset($_POST['website']) && !empty($_POST['website'])) {
|
|||||||
sendResponse(false, 'Spam detected');
|
sendResponse(false, 'Spam detected');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for spam keywords
|
// Enhanced spam protection - expanded keyword list
|
||||||
$spamKeywords = ['viagra', 'casino', 'lottery', 'bitcoin', 'forex', 'loan', 'debt', 'pharmacy'];
|
$spamKeywords = [
|
||||||
$messageContent = strtolower($message . ' ' . $name . ' ' . $company);
|
'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'
|
||||||
|
];
|
||||||
|
|
||||||
|
$messageContent = strtolower($message . ' ' . $name . ' ' . $company);
|
||||||
foreach ($spamKeywords as $keyword) {
|
foreach ($spamKeywords as $keyword) {
|
||||||
if (strpos($messageContent, $keyword) !== false) {
|
if (strpos($messageContent, $keyword) !== false) {
|
||||||
sendResponse(false, 'Invalid content detected');
|
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)
|
||||||
|
if (!isset($_SESSION['form_start_time'])) {
|
||||||
|
$_SESSION['form_start_time'] = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
$submissionTime = time() - $_SESSION['form_start_time'];
|
||||||
|
if ($submissionTime < 5) { // Less than 5 seconds to fill form
|
||||||
|
sendResponse(false, 'Form submitted too quickly');
|
||||||
|
}
|
||||||
|
|
||||||
// Update rate limit counter
|
// Update rate limit counter
|
||||||
$ip = $_SERVER['REMOTE_ADDR'];
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||||||
$key = 'contact_' . md5($ip);
|
$key = 'contact_' . md5($ip);
|
||||||
|
|||||||
@@ -8,6 +8,18 @@ header('X-Frame-Options: DENY');
|
|||||||
header('X-XSS-Protection: 1; mode=block');
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// CSRF Protection
|
||||||
|
function generateCSRFToken() {
|
||||||
|
if (!isset($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
return $_SESSION['csrf_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCSRFToken($token) {
|
||||||
|
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
|
||||||
|
}
|
||||||
|
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
function checkRateLimit() {
|
function checkRateLimit() {
|
||||||
$ip = $_SERVER['REMOTE_ADDR'];
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||||||
@@ -71,11 +83,47 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|||||||
sendResponse(false, 'Invalid request method');
|
sendResponse(false, 'Invalid request method');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate CSRF token
|
||||||
|
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||||
|
sendResponse(false, 'Security validation failed. Please refresh the page and try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Check rate limiting
|
||||||
if (!checkRateLimit()) {
|
if (!checkRateLimit()) {
|
||||||
sendResponse(false, 'Too many requests. Please try again later.');
|
sendResponse(false, 'Too many requests. Please try again later.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spam protection - honeypot field
|
||||||
|
if (isset($_POST['website']) && !empty($_POST['website'])) {
|
||||||
|
sendResponse(false, 'Spam detected');
|
||||||
|
}
|
||||||
|
|
||||||
// Validate and sanitize inputs
|
// Validate and sanitize inputs
|
||||||
$services = $_POST['services'] ?? [];
|
$services = $_POST['services'] ?? [];
|
||||||
$project_scale = validateInput($_POST['project_scale'] ?? '', 'text');
|
$project_scale = validateInput($_POST['project_scale'] ?? '', 'text');
|
||||||
@@ -119,6 +167,42 @@ if (!empty($errors)) {
|
|||||||
sendResponse(false, implode('. ', $errors));
|
sendResponse(false, implode('. ', $errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enhanced spam protection - content filtering
|
||||||
|
$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'
|
||||||
|
];
|
||||||
|
|
||||||
|
$contentToCheck = strtolower($requirements . ' ' . $name . ' ' . $company . ' ' . $data_sources);
|
||||||
|
foreach ($spamKeywords as $keyword) {
|
||||||
|
if (strpos($contentToCheck, $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)
|
||||||
|
if (!isset($_SESSION['form_start_time'])) {
|
||||||
|
$_SESSION['form_start_time'] = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
$submissionTime = time() - $_SESSION['form_start_time'];
|
||||||
|
if ($submissionTime < 10) { // Less than 10 seconds for quote form (more complex)
|
||||||
|
sendResponse(false, 'Form submitted too quickly');
|
||||||
|
}
|
||||||
|
|
||||||
// Sanitize services array
|
// Sanitize services array
|
||||||
$services = array_map(function($service) {
|
$services = array_map(function($service) {
|
||||||
return htmlspecialchars(trim($service), ENT_QUOTES, 'UTF-8');
|
return htmlspecialchars(trim($service), ENT_QUOTES, 'UTF-8');
|
||||||
|
|||||||
@@ -411,6 +411,15 @@ $canonical_url = "https://ukdataservices.co.uk/quote";
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="quote-form-container">
|
<div class="quote-form-container">
|
||||||
<form id="quote-form" action="quote-handler.php" method="POST">
|
<form id="quote-form" action="quote-handler.php" method="POST">
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||||
|
<!-- Honeypot field for spam protection -->
|
||||||
|
<input type="text" name="website" style="display: none !important; position: absolute !important; left: -9999px !important;" tabindex="-1" autocomplete="off">
|
||||||
<!-- Step 1: Project Type -->
|
<!-- Step 1: Project Type -->
|
||||||
<div class="form-step">
|
<div class="form-step">
|
||||||
<div class="step-title">
|
<div class="step-title">
|
||||||
|
|||||||
Reference in New Issue
Block a user