API Security: The Attack That Almost Got Through
🚨 The Attack
It started with unusual traffic patterns. My API was getting hammered with requests from a single IP. Then I noticed authentication attempts—thousands of them.
Someone was trying to break into my system.
The attack: Brute force authentication, rate limit bypass attempts, and SQL injection probes. Here's what happened.
📊 What I Detected
1. Brute Force Authentication
10,000+ login attempts in one hour from a single IP. They were trying common passwords.
2. Rate Limit Bypass
They rotated IPs to bypass rate limits. Used proxies and VPNs.
3. SQL Injection Probes
Attempted SQL injection in API parameters. Classic attack patterns.
4. Directory Traversal
Tried to access files outside the web root using path traversal.
✅ How I Detected It
1. Log Monitoring
I noticed unusual patterns in Laravel logs:
// Unusual pattern: many 401 responses
[2024-12-28 10:00:00] Failed login attempt: user@example.com
[2024-12-28 10:00:01] Failed login attempt: admin@example.com
[2024-12-28 10:00:02] Failed login attempt: test@example.com
// ... thousands more
2. Rate Limiting Alerts
My rate limiter was triggering constantly. That was the first red flag.
3. Failed Authentication Counts
I track failed authentication attempts. The count spiked dramatically.
🛡️ What I Fixed
1. Enhanced Rate Limiting
Implemented stricter rate limits with IP-based and user-based tracking:
// Laravel rate limiting
Route::middleware(['throttle:5,1'])->group(function () {
Route::post('/login', [AuthController::class, 'login']);
});
// Custom rate limiter
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
2. Account Lockout
Lock accounts after multiple failed attempts:
if ($failedAttempts >= 5) {
$user->locked_until = now()->addMinutes(30);
$user->save();
throw new AccountLockedException('Account locked due to too many failed attempts');
}
3. IP Blocking
Block IPs that show attack patterns:
// Track suspicious IPs
$suspiciousIPs = Cache::remember("suspicious_ips", 3600, function () {
return DB::table('failed_logins')
->select('ip')
->where('created_at', '>', now()->subHour())
->groupBy('ip')
->havingRaw('COUNT(*) > 100')
->pluck('ip');
});
// Block in middleware
if ($suspiciousIPs->contains($request->ip())) {
abort(403, 'IP blocked due to suspicious activity');
}
4. Input Validation
Strict input validation to prevent injection attacks:
$request->validate([
'email' => 'required|email|max:255',
'password' => 'required|string|min:8',
]);
5. Security Headers
Added security headers:
// In middleware
return $next($request)
->header('X-Content-Type-Options', 'nosniff')
->header('X-Frame-Options', 'DENY')
->header('X-XSS-Protection', '1; mode=block')
->header('Strict-Transport-Security', 'max-age=31536000');
📊 Attack Statistics
| Attack Type | Attempts | Blocked | Status |
|---|---|---|---|
| Brute Force | 10,000+ | 100% | ✅ Blocked |
| SQL Injection | 500+ | 100% | ✅ Blocked |
| Path Traversal | 200+ | 100% | ✅ Blocked |
💡 Security Lessons
- Monitor logs: Regular log review catches attacks early
- Rate limiting: Essential for API security
- Account lockout: Prevents brute force attacks
- Input validation: Never trust user input
- Security headers: Add defense in depth
- IP blocking: Block known attackers
🎯 Key Takeaways
- Attacks happen—be prepared
- Monitoring is essential
- Rate limiting is your first defense
- Multiple layers of security are better
- Don't wait for an attack to implement security
The attack failed, but it was a wake-up call. I'm glad I had basic security in place, but I've strengthened it significantly since then.