mirror of
https://github.com/myronblair/jarvis
synced 2026-07-28 08:43:00 -05:00
Auth hardening: login rate-limiting + session fixation defenses
- login.php: Redis-backed per-IP rate limit (10 fails / 15 min lockout), keyed off CF-Connecting-IP/X-Forwarded-For so it sees the real client behind NPM; fails open if Redis is down - login.php: session_regenerate_id(true) on successful auth (prevents session fixation) - php.ini: session.use_strict_mode = 1 (reject unknown/attacker-supplied session IDs) - netscan.php: constant-time hash_equals for the registration-key check (matches agent.php) Cookie flags already HttpOnly + SameSite=Lax (verified live). Agent auth verified: missing/bad X-Agent-Key -> 401 on every machine action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ if ($method !== 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$reqKey = $_SERVER['HTTP_X_REGISTRATION_KEY'] ?? '';
|
$reqKey = $_SERVER['HTTP_X_REGISTRATION_KEY'] ?? '';
|
||||||
if ($reqKey !== NETSCAN_KEY) {
|
if (!hash_equals(NETSCAN_KEY, $reqKey)) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(['error' => 'Unauthorized']); exit;
|
echo json_encode(['error' => 'Unauthorized']); exit;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-20
@@ -5,27 +5,47 @@ require_once __DIR__ . '/../api/config.php';
|
|||||||
session_start();
|
session_start();
|
||||||
if (!empty($_SESSION['jarvis_token'])) { header('Location: /'); exit; }
|
if (!empty($_SESSION['jarvis_token'])) { header('Location: /'); exit; }
|
||||||
$error = '';
|
$error = '';
|
||||||
|
// ── Login rate limiting (Redis, per client IP) ────────────────────────────────
|
||||||
|
// Blocks brute force: 10 failed attempts within 15 min -> locked out for 15 min.
|
||||||
|
$clientIp = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||||
|
$clientIp = trim(explode(',', $clientIp)[0]);
|
||||||
|
$rl = null;
|
||||||
|
try {
|
||||||
|
$rl = new Redis();
|
||||||
|
$rl->connect('127.0.0.1', 6379, 1.5);
|
||||||
|
} catch (Throwable $e) { $rl = null; } // fail open if Redis is down
|
||||||
|
$rlKey = 'login_fail:' . $clientIp;
|
||||||
|
$RL_MAX = 10; $RL_WINDOW = 900;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$u = trim($_POST['username'] ?? '');
|
$fails = ($rl && $rl->exists($rlKey)) ? (int)$rl->get($rlKey) : 0;
|
||||||
$p = $_POST['password'] ?? '';
|
if ($fails >= $RL_MAX) {
|
||||||
if ($u && $p) {
|
$error = 'TOO MANY ATTEMPTS — LOCKED';
|
||||||
$pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
|
} else {
|
||||||
DB_USER, DB_PASS,
|
$u = trim($_POST['username'] ?? '');
|
||||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
$p = $_POST['password'] ?? '';
|
||||||
$row = $pdo->prepare('SELECT * FROM users WHERE username=? LIMIT 1');
|
if ($u && $p) {
|
||||||
$row->execute([$u]);
|
$pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
|
||||||
$user = $row->fetch(PDO::FETCH_ASSOC);
|
DB_USER, DB_PASS,
|
||||||
if ($user && password_verify($p, $user['password_hash'])) {
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||||
$token = bin2hex(random_bytes(32));
|
$row = $pdo->prepare('SELECT * FROM users WHERE username=? LIMIT 1');
|
||||||
$_SESSION['jarvis_token'] = $token;
|
$row->execute([$u]);
|
||||||
$_SESSION['jarvis_user_id'] = $user['id'];
|
$user = $row->fetch(PDO::FETCH_ASSOC);
|
||||||
$_SESSION['jarvis_name'] = $user['display_name'];
|
if ($user && password_verify($p, $user['password_hash'])) {
|
||||||
$pdo->prepare('UPDATE users SET last_seen=NOW() WHERE id=?')->execute([$user['id']]);
|
if ($rl) $rl->del($rlKey);
|
||||||
header('Location: /');
|
session_regenerate_id(true);
|
||||||
exit;
|
$token = bin2hex(random_bytes(32));
|
||||||
}
|
$_SESSION['jarvis_token'] = $token;
|
||||||
$error = 'ACCESS DENIED';
|
$_SESSION['jarvis_user_id'] = $user['id'];
|
||||||
} else { $error = 'ENTER CREDENTIALS'; }
|
$_SESSION['jarvis_name'] = $user['display_name'];
|
||||||
|
$pdo->prepare('UPDATE users SET last_seen=NOW() WHERE id=?')->execute([$user['id']]);
|
||||||
|
header('Location: /');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($rl) { $rl->incr($rlKey); $rl->expire($rlKey, $RL_WINDOW); }
|
||||||
|
$error = 'ACCESS DENIED';
|
||||||
|
} else { $error = 'ENTER CREDENTIALS'; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?><!DOCTYPE html>
|
?><!DOCTYPE html>
|
||||||
<html lang="en"><head>
|
<html lang="en"><head>
|
||||||
|
|||||||
Reference in New Issue
Block a user