Password Spraying in Fiber
How Password Spraying Manifests in Fiber
Password spraying is a brute-force attack where adversaries try a small set of common passwords (like 'Password123', 'admin', 'welcome') against many user accounts, bypassing account lockout policies that trigger after repeated failures on a single account. In Fiber applications, this vulnerability typically stems from two code-level issues: missing rate limiting on authentication endpoints and inadequate password complexity enforcement.
A typical Fiber login handler without protections looks like this:
app.Post("/login", func(c *fiber.Ctx) error {
username := c.FormValue("username")
password := c.FormValue("password")
user, err := db.GetUserByUsername(username)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid credentials",
})
}
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid credentials",
})
}
// Set session, return token, etc.
return c.JSON(fiber.Map{"status": "success"})
})This code has two critical flaws for password spraying:
- No rate limiting: An attacker can iterate through thousands of usernames with the same common password, and the server will process each request without throttling.
- Uniform error messages: Both 'user not found' and 'wrong password' return the same 401 response. This prevents the attacker from enumerating valid usernames but still allows spraying since the attacker can try the same password (e.g., 'Spring2024!') against all discovered usernames from other sources (like data breaches).
Fiber's default behavior does not include any built-in protection against rapid-fire authentication attempts. Even if you use bcrypt for password hashing (which is good for credential storage), the lack of request throttling at the HTTP layer leaves the endpoint exposed.
Fiber-Specific Detection
Detecting password spraying vulnerabilities in Fiber applications involves checking for two primary indicators: the absence of rate limiting middleware on /login (or similar) routes and the presence of generic authentication error messages that don't leak user existence.
Manual inspection requires reviewing your Fiber router setup. Look for missing limiter.New() middleware on authentication endpoints. Also, verify that your login handler doesn't differentiate between 'user not found' and 'wrong password' in production.
Automated scanning with middleBrick simplifies this. When you submit your Fiber API's base URL, middleBrick's Rate Limiting check will:
- Send a burst of login requests (e.g., 20 attempts in 10 seconds) with the same username but different passwords.
- Monitor for HTTP 429 (Too Many Requests) responses,
Retry-Afterheaders, or increasing response latencies. - Cross-reference findings with its Authentication check to ensure error messages don't reveal user existence.
For example, a vulnerable Fiber API might receive this middleBrick finding:
{
"check": "rate_limiting",
"severity": "high",
"title": "Authentication endpoint lacks rate limiting",
"endpoint": "/api/v1/login",
"evidence": "Sent 20 requests in 5s. All returned 200/401 without throttling.",
"remediation": "Apply per-IP rate limiting to login routes using Fiber's limiter middleware. Consider also limiting by username/IP combination to prevent spraying."
}middleBrick's black-box approach tests your live Fiber deployment exactly as an attacker would—no credentials, no config files needed—and reports whether your authentication surface is resilient to rapid credential stuffing.
Fiber-Specific Remediation
Fixing password spraying in Fiber requires implementing layered defenses: request throttling, secure password handling, and optional account lockout. Below are production-ready patterns.
1. Apply Rate Limiting to Login Routes
Use Fiber's official limiter middleware. For authentication, you typically want a lower threshold than for public endpoints:
package main
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
)
func main() {
app := fiber.New()
// Global limiter (optional for other routes)
// app.Use(limiter.New(limiter.Config{ Max: 100, Expiration: 1 * time.Minute }))
// Stricter limiter for login only
loginLimiter := limiter.New(limiter.Config{
Max: 5, // Max 5 attempts
Expiration: 15 * time.Minute,
KeyGenerator: func(c *fiber.Ctx) string {
// Limit by IP AND username to prevent spraying across accounts
username := c.FormValue("username")
if username == "" {
username = "anonymous"
}
return c.IP() + ":" + username
},
LimitReached: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "Too many login attempts. Please try again later.",
})
},
})
app.Post("/login", loginLimiter, loginHandler)
app.Listen(":3000")
}This configuration limits each IP/username combination to 5 attempts per 15 minutes. The KeyGenerator combines IP and username to prevent an attacker from using a single IP to spray many accounts (or a single account from many IPs).
2. Use Uniform Error Messages
Your login handler must return identical responses for invalid usernames and wrong passwords:
func loginHandler(c *fiber.Ctx) error {
username := c.FormValue("username")
password := c.FormValue("password")
user, err := db.GetUserByUsername(username)
if err != nil || user == nil {
// Always hash a dummy password to maintain consistent timing
dummyHash, _ := bcrypt.GenerateFromPassword([]byte("dummy"), bcrypt.DefaultCost)
bcrypt.CompareHashAndPassword(dummyHash, []byte(password))
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid username or password",
})
}
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid username or password",
})
}
// Successful login logic...
return c.JSON(fiber.Map{"status": "authenticated"})
}The dummy bcrypt comparison ensures timing doesn't reveal whether the username exists.
3. Optional: Account Lockout with Caution
Fiber doesn't have built-in account lockout, but you can implement it via a cache (e.g., Redis) tracking failed attempts per username. Be wary of denial-of-service via deliberate lockouts. A safer approach is exponential backoff: after each failure, increase the delay before accepting another attempt for that account.
After applying these fixes, rescan with middleBrick. Its Rate Limiting check should now report a passing status, and the Authentication check will verify your error message consistency.
Frequently Asked Questions
Does middleBrick only check for rate limiting, or does it also test for other authentication weaknesses that enable password spraying?
Can I use middleBrick's CLI to scan my Fiber API from my CI/CD pipeline?
middlebrick npm package and run npx middlebrick scan https://your-fiber-api.com/login. For CI/CD, use the provided GitHub Action to automatically scan staging APIs and fail builds if the security score drops below your threshold. This integrates seamlessly into your existing Go/Fiber deployment workflow.