Full website with security hardening: - Redesigned sections: hero, services, capabilities, clients, partners, stats, about, workflow, contact - Contact form fixed (variable names, XSS sanitisation, rate limiting, open redirect removed) - .htaccess with security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) - error_log blocked from public access - Robots.txt corrected to allow JS/CSS for SEO rendering
127 lines
3.7 KiB
PHP
127 lines
3.7 KiB
PHP
<?php
|
|
// Only handle POST requests
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
http_response_code(405);
|
|
exit('Method Not Allowed');
|
|
}
|
|
|
|
// Basic rate limiting via session (max 3 submissions per hour per IP)
|
|
session_start();
|
|
$ip = $_SERVER['REMOTE_ADDR'];
|
|
$key = 'cf_submissions_' . md5($ip);
|
|
$now = time();
|
|
if (!isset($_SESSION[$key])) {
|
|
$_SESSION[$key] = [];
|
|
}
|
|
// Keep only submissions within the last hour
|
|
$_SESSION[$key] = array_filter($_SESSION[$key], fn($t) => $now - $t < 3600);
|
|
if (count($_SESSION[$key]) >= 3) {
|
|
if ($this_is_ajax()) {
|
|
http_response_code(429);
|
|
echo json_encode(['form_ok' => false, 'errors' => ['Too many submissions. Please try again later.']]);
|
|
} else {
|
|
header('Location: /');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function this_is_ajax(): bool {
|
|
return !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
|
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
|
}
|
|
|
|
function h(string $str): string {
|
|
return htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
}
|
|
|
|
$formok = true;
|
|
$errors = [];
|
|
|
|
$date = date('d/m/Y');
|
|
$time = date('H:i:s');
|
|
|
|
// Read and sanitise inputs
|
|
$name = trim($_POST['Name'] ?? '');
|
|
$email = trim($_POST['Email'] ?? '');
|
|
$telephone = trim($_POST['Phone'] ?? '');
|
|
$message = trim($_POST['message'] ?? '');
|
|
|
|
// Validate name
|
|
if ($name === '') {
|
|
$formok = false;
|
|
$errors[] = 'You have not entered a name';
|
|
}
|
|
|
|
// Validate email
|
|
if ($email === '') {
|
|
$formok = false;
|
|
$errors[] = 'You have not entered an email address';
|
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
$formok = false;
|
|
$errors[] = 'You have not entered a valid email address';
|
|
}
|
|
|
|
// Validate message
|
|
if ($message === '') {
|
|
$formok = false;
|
|
$errors[] = 'You have not entered a message';
|
|
} elseif (strlen($message) < 20) {
|
|
$formok = false;
|
|
$errors[] = 'Your message must be at least 20 characters';
|
|
}
|
|
|
|
// Strip newlines from header-injectable fields (mail header injection defence)
|
|
$name = str_replace(["\r", "\n"], ' ', $name);
|
|
$email = str_replace(["\r", "\n"], ' ', $email);
|
|
$telephone = str_replace(["\r", "\n"], ' ', $telephone);
|
|
|
|
if ($formok) {
|
|
// Record this submission in the rate-limit bucket
|
|
$_SESSION[$key][] = $now;
|
|
|
|
$to = 'info@provoc.ug';
|
|
$subject = 'New Enquiry from Website';
|
|
|
|
$headers = "From: noreply@provocgroup.com\r\n";
|
|
$headers .= "Reply-To: " . h($email) . "\r\n";
|
|
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
|
$headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
|
|
|
|
$emailbody = "
|
|
<html><body style=\"font-family:Arial,sans-serif;font-size:14px;color:#333\">
|
|
<p>You have received a new enquiry from the website contact form.</p>
|
|
<table cellpadding=\"6\" cellspacing=\"0\" border=\"0\">
|
|
<tr><td><strong>Name:</strong></td><td>" . h($name) . "</td></tr>
|
|
<tr><td><strong>Email:</strong></td><td>" . h($email) . "</td></tr>
|
|
<tr><td><strong>Phone:</strong></td><td>" . h($telephone) . "</td></tr>
|
|
<tr><td><strong>Message:</strong></td><td>" . nl2br(h($message)) . "</td></tr>
|
|
<tr><td><strong>IP:</strong></td><td>" . h($ip) . "</td></tr>
|
|
<tr><td><strong>Date/Time:</strong></td><td>" . h($date) . " " . h($time) . "</td></tr>
|
|
</table>
|
|
</body></html>";
|
|
|
|
mail($to, $subject, $emailbody, $headers);
|
|
}
|
|
|
|
$returndata = [
|
|
'posted_form_data' => [
|
|
'name' => $name,
|
|
'email' => $email,
|
|
'phone' => $telephone,
|
|
'message' => $message,
|
|
],
|
|
'form_ok' => $formok,
|
|
'errors' => $errors,
|
|
];
|
|
|
|
if (this_is_ajax()) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode($returndata);
|
|
exit;
|
|
}
|
|
|
|
// Non-AJAX: redirect back to homepage (never to user-supplied Referer)
|
|
$_SESSION['cf_returndata'] = $returndata;
|
|
header('Location: /');
|
|
exit;
|