web app security fix
This commit is contained in:
@@ -70,14 +70,37 @@ $user_id = $sth->fetchColumn();
|
||||
|
||||
$username = strtolower($data["username"]);
|
||||
|
||||
// ── Step 2: Fetch the user's hashed password ──────────────────────────────────
|
||||
$sth = $pdo1->prepare("select password from user where username = ? or email = ? limit 1;");
|
||||
// ── Step 2: Fetch the user's hashed password + lockout state ─────────────────
|
||||
$sth = $pdo1->prepare("SELECT password, login_attempts, locked_until FROM user WHERE username = ? OR email = ? LIMIT 1;");
|
||||
$sth->execute(array($username, $username));
|
||||
$temp = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// ── Step 2a: Lockout check — only when the username resolves to a real user ──
|
||||
// We only block here when $user_id is set (valid username) to avoid leaking
|
||||
// whether an account exists via a different error message.
|
||||
if ($user_id && !empty($temp['locked_until'])) {
|
||||
if (strtotime($temp['locked_until']) > time()) {
|
||||
// Still within the lockout window — reject
|
||||
$retry_at = date('H:i', strtotime($temp['locked_until']));
|
||||
$answer['message'] = "Too many failed attempts. Please try again after {$retry_at}.";
|
||||
exit(json_encode($answer));
|
||||
} else {
|
||||
// Lockout has expired — reset counter so they get a fresh 10 attempts
|
||||
$pdo1->prepare("UPDATE user SET login_attempts = 0, locked_until = NULL WHERE user_id = :id")
|
||||
->execute([':id' => $user_id]);
|
||||
$temp['login_attempts'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 3–4: Verify password — exit with error on mismatch ──────────────────
|
||||
if (password_verify(trim($data["password"]), $temp["password"])) {
|
||||
|
||||
// ── Reset lockout on successful password verification ─────────────────────
|
||||
if ($user_id) {
|
||||
$pdo1->prepare("UPDATE user SET login_attempts = 0, locked_until = NULL WHERE user_id = :id")
|
||||
->execute([':id' => $user_id]);
|
||||
}
|
||||
|
||||
// ── Step 5a: Fetch full user record ──────────────────────────────────────
|
||||
// 'support' user gets a hardcoded email so it can always log in even without
|
||||
// a registered email address in the DB.
|
||||
@@ -223,15 +246,10 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
|
||||
// ── Step 5f: Licence expiry check ────────────────────────────────────────
|
||||
// $expire is loaded from db_auth.php via session/preset bootstrap.
|
||||
// If the licence expired more than 1 day ago, reject the login.
|
||||
// Note: the cookie-clearing lines after exit() are unreachable — left as-is
|
||||
// to preserve original logic without business-logic changes.
|
||||
if (strtotime("now") > strtotime($expire . " + 1 day")) {
|
||||
session_destroy();
|
||||
$answer["expire"] = "expire";
|
||||
exit(json_encode($answer));
|
||||
setcookie("u", "", time() - 1, "/"); // unreachable — preserved from original
|
||||
setcookie("h1", "", time() - 1, "/");
|
||||
setcookie("h2", "", time() - 1, "/");
|
||||
}
|
||||
|
||||
// ── Step 6: Generate 6-digit TOTP ────────────────────────────────────────
|
||||
@@ -327,8 +345,25 @@ if (password_verify(trim($data["password"]), $temp["password"])) {
|
||||
} else {
|
||||
|
||||
// ── Password mismatch ─────────────────────────────────────────────────────
|
||||
// Clear identifying cookies on failure to prevent cookie-based session reuse.
|
||||
$answer["message"] = "Incorrect Password";
|
||||
// Only increment the counter when the username is valid — wrong usernames
|
||||
// don't count so a typo in your own name doesn't eat your own attempts.
|
||||
if ($user_id) {
|
||||
$attempts = (int)($temp['login_attempts'] ?? 0) + 1;
|
||||
if ($attempts >= 10) {
|
||||
$locked_until = date('Y-m-d H:i:s', strtotime('+30 minutes'));
|
||||
$pdo1->prepare("UPDATE user SET login_attempts = :a, locked_until = :l WHERE user_id = :id")
|
||||
->execute([':a' => $attempts, ':l' => $locked_until, ':id' => $user_id]);
|
||||
$retry_at = date('H:i', strtotime($locked_until));
|
||||
$answer['message'] = "Too many failed attempts. Please try again after {$retry_at}.";
|
||||
} else {
|
||||
$pdo1->prepare("UPDATE user SET login_attempts = :a WHERE user_id = :id")
|
||||
->execute([':a' => $attempts, ':id' => $user_id]);
|
||||
$answer['message'] = "Incorrect Password";
|
||||
}
|
||||
} else {
|
||||
$answer['message'] = "Incorrect Password";
|
||||
}
|
||||
|
||||
setcookie("u", "", time() - 1, "/");
|
||||
setcookie("h1", "", time() - 1, "/");
|
||||
setcookie("h2", "", time() - 1, "/");
|
||||
|
||||
Reference in New Issue
Block a user