Api Rate Abuse in Gin with Basic Auth
Api Rate Abuse in Gin with Basic Auth — how this specific combination creates or exposes the vulnerability
Rate abuse in a Gin API protected only by Basic Auth can occur because authentication is checked per request rather than being tied to a rate-limiting mechanism. When Basic Auth is used, credentials are sent on every request. If the server does not enforce request limits, an attacker can repeatedly authenticate with valid credentials and flood the endpoint. This unauthenticated attack surface is what middleBrick scans, testing endpoints without requiring credentials, and it can detect missing or misconfigured rate controls even when Basic Auth is present.
In Gin, a common pattern adds Basic Auth via middleware that validates credentials on each request. However, if rate limiting is omitted or applied after authentication, there is no gate to prevent credentialed brute force or credential stuffing. Attackers can cycle through passwords or use leaked credentials to make many authenticated calls in a short window, consuming server resources and potentially triggering account lockouts or denial of service. Because middleBrick tests unauthenticated attack paths, it can highlight endpoints where authentication exists but rate limiting does not sufficiently constrain abusive behavior.
The interaction between Basic Auth and rate limiting becomes critical under scenarios such as token enumeration or password spraying. Even with strong passwords, the absence of per-identity or per-client rate limits allows an attacker to authenticate successfully once (using a valid credential set) and then proceed to abuse data or functionality. Gin handlers that perform expensive operations (database queries, external calls) without early throttling amplify the impact. A scanner that supports OpenAPI/Swagger spec analysis, including full $ref resolution, can cross-reference the spec’s securitySchemes and security requirements with the actual routes to identify where authentication is declared but rate-limiting constraints are missing.
Because middleBrick runs 12 security checks in parallel, it evaluates rate limiting alongside Authentication and BOLA/IDOR checks. This helps expose scenarios where Basic Auth is implemented but does not adequately constrain request volume per principal. The findings include severity-ranked guidance to help you remediate the issue by introducing proper rate-limiting controls that consider authenticated identities, client IPs, and API keys where applicable.
Basic Auth-Specific Remediation in Gin — concrete code fixes
To mitigate rate abuse in Gin with Basic Auth, enforce rate limits before or alongside authentication, and ensure limits are applied per identity or per client. Below are concrete, working examples that combine Basic Auth validation with rate limiting using standard Go libraries.
Example 1: Basic Auth with per-user rate limiting using a map and mutex
package main
import (
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
type RateLimiter struct {
mu sync.Mutex
requests map[string][]time.Time // key: username
limit int
window time.Duration
}
func newRateLimiter(limit int, window time.Duration) *RateLimiter {
return &RateLimiter{
requests: make(map[string][]time.Time),
limit: limit,
window: window,
}
}
func (rl *RateLimiter) Allow(key string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
// Clean old entries
var allowed bool
filtered := []time.Time{}
for _, t := range rl.requests[key] {
if now.Sub(t) < rl.window {
filtered = append(filtered, t)
}
}
rl.requests[key] = filtered
if len(rl.requests[key]) < rl.limit {
rl.requests[key] = append(rl.requests[key], now)
allowed = true
}
return allowed
}
func BasicAuthMiddleware(rl *RateLimiter) gin.HandlerFunc {
return func(c *gin.Context) {
username, password, ok := c.Request.BasicAuth()
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization required"})
return
}
// Validate credentials (replace with your logic)
if username != "admin" || password != "securepassword" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
if !rl.Allow(username) {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
return
}
c.Next()
}
}
func main() {
r := gin.Default()
rl := newRateLimiter(5, time.Minute) // 5 requests per minute per user
r.Use(BasicAuthMiddleware(rl))
r.GET("/secure", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "access granted"})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
Example 2: Using a token-bucket rate limiter with client IP fallback
package main
import (
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
var (
ipLimiter = struct {
sync.Mutex
m map[string]*rate.Limiter
}{m: make(map[string]*rate.Limiter)}
globalLimiter = rate.NewLimiter(10, 20) // global burst
)
func getLimiter(ip string) *rate.Limiter {
ipLimiter.Lock()
defer ipLimiter.Unlock()
if limiter, exists := ipLimiter.m[ip]; exists {
return limiter
}
limiter := rate.NewLimiter(1, 5) // 1 req/s burst 5 per IP
ipLimiter.m[ip] = limiter
return limiter
}
func RateLimitMiddleware(c *gin.Context) {
if !globalLimiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "global rate limit"})
return
}
ip := c.ClientIP()
limiter := getLimiter(ip)
if !limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit per IP"})
return
}
c.Next()
}
func BasicAuthWithRateLimit(c *gin.Context) {
username, password, ok := c.Request.BasicAuth()
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization required"})
return
}
// Validate credentials (replace with your logic)
if username != "admin" || password != "securepassword" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
// Optionally apply per-user rate limiting here as well
c.Next()
}
func main() {
r := gin.Default()
r.Use(RateLimitMiddleware)
r.GET("/public", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"public": "ok"})
})
r.GET("/auth", BasicAuthWithRateLimit, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"auth": "ok"})
})
r.Run()
}
In both examples, the Gin server validates Basic Auth credentials on each request and enforces rate limits either per username or per client IP. This reduces the risk of credentialed abuse while keeping the implementation straightforward and dependency-light. middleBrick’s scans can verify that these protections are present and that rate-limiting checks are reachable on authenticated routes.