Timing Attack in Gorilla Mux with Basic Auth
Timing Attack in Gorilla Mux with Basic Auth — how this specific combination creates or exposes the vulnerability
A timing attack in the context of Gorilla Mux with HTTP Basic Auth exploits the fact that credential validation can short-circuit as soon as a mismatch is found. In Go, when you compare the provided username and password using simple equality checks, the comparison often stops at the first differing byte. This behavior introduces a measurable difference in execution time depending on how many initial bytes are correct. An attacker who can measure response times precisely can iteratively guess the correct credentials byte by byte, gradually reducing the search space until the full secret is recovered.
Gorilla Mux is a standard HTTP router; it does not add authentication itself. If you implement Basic Auth by wrapping your handlers with a manual check that compares the decoded username and password strings directly, the timing characteristics of those string comparisons become relevant. For example, using username == expectedUser && password == expectedPassword will usually fail fast when the username is wrong, but if the username matches and the password diverges later, the comparison will take longer as it processes more bytes. This difference can be observable in network latency, especially on high-performance services where timing noise is relatively low.
The attack surface is unauthenticated because middleBrick tests the endpoint without credentials, probing for subtle timing differences that may indicate validation behavior. During a scan, checks related to Authentication, Input Validation, and Rate Limiting can help surface whether the endpoint is susceptible to timing-based leakage. Although middleBrick does not attempt to exploit timing attacks directly, it highlights findings that could enable such side-channel behavior. Proper remediation involves ensuring that all credential checks take constant time, regardless of validity, combined with other protections to reduce the feasibility of timing measurements.
Basic Auth-Specific Remediation in Gorilla Mux — concrete code fixes
To mitigate timing-based side channels in Gorilla Mux when using HTTP Basic Auth, ensure that username and password comparisons execute in constant time. Use cryptographic functions designed for this purpose rather than native string equality. Below are concrete, working examples that demonstrate a secure approach.
Example 1: Constant-time comparison with subtle and HMAC-based approach
Use subtle.ConstantTimeCompare for both username and password. This function returns 1 only when the two slices are exactly equal and does so in constant time.
package main
import (
"crypto/subtle"
"encoding/base64"
"net/http"
"strings"
)
const (
expectedUser = "admin"
expectedPass = "s3cr3tP@ss"
)
func basicAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
payload, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Split on the first ':' only
for i := 0; i < len(payload); i++ {
if payload[i] == ':' {
userProvided := payload[:i:i] // subslice to avoid allocation
passProvided := payload[i+1:]
userOK := subtle.ConstantTimeCompare(userProvided, []byte(expectedUser)) == 1
passOK := subtle.ConstantTimeCompare(passProvided, []byte(expectedPass)) == 1
if userOK && passOK {
next.ServeHTTP(w, r)
return
}
break
}
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, authenticated user."))
}
func main() {
r := http.NewServeMux()
r.Handle("/hello", basicAuthMiddleware(http.HandlerFunc(helloHandler)))
http.ListenAndServe(":8080", r)
}
Example 2: HMAC-based approach for stored credentials
If you store credentials as hashes, compare HMACs of the provided password against the stored HMAC using a constant-time comparison. This avoids leaking information about which part of the credential is incorrect.
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"strings"
)
var storedHMACKey = []byte("supersecretmackey")
var storedPasswordHMAC = "" // precomputed, e.g., base64 of HMAC-SHA256
func computeHMAC(password string) string {
mac := hmac.New(sha256.New, storedHMACKey)
mac.Write([]byte(password))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func basicAuthMiddlewareHMAC(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
payload, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
for i := 0; i < len(payload); i++ {
if payload[i] == ':' {
passProvided := payload[i+1:]
providedHMAC := computeHMAC(string(passProvided))
if subtle.ConstantTimeCompare([]byte(providedHMAC), []byte(storedPasswordHMAC)) == 1 {
next.ServeHTTP(w, r)
return
}
break
}
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
})
}
In both examples, the use of subtle.ConstantTimeCompare ensures that the comparison duration does not depend on the correctness of the input. This removes the timing side channel exploitable by an attacker measuring response times. middleBrick can identify whether an endpoint shows timing-sensitive behavior under the Authentication and Input Validation checks, guiding you toward these secure patterns.