Dns Cache Poisoning in Gorilla Mux with Jwt Tokens
Dns Cache Poisoning in Gorilla Mux with Jwt Tokens — how this specific combination creates or exposes the vulnerability
In a setup using Gorilla Mux as the router and JWT tokens for authorization, DNS cache poisoning can undermine trust in the service identity that JWT validation relies on. When a client resolves a hostname to an IP address—whether via a malicious resolver, a compromised DNS response, or an attacker on the network path—the client may be directed to an endpoint that is not the legitimate API service. If the client then sends JWT-signed requests to that poisoned IP, the tokens are presented to an unintended host that does not share the expected validation context.
Gorilla Mux does not perform DNS resolution itself; it routes requests based on patterns matched after the HTTP server has accepted a connection. Therefore, if the underlying transport is poisoned, requests bearing valid JWTs can be delivered to a rogue server that either does not validate tokens or validates them incorrectly. This creates a scenario where authentication appears to succeed on the client side, but the authorization boundary is broken because the server endpoint is not the intended one. Attack patterns such as cache poisoning can lead to token interception or to tokens being accepted by an attacker-controlled host that mimics the API surface, enabling unauthorized access or token replay.
JWT validation in Gorilla Mux is typically implemented as a middleware check that inspects the Authorization header, parses the token, and verifies claims such as issuer, audience, and expiration. If DNS cache poisoning redirects the client to a malicious server, the malicious server may present a different public key or accept tokens without proper verification. Because JWTs often contain important authorization information (scopes, roles), an attacker who can observe or alter the path may gain insights or force the client to interact with a hostile service. This is especially risky when token introspection or key discovery (JWKS) endpoints are involved, as poisoned DNS responses can redirect those lookups to attacker-controlled endpoints that supply invalid or malicious keys.
Jwt Tokens-Specific Remediation in Gorilla Mux — concrete code fixes
Remediation focuses on ensuring that JWT validation is performed consistently and that the client and server agree on the expected network identity. Even when using Gorilla Mux, you must enforce strict hostname verification over the final connection and avoid relying solely on DNS for trust. Use HTTPS with certificate pinning or strict hostname checks on the client, and validate all JWT claims on every request, independent of routing logic.
Below are concrete code examples for a Gorilla Mux-based service that validates JWTs with strong claims verification and mitigates risks when DNS resolution is involved.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
"github.com/golang-jwt/jwt/v5"
)
// Hard‑coded or environment‑provided expected values — in production, load securely.
const (
expectedIssuer = "https://auth.example.com"
expectedAudience = "api.example.com"
hostname = "api.example.com" // enforce expected hostname for TLS verification
)
var jwksURL = "https://auth.example.com/.well-known/jwks.json"
func jwtMiddleware(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, `{"error":"missing_authorization"}`, http.StatusUnauthorized)
return
}
tokenString := auth[len("Bearer "):]
// Parse with keyfunc that fetches signing keys securely.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Ensure signing method is as expected.
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
key, err := fetchKeyFromJWKS(jwksURL, token.Header["kid"])
if err != nil {
return nil, err
}
return key, nil
})
if err != nil || !token.Valid {
http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized)
return
}
// Enforce issuer and audience claims.
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, `{"error":"invalid_claims"}`, http.StatusUnauthorized)
return
}
if iss, ok := claims["iss"].(string); !ok || iss != expectedIssuer {
http.Error(w, `{"error":"invalid_issuer"}`, http.StatusUnauthorized)
return
}
if aud, ok := claims["aud"].(string); !ok || aud != expectedAudience {
http.Error(w, `{"error":"invalid_audience"}`, http.StatusUnauthorized)
return
}
// Enforce expiration and not‑before strictly.
if exp, ok := claims["exp"].(float64); !ok || time.Unix(int64(exp), 0).Before(time.Now()) {
http.Error(w, `{"error":"token_expired"}`, http.StatusUnauthorized)
return
}
if nbf, ok := claims["nbf"].(float64); !ok || time.Unix(int64(nbf), 0).After(time.Now()) {
http.Error(w, `{"error":"token_not_yet_valid"}`, http.StatusUnauthorized)
return
}
// Optional: ensure request arrives at expected hostname.
if r.Host != hostname {
http.Error(w, `{"error":"invalid_host"}`, http.StatusBadRequest)
return
}
ctx := context.WithValue(r.Context(), "claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func fetchKeyFromJWKS(jwksURL string, kid string) (interface{}, error) {
// In production, implement caching and HTTPS client with timeouts.
// This is a simplified example.
resp, err := http.Get(jwksURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var jwks struct {
Keys []struct {
Kid string `json:"kid"`
N string `json:"n"`
E string `json:"e"`
} `json:"keys"`
}
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
return nil, err
}
for _, key := range jwks.Keys {
if key.Kid == kid {
// Reconstruct RSA public key from modulus and exponent.
// Use a library like github.com/lestrrat-go/jwx/jwk for production.
return nil, fmt.Errorf("key reconstruction omitted for brevity")
}
}
return nil, fmt.Errorf("key not found")
}
func main() {
router := mux.NewRouter()
router.Use(jwtMiddleware)
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"ok"}`))
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("server listening on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, router))
}
Key points in this remediation:
- Validate JWT claims (issuer, audience, exp, nbf) on every request to prevent tokens accepted in a different security context.
- Enforce the expected hostname on the server side (r.Host) to reduce the impact of DNS redirection, acknowledging that Gorilla Mux relies on the underlying HTTP server’s Host header.
- Fetch signing keys over HTTPS from a JWKS endpoint with proper error handling and timeouts; do not trust DNS alone for discovering keys.
- Use strong signature verification and reject tokens with unexpected signing methods.
These practices reduce the risk that DNS cache poisoning leads to token acceptance by an unintended or malicious endpoint.