Dns Cache Poisoning in Fiber with Jwt Tokens
Dns Cache Poisoning in Fiber with Jwt Tokens — how this specific combination creates or exposes the vulnerability
DNS cache poisoning (also known as DNS spoofing) occurs when an attacker injects a fraudulent DNS response into a resolver’s cache, causing the resolver to return an attacker-controlled IP for a legitimate domain. In a Fiber-based API service that uses JWT tokens for authentication, this attack can intersect with the authentication flow in several concrete ways.
Consider a typical setup where your Fiber application validates JWT tokens by resolving a JSON Web Key Set (JWKS) endpoint hosted at a domain like auth.example.com. The application performs a DNS lookup to reach the JWKS endpoint, then verifies the token’s signature using the retrieved public key. If an attacker successfully poisons the local or upstream DNS cache for auth.example.com to point to a malicious server, the application may unknowingly fetch a malicious JWKS. Using that key, it can validate attacker-crafted tokens and escalate privileges by impersonating any user.
In a black-box scan, middleBrick tests unauthenticated endpoints and inspects how the service resolves dependencies and validates incoming tokens. A JWT validation step that relies on external DNS resolution without strict certificate or host pinning can expose a path where token integrity depends on DNS correctness. For example, if your application uses a standard HTTP client to fetch the JWKS, and that client follows redirects or uses a resolver that is vulnerable to cache poisoning, an attacker may bypass token validation indirectly. This does not require access to your application’s network; it targets the dependency chain that your API relies on.
Even when JWT tokens are verified locally using embedded public keys or static JWKS, a poisoned DNS entry can affect logging, telemetry, or token introspection endpoints that the application calls. If your token validation flow includes fetching revocation lists or calling an authorization server at a DNS-resolved hostname, cache poisoning can redirect those calls, leading to incorrect authorization decisions. middleBrick’s checks for Authentication, Input Validation, and SSRF can surface indirect dependencies that rely on external resolution, helping you identify where DNS trust assumptions intersect with JWT handling.
To illustrate, this is a valid JWT verification snippet in Fiber that fetches a JWKS over HTTPS:
// Example: Fiber JWT setup that fetches JWKS (vulnerable if DNS is poisoned)
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"net/http"
"io/ioutil"
)
func main() {
app := fiber.New()
app.Get("/protected", func(c *fiber.Ctx) error {
auth := c.Get("Authorization")
if auth == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "missing authorization header"})
}
// Fetch JWKS from external domain (DNS resolution involved)
resp, err := http.Get("https://auth.example.com/.well-known/jwks.json")
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to fetch jwks"})
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to read jwks"})
}
// Parse JWKS and validate token (simplified)
keyFunc := func(token *jwt.Token) (interface{}, error) {
// In a real implementation, you would select the correct key from the JWKS
return []byte("super-secret-key"), nil
}
_, err = jwt.Parse(string(data), keyFunc)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
}
return c.SendString("OK")
})
app.Listen(":3000")
}
In this example, if the DNS for auth.example.com is poisoned, the HTTP GET can be redirected to an attacker server serving a malicious JWKS, undermining the entire token validation. middleBrick’s scans highlight such external dependencies and the need to harden resolution paths.
Jwt Tokens-Specific Remediation in Fiber — concrete code fixes
Remediation focuses on reducing reliance on mutable external resolution at validation time and ensuring that any fetched keys are authenticated and integrity-checked. Below are concrete, Fiber-compatible code practices to mitigate risks that could be amplified by DNS cache poisoning when JWT tokens are involved.
1) Pin the JWKS host and use HTTPS with strict transport security. Ensure your HTTP client enforces HTTPS and does not follow unexpected redirects. This reduces opportunities for an attacker to intercept or redirect the JWKS fetch.
// Secure JWKS fetch with custom transport and no redirect following
package main
import (
"crypto/tls"
"net/http"
"time"
"github.com/gofiber/fiber/v2"
)
func secureHTTPClient() *http.Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
}
return &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // prevent following redirects
},
Timeout: 10 * time.Second,
}
}
func main() {
client := secureHTTPClient()
// Use client to fetch JWKS, ensuring host is fixed and TLS is strict
// ...
}
2) Cache and pin a trusted JWKS locally, and validate tokens against the pinned key material. Instead of fetching the JWKS on every request, retrieve it once at startup or via a controlled refresh, and use the embedded public key(s) for validation. This ensures that even if DNS is poisoned after startup, your verification does not depend on the compromised lookup.
// Example: Load JWKS at startup and use a specific key for verification
package main
import (
"context"
"crypto/rsa"
"encoding/json"
"net/http"
"github.com/golang-jwt/jwt/v5"
"github.com/fatih/structs"
)
type jwks struct {
Keys []struct {
Kid string
N string
E string
Alg string
Use string
}
}
func fetchJWKS(url string) (*jwt.Keyfunc, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var data jwks
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, err
}
// Pick a specific key by kid and convert to *rsa.PublicKey (simplified)
// In production, use proper JWK-to-key conversion and handle multiple keys
return &jwt.Keyfunc(func(token *jwt.Token) (interface{}, error) {
// Example: hard-code expected kid and use a pinned public key
return &rsa.PublicKey{N: big.NewInt(1), E: 65537}, nil
}), nil
}
func main() {
keyfunc, err := fetchJWKS("https://auth.example.com/.well-known/jwks.json")
if err != nil {
// handle error
}
app := fiber.New()
app.Get("/protected", func(c *fiber.Ctx) error {
auth := c.Get("Authorization")
token, err := jwt.Parse(auth, keyfunc.Keyfunc)
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
}
c.Locals("user", token.Claims)
return c.Next()
})
app.Listen(":3000")
}
3) Validate token issuers and audiences strictly. Even if a token is correctly signed with a key obtained via a poisoned DNS, strict validation of iss, aud, and exp can reduce the impact of a token issued by an attacker. Enforce expected issuer and audience values in your validation logic.
// Example: Enforce issuer and audience checks
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
)
func main() {
app := fiber.New()
app.Get("/protected", func(c *fiber.Ctx) error {
auth := c.Get("Authorization")
token, err := jwt.Parse(auth, func(token *jwt.Token) (interface{}, error) {
return []byte("your-256-bit-secret"), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
}
if claims, ok := token.Claims.(jwt.MapClaims); ok {
if claims["iss"] != "https://auth.example.com" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid issuer"})
}
if claims["aud"] != "your-api-audience" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid audience"})
}
}
return c.SendString("OK")
})
app.Listen(":3000")
}
By combining strict transport, reduced redirect behavior, key pinning, and rigorous claim validation, you reduce the risk that DNS cache poisoning can undermine your JWT-based authentication in Fiber.