Phase 1: Implement CSRF protection, input validation, and rate limiting
Major security improvements: - Added CSRF token generation, validation, and cleanup functions - Implemented comprehensive input validators (email, phone, name, date, amount, ID, file uploads) - Added rate limiting with login attempt tracking and account lockout (5 failures = 15 min lockout) - Implemented session fixation protection with session_regenerate_id() and 30-min timeout - Fixed SQL injection in getResultFromTable() with whitelisted columns/tables - Added audit logging for security events - Applied CSRF validation to all 7 process_*.php files - Applied input validation to critical endpoints (login, registration, bookings, application) - Created database migration for login_attempts, audit_log tables and locked_until column Modified files: - functions.php: +500 lines of security functions - validate_login.php: Added CSRF, rate limiting, session hardening - register_user.php: Added CSRF, input validation, registration rate limiting - process_*.php (7 files): Added CSRF token validation - Created migration: 001_phase1_security_schema.sql Next steps: Add CSRF tokens to form templates, harden file uploads, create testing checklist
This commit is contained in:
@@ -13,8 +13,8 @@ if (!$conn) {
|
||||
|
||||
// Google Client Setup
|
||||
$client = new Google_Client();
|
||||
$client->setClientId('948441222188-8qhboq2urr8o9n35mc70s5h2nhd52v0m.apps.googleusercontent.com');
|
||||
$client->setClientSecret('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');
|
||||
$client->setClientId($_ENV['GOOGLE_CLIENT_ID']);
|
||||
$client->setClientSecret($_ENV['GOOGLE_CLIENT_SECRET']);
|
||||
$client->setRedirectUri($_ENV['HOST'] . '/validate_login.php');
|
||||
$client->addScope("email");
|
||||
$client->addScope("profile");
|
||||
@@ -86,18 +86,57 @@ if (isset($_GET['code'])) {
|
||||
|
||||
// Check if email and password login is requested
|
||||
if (isset($_POST['email']) && isset($_POST['password'])) {
|
||||
// Retrieve and sanitize form data
|
||||
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
|
||||
$password = trim($_POST['password']); // Remove extra spaces
|
||||
|
||||
// Validate input
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog(null, 'CSRF_VALIDATION_FAILED', 'users', null, ['endpoint' => 'validate_login.php']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Retrieve and validate email input
|
||||
$email = validateEmail($_POST['email']);
|
||||
if ($email === false) {
|
||||
auditLog(null, 'INVALID_EMAIL_FORMAT', 'users', null, ['email' => $_POST['email']]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Retrieve and sanitize password
|
||||
$password = isset($_POST['password']) ? trim($_POST['password']) : '';
|
||||
|
||||
// Basic validation
|
||||
if (empty($email) || empty($password)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||
|
||||
// Check for account lockout
|
||||
$lockoutStatus = checkAccountLockout($email);
|
||||
if ($lockoutStatus['is_locked']) {
|
||||
auditLog(null, 'LOGIN_ATTEMPT_LOCKED_ACCOUNT', 'users', null, [
|
||||
'email' => $email,
|
||||
'locked_until' => $lockoutStatus['locked_until']
|
||||
]);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Account is temporarily locked due to multiple failed login attempts. Please try again in ' . $lockoutStatus['minutes_remaining'] . ' minutes.'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Check recent failed attempts
|
||||
$recentFailedAttempts = countRecentFailedAttempts($email);
|
||||
if ($recentFailedAttempts >= 5) {
|
||||
// Lock account for 15 minutes
|
||||
lockAccount($email, 15);
|
||||
auditLog(null, 'ACCOUNT_LOCKED_THRESHOLD', 'users', null, [
|
||||
'email' => $email,
|
||||
'failed_attempts' => $recentFailedAttempts
|
||||
]);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Account locked due to multiple failed login attempts. Please try again in 15 minutes.'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -120,22 +159,55 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
|
||||
|
||||
// Check if the user is verified
|
||||
if ($row['is_verified'] == 0) {
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_ATTEMPT_UNVERIFIED_ACCOUNT', 'users', $row['user_id']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (password_verify($password, $row['password'])) {
|
||||
// Record successful attempt
|
||||
recordLoginAttempt($email, true);
|
||||
|
||||
// Regenerate session ID to prevent session fixation attacks
|
||||
session_regenerate_id(true);
|
||||
|
||||
// Password is correct, set up session
|
||||
$_SESSION['user_id'] = $row['user_id']; // Adjust as per your table structure
|
||||
$_SESSION['first_name'] = $row['first_name']; // Adjust as per your table structure
|
||||
$_SESSION['user_id'] = $row['user_id'];
|
||||
$_SESSION['first_name'] = $row['first_name'];
|
||||
$_SESSION['profile_pic'] = $row['profile_pic'];
|
||||
|
||||
// Set session timeout (30 minutes)
|
||||
$_SESSION['login_time'] = time();
|
||||
$_SESSION['session_timeout'] = 1800; // 30 minutes in seconds
|
||||
|
||||
auditLog($row['user_id'], 'LOGIN_SUCCESS', 'users', $row['user_id']);
|
||||
echo json_encode(['status' => 'success', 'message' => 'Successful Login']);
|
||||
} else {
|
||||
// Password is incorrect
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid password.']);
|
||||
// Password is incorrect - record failed attempt
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_FAILED_INVALID_PASSWORD', 'users', null, ['email' => $email]);
|
||||
|
||||
// Check if this was the threshold failure
|
||||
$newFailureCount = countRecentFailedAttempts($email);
|
||||
if ($newFailureCount >= 5) {
|
||||
lockAccount($email, 15);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Too many failed login attempts. Account locked for 15 minutes.'
|
||||
]);
|
||||
} else {
|
||||
$attemptsRemaining = 5 - $newFailureCount;
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid password. ' . $attemptsRemaining . ' attempts remaining before account lockout.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User does not exist
|
||||
// User does not exist - still record attempt
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_FAILED_USER_NOT_FOUND', 'users', null, ['email' => $email]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'User with that email does not exist.']);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user