diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 14 |
1 files changed, 14 insertions, 0 deletions
@@ -184,6 +184,20 @@ Nginx adds HTTP response headers that instruct the browser to enforce additional When a login fails, our error message says "Invalid username or password" rather than "Username not found" or "Wrong password". Similarly, the forgot-password form always says "If that email is registered, a reset link has been sent." This is deliberate: if the server told you *which* part was wrong, an attacker could probe the system to build a list of valid usernames or emails, then target those accounts specifically with brute-force or phishing attacks. +### Rate limiting (brute-force protection) + +Without rate limiting, an attacker can try thousands of passwords per second against a login form (a brute-force attack), or flood the password reset endpoint to spam a victim's inbox. Even strong passwords can be cracked given unlimited attempts — an 8-character password has a finite number of combinations, and automated tools can cycle through common passwords and variations very quickly. + +Camagru tracks attempts per IP address using a `rate_limits` database table. Each row records which IP performed which action and when. Before processing a login or password reset request, the `RateLimiter` class counts how many attempts that IP has made within a sliding time window. If the count exceeds the threshold, the request is rejected immediately — the password isn't even checked. + +**Limits enforced:** +- **Login:** 5 failed attempts per IP per 15 minutes. Only failed attempts are recorded — a successful login doesn't count against you. This means a legitimate user who knows their password is never blocked, but an attacker guessing passwords hits the wall after 5 tries. +- **Password reset:** 3 requests per IP per 15 minutes. Unlike login, *every* attempt is recorded (not just failures), because the reset endpoint doesn't reveal whether the email exists. If only "successful" resets counted, an attacker could probe for valid emails by watching which requests get rate-limited and which don't. + +**Why per-IP and not per-username?** If rate limiting were per-username, an attacker could lock out any user by deliberately failing logins against their account (a denial-of-service). Per-IP limiting means only the attacker's own IP gets blocked — the real user can still log in from their own network. The trade-off is that a distributed attack from many IPs can bypass this, but that level of attack is beyond the scope of a school project and would require infrastructure-level solutions (WAF, fail2ban, etc.). + +**Table cleanup:** to prevent the `rate_limits` table from growing indefinitely, old entries are purged on every check. Each call to `isLimited()` first deletes rows older than the time window, so only recent attempts are kept. This is a simple approach that works well for low-traffic applications — high-traffic production systems would typically use an in-memory store like Redis instead of a database table. + ### Upload security (preventing unwanted content) A classic web vulnerability is allowing users to upload arbitrary files — an attacker might upload a `.php` script disguised as an image and then request it directly to execute server-side code. Camagru prevents this with multiple layers: |
