Rate Limiting Bypass in Gin with Bearer Tokens
Rate Limiting Bypass in Gin with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Rate limiting in Gin typically relies on identifying requests by a stable key. When Bearer tokens are used for authentication, a common approach is to key the rate limiter on the token value itself. This couples the limit to the credential rather than to the user or client identity. If token issuance is not tightly scoped, an attacker who obtains a low-privilege token can still exhaust the quota allocated for that token. Because the limiter counts requests per token string, the same token can be reused across many client instances, enabling a horizontal bypass where many compromised tokens or a single shared token circumvent intended per-user caps.
Another bypass vector arises from how Gin middleware is ordered and how authentication and rate limiting are composed. If the authentication middleware attaches identity to the context after the rate limit middleware runs, the rate limiter may see an unauthenticated request and apply a default or higher limit. Additionally, if the token is included as a query parameter or in an easily modified header and the rate limiter uses the raw value without normalization, an attacker can vary the token casing or add whitespace to produce distinct keys that the limiter treats as separate clients. Poor handling of token extraction also creates gaps: if the middleware fails to reject malformed or missing tokens before counting requests, unauthenticated probes can still be tallied against the endpoint’s quota, effectively diluting the limit across invalid identities.
Specification and runtime analysis can expose these issues. middleBrick scans the OpenAPI/Swagger definition (2.0, 3.0, 3.1) with full $ref resolution and correlates it with runtime behavior. It checks whether authentication and rate limiting are consistently applied, whether tokens are normalized before counting, and whether unauthenticated paths still expose unprotected endpoints. The scanner’s LLM/AI Security checks include active prompt injection tests and system prompt leakage detection, but for API security they also flag missing or inconsistent security schemes that can enable rate limiting bypass. Findings include severity, a description of the misconfiguration, and remediation guidance mapped to frameworks such as OWASP API Top 10 and PCI-DSS.
Bearer Tokens-Specific Remediation in Gin — concrete code fixes
To fix rate limiting bypass with Bearer tokens in Gin, enforce a stable, authoritative identity before counting requests and ensure consistent middleware ordering. Prefer a numeric user or client identifier extracted from a validated token rather than using the raw token string as the rate limit key. Below are concrete, syntactically correct examples for Gin in Go.
package main
import (
"context"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// extractUserID extracts a stable user ID from a Bearer token.
// It returns empty string if the token is invalid.
func extractUserID(tokenString string) string {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// TODO: provide your key function, e.g., HMAC or RSA public key
return []byte("your-secret"), nil
})
if err != nil || !token.Valid {
return ""
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return ""
}
uid, ok := claims["sub"].(string)
if !ok || uid == ""{
return ""
}
return uid
}
func main() {
r := gin.Default()
// Authentication middleware: validates Bearer token and attaches user ID.
authMiddleware := func(c *gin.Context) {
auth := c.GetHeader("Authorization")
if auth == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization header required"})
return
}
const bearerPrefix = "Bearer "
if !strings.HasPrefix(auth, bearerPrefix) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization format"})
return
}
token := strings.TrimPrefix(auth, bearerPrefix)
userID := extractUserID(token)
if userID == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("userID", userID)
}
// Rate limiting middleware keyed by user ID.
// Replace inMemoryLimiter with a distributed store for production.
type limiter struct {
allow map[string]int
limit int
}
inMemoryLimiter := &limiter{allow: make(map[string]int), limit: 100}
rateLimitMiddleware := func(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "user context missing"})
return
}
key := userID.(string)
inMemoryLimiter.allow[key]++
if inMemoryLimiter.allow[key] > inMemoryLimiter.limit {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
return
}
c.Next()
}
// Correct order: authenticate first, then rate limit.
r.Use(authMiddleware)
r.Use(rateLimitMiddleware)
r.GET("/profile", func(c *gin.Context) {
userID, _ := c.Get("userID")
c.JSON(http.StatusOK, gin.H{"userID": userID})
})
_ = r.Run() // listen and serve on 0.0.0.0:8080
}
Key remediation points:
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |