Rate Limiting Bypass in Gin
How Rate Limiting Bypass Manifests in Gin
Rate limiting bypass in Gin applications typically exploits weaknesses in how the middleware tracks and enforces request limits. The most common attack vectors target the identification mechanism that rate limiters use to distinguish between users.
Gin's default rate limiting implementations often rely on client IP addresses extracted from the X-Forwarded-For header. An attacker can bypass these limits by rotating through IP addresses using proxy chains or cloud services. Here's a vulnerable pattern:
r := gin.Default()
// Vulnerable: trusts X-Forwarded-For without validation
rl := tollbooth.NewLimiter(10, time.Minute)
rl.IPLookups = []string{"X-Forwarded-For"}
r.Use(tollbooth_gin.LimitHandler(rl))
r.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "data"})
})
This code trusts the X-Forwarded-For header, which can be easily spoofed. An attacker sends requests with different IP addresses in this header, effectively creating unlimited new rate limit buckets.
Another common bypass occurs when rate limiting is applied at the wrong layer. If you rate limit only the HTTP handler but not middleware or upstream services, attackers can exhaust resources through alternative endpoints. Consider this flawed approach:
r.GET("/api/expensive", gin.RateLimit(5, time.Minute), func(c *gin.Context) {
// expensive operation
})
r.GET("/api/health", func(c *gin.Context) {
// no rate limiting, but calls expensive operation
heavyOperation()
})
An attacker can bypass the rate limit on /api/expensive by hammering /api/health, which performs the same operation without restrictions.
Time-based bypasses are particularly insidious in Gin applications. If your rate limiter uses wall-clock time without accounting for clock skew or time zone differences, distributed attackers can exploit this. Some implementations also suffer from integer overflow issues where extremely high request counts wrap around to negative values.
Header manipulation attacks are especially effective against Gin applications that use custom rate limiting logic. If you implement your own limiter based on request headers, attackers can modify or remove identifying headers to appear as new users:
func customRateLimiter() gin.HandlerFunc {
return func(c *gin.Context) {
userID := c.GetHeader("X-User-ID")
if userID == "" {
userID = generateRandomID()
}
// Rate limit by userID
}
}
Here, simply omitting the X-User-ID header generates a new rate limit bucket each request.
Gin-Specific Detection
Detecting rate limiting bypasses in Gin applications requires examining both the middleware configuration and runtime behavior. Start by analyzing how your application identifies clients for rate limiting purposes.
Using middleBrick's API security scanner, you can automatically detect common rate limiting bypass vulnerabilities in your Gin endpoints. The scanner examines your API's unauthenticated attack surface and identifies:
- Endpoints that trust client-provided IP addresses without validation
- Missing rate limits on high-risk endpoints
- Inconsistent rate limiting across similar endpoints
- Rate limiting that can be bypassed through header manipulation
middleBrick's LLM/AI security module also detects if your Gin application exposes AI endpoints that might be vulnerable to prompt injection attacks that could manipulate rate limiting logic in AI-powered APIs.
Manual detection involves testing your endpoints with various IP addresses and headers. Here's how to verify your rate limiting implementation:
# Test X-Forwarded-For bypass
curl -H "X-Forwarded-For: 1.2.3.4" http://your-api.com/endpoint
curl -H "X-Forwarded-For: 5.6.7.8" http://your-api.com/endpoint
If both requests succeed beyond your rate limit, you have a bypass vulnerability. Test with multiple headers like X-Real-IP, Forwarded, and even custom headers your application might trust.
Monitor your application logs for unusual patterns: sudden traffic spikes from many different IP addresses, repeated requests with manipulated headers, or traffic patterns that suggest automated bypassing attempts.
middleBrick's continuous monitoring (Pro plan) can alert you when new bypass vulnerabilities are detected in your staging or production APIs, helping you catch issues before deployment.
Gin-Specific Remediation
Securing rate limiting in Gin requires implementing robust client identification and consistent enforcement across all endpoints. Here's how to fix common bypass vulnerabilities:
First, validate and normalize client IP addresses instead of trusting headers:
func getRealIP(c *gin.Context) string {
// Trust X-Forwarded-For only if from trusted proxy
forwarded := c.Request.Header.Get("X-Forwarded-For")
if isTrustedProxy(c.Request.RemoteAddr) && forwarded != "" {
parts := strings.Split(forwarded, ",")
return strings.TrimSpace(parts[0])
}
return c.ClientIP()
}
// Use in rate limiter
rl := tollbooth.NewLimiter(100, time.Minute)
rl.IPLookups = []string{"RealIP"}
r.Use(tollbooth_gin.LimitHandler(rl))
This implementation only trusts forwarded headers from known proxies, preventing attackers from spoofing their IP address.
Apply rate limiting consistently across all endpoints that perform similar operations:
// Global rate limiter for all API endpoints
apiLimiter := tollbooth.NewLimiter(100, time.Minute)
apiLimiter.IPLookups = []string{"RealIP"}
// Apply to all routes
public := r.Group("/api")
public.Use(tollbooth_gin.LimitHandler(apiLimiter))
// Additional limits for expensive operations
expensive := public.Group("/expensive")
expensive.Use(tollbooth_gin.LimitHandler(
tollbooth.NewLimiter(10, time.Minute),
))
This ensures attackers can't bypass limits by finding alternative endpoints that perform the same work.
For applications with authenticated users, rate limit by user ID rather than IP:
type userIDBucketStore struct{
store map[string]*tollbooth.Bucket
mu sync.RWMutex
}
func (s *userIDBucketStore) GetBucket(userID string) *tollbooth.Bucket {
s.mu.RLock()
bucket, exists := s.store[userID]
s.mu.RUnlock()
if !exists {
s.mu.Lock()
defer s.mu.Unlock()
bucket = tollbooth.NewBucket(100, time.Minute)
s.store[userID] = bucket
}
return bucket
}
func userRateLimiter() gin.HandlerFunc {
store := &userIDBucketStore{store: make(map[string]*tollbooth.Bucket)}
return func(c *gin.Context) {
userID := c.GetString("userID") // from auth middleware
if userID == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
bucket := store.GetBucket(userID)
if !bucket.TakeAvailable(1) {
c.AbortWithStatusJSON(429, gin.H{"error": "rate limit exceeded"})
return
}
c.Next()
}
}
This approach prevents IP-based bypasses and ensures each authenticated user has consistent limits regardless of their network location.
Consider implementing distributed rate limiting using Redis for applications behind load balancers:
import "github.com/go-redis/redis/v8"
func redisRateLimiter() gin.HandlerFunc {
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
return func(c *gin.Context) {
key := "rl:" + getRealIP(c)
current, err := client.Incr(context.Background(), key).Result()
if err != nil {
c.Next()
return
}
if current == 1 {
client.Expire(context.Background(), key, time.Minute)
}
if current > 100 {
c.AbortWithStatusJSON(429, gin.H{"error": "rate limit exceeded"})
return
}
c.Next()
}
}
This ensures rate limiting works correctly across multiple server instances and prevents distributed bypass attempts.
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 |
Frequently Asked Questions
How can I test if my Gin API has rate limiting bypass vulnerabilities?
X-Forwarded-For headers, multiple IP addresses, and by checking if similar endpoints have inconsistent rate limiting. Monitor for unusual traffic patterns that suggest automated bypassing attempts.