Timing Attack in Gin
How Timing Attack Manifests in Gin
Timing attacks in Gin applications typically occur when authentication or authorization logic performs operations that leak information through response time variations. The most common pattern involves password comparison functions that exit early when a character mismatch is found, creating measurable timing differences between valid and invalid credentials.
In Gin, this manifests in several ways. When using basic authentication middleware, developers often write custom credential verification that directly compares plaintext passwords. Consider this vulnerable pattern:
func basicAuth() gin.HandlerFunc {
return func(c *gin.Context) {
user, pass, ok := c.Request.BasicAuth()
if !ok {
c.AbortWithStatus(401)
return
}
// Vulnerable: early exit on first mismatch
if user != validUser || pass != validPass {
c.AbortWithStatus(401)
return
}
c.Next()
}
}
The string comparison operator (!=) in Go performs byte-by-byte comparison and returns immediately upon finding the first mismatch. An attacker can measure response times across many requests to determine valid characters in the password one by one.
Another Gin-specific manifestation occurs in route parameter validation. When checking authorization tokens or API keys in URL parameters:
func validateToken(c *gin.Context) {
token := c.Param("token")
if token != expectedToken {
c.AbortWithStatus(401)
return
}
c.Next()
}
This creates timing variations based on how many characters match before failing. The Gin router's parameter extraction and validation sequence also introduces timing differences between valid and invalid routes, though this is typically less exploitable than credential comparison.
Database queries in Gin handlers present another vector. When authentication involves database lookups with conditional logic:
func checkUser(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
var user User
db.Where("username = ?", username).First(&user)
// Vulnerable: early exit if user not found
if user.ID == 0 || user.Password != password {
c.AbortWithStatus(401)
return
}
c.Next()
}
The database query timing itself leaks whether a username exists, and the subsequent password comparison timing leaks password validity. An attacker can first enumerate valid usernames by measuring query response times, then perform password timing attacks on valid accounts.
Gin-Specific Detection
Detecting timing attacks in Gin applications requires both static analysis and runtime monitoring. For static analysis, middleBrick's API security scanner examines your Gin application's source code patterns to identify vulnerable comparison operations and conditional logic that could leak timing information.
middleBrick specifically scans for these Gin patterns:
middlebrick scan https://your-gin-app.com/api/auth
The scanner analyzes your OpenAPI/Swagger spec if available, then performs black-box testing against your endpoints. For timing attacks, it measures response time variations across authentication endpoints, looking for statistically significant differences between valid and invalid credentials.
Key detection patterns middleBrick identifies in Gin applications:
- Direct string comparisons in authentication middleware
- Early return patterns in authorization checks
- Database query patterns that leak existence information
- Route parameter validation with timing variations
- API key validation with non-constant time comparisons
- Session token validation with early exits
For runtime detection, middleBrick's continuous monitoring (Pro plan) can track response time distributions over time. It establishes baseline response times for each endpoint and alerts when timing variations exceed normal thresholds, which might indicate active timing attacks.
You can also use Gin's built-in middleware for basic timing monitoring:
r := gin.New()
// Add timing middleware
r.Use(func(c *gin.Context) {
start := time.Now()
c.Next()
latency := time.Since(start)
log.Printf("[%s] %s %s %v", c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr, latency)
})
This provides basic timing data, but middleBrick's specialized analysis goes further by correlating timing patterns with authentication failures and identifying statistical anomalies that suggest timing attacks.
Gin-Specific Remediation
Fixing timing attacks in Gin requires implementing constant-time comparison functions and eliminating early exit patterns. The Go standard library provides crypto/subtle for constant-time comparisons:
import "crypto/subtle"
func constantTimeAuth(c *gin.Context) {
user, pass, ok := c.Request.BasicAuth()
if !ok {
c.AbortWithStatus(401)
return
}
// Constant-time comparison for both username and password
valid := subtle.ConstantTimeCompare([]byte(user), []byte(validUser)) == 1 &&
subtle.ConstantTimeCompare([]byte(pass), []byte(validPass)) == 1
if !valid {
c.AbortWithStatus(401)
return
}
c.Next()
}
For database-based authentication, use constant-time comparison after fetching the user record:
func dbAuth(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
var user User
db.Where("username = ?", username).First(&user)
// Always perform hash comparison, even if user not found
validPassword := false
if user.ID != 0 {
validPassword = subtle.ConstantTimeCompare(
[]byte(password),
[]byte(user.Password)) == 1
}
// Constant-time final validation
valid := subtle.ConstantTimeSelect(
subtle.ConstantTimeCompare([]byte(username), []byte(validUser)),
validPassword,
false)
if !valid {
c.AbortWithStatus(401)
return
}
c.Next()
}
For API key validation in route parameters:
func validateAPIKey(c *gin.Context) {
token := c.Param("token")
// Always perform constant-time comparison
valid := subtle.ConstantTimeCompare([]byte(token), []byte(expectedToken)) == 1
if !valid {
c.AbortWithStatus(401)
return
}
c.Next()
}
For JWT validation in Gin, use constant-time signature verification:
func jwtAuth(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatus(401)
return
}
// Use a JWT library that performs constant-time verification
claims, err := VerifyJWT(token)
if err != nil {
c.AbortWithStatus(401)
return
}
c.Set("claims", claims)
c.Next()
}
The key principle is ensuring all code paths take approximately the same time regardless of input validity. This includes database queries (always query, never early return), comparisons (always use constant-time), and error handling (always return the same HTTP status).