Cache Poisoning in Chi with Mutual Tls
Cache Poisoning in Chi with Mutual Tls — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached responses so that subsequent users receive malicious or incorrect data. In Chi, a lightweight HTTP router for Go, enabling mutual TLS (mTLS) ensures both client and server present valid certificates, which protects transport integrity but does not inherently prevent cache poisoning introduced at the application layer.
When mTLS is enforced, Chi verifies client certificates on every request. If the application uses request attributes that are not validated—such as user-supplied headers, cookies, or URL parameters—to construct cache keys, an authenticated client may inject values that cause distinct responses to be cached under the same key. For example, a header like X-User-Role might be trusted because the request is mTLS-authenticated, but if the application uses that header to decide response content without normalizing or excluding it from the cache key, different users’ data can be served from the same cached entry.
Chi does not automatically define cache keys; developers implement caching via middleware or custom handlers. If cache keys are derived from mutable or attacker-influenced inputs, mTLS alone does not stop an authenticated client from probing endpoints and learning how to poison the cache. Additionally, if responses include sensitive or personalized data but are cached based solely on the request path, subsequent users may receive another user’s data, violating confidentiality and integrity expectations.
Real-world attack patterns include tampering with non-unique identifiers or leveraging weak normalization. For instance, an endpoint /api/products that appends a query parameter ?variant=default but caches based on the full URL might store a response for one variant and serve it to others. Even with mTLS, if the server trusts a header like Accept-Language for caching decisions, an attacker can force cache entries that bypass intended routing logic.
Compliance frameworks such as OWASP API Top 10 (2023) categorize cache poisoning as a distinct issue, often intersecting with Broken Object Level Authorization (BOLA) when authorization decisions are cached incorrectly. Proper remediation requires canonicalizing inputs, excluding sensitive headers from cache key derivation, and validating that cached responses are scoped to the correct audience, regardless of transport-level authentication.
Mutual Tls-Specific Remediation in Chi — concrete code fixes
Remediation focuses on ensuring that cache keys are deterministic, normalized, and independent of attacker-influenced values—even when mTLS is enforced. In Chi, this means carefully designing middleware or caching logic to strip or hash headers that should not affect the cache key, and ensuring that per-user data is not stored in shared caches.
Example: a secure caching middleware for Chi that excludes sensitive headers and normalizes the request path:
import (
"net/http"
"strings"
"crypto/sha256"
"encoding/hex"
)
// SecureCacheOptions defines headers to exclude from cache key generation.
type SecureCacheOptions struct {
ExcludeHeaders []string
// SecureCache middleware builds a cache key without sensitive or user-specific headers.
func SecureCache(next http.Handler, opts SecureCacheOptions) http.Handler {
exclude := make(map[string]struct{})
for _, h := range opts.ExcludeHeaders {
exclude[strings.ToLower(h)] = struct{}{}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Build a canonical key from method + host + path with normalized query.
key := r.Method + ":" + r.Host + ":" + r.URL.Path
if r.URL.RawQuery != "" {
// Normalize query parameters: sort and exclude sensitive ones.
// (Implement a stable sorting function for params here.)
key += "?" + normalizeQuery(r.URL.Query(), exclude)
}
// Optionally hash the key for fixed length.
hash := sha256.Sum256([]byte(key))
cacheKey := hex.EncodeToString(hash[:])
// Lookup or store cache entry using cacheKey.
// ...
next.ServeHTTP(w, r)
})
}
func normalizeQuery(query url.Values, exclude map[string]struct{}) string {
keys := make([]string, 0, len(query))
for k := range query {
if _, skip := exclude[strings.ToLower(k)]; !skip {
keys = append(keys, k)
}
}
sort.Strings(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 {
b.WriteByte('&')
}
b.WriteString(k)
b.WriteByte('=')
b.WriteString(query.Get(k))
}
return b.String()
}
Mutual Tls configuration examples for Chi
These examples show how to configure Chi with mTLS using Go’s tls.Config and request verification, while ensuring cache-safe practices.
1) Server with client certificate verification:
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
// Load CA pool that signs allowed client certificates.
caCert, err := ioutil.ReadFile("path/to/ca.crt")
if err != nil {
panic(err)
}
caPool := x509.NewCertPool()
if ok := caPool.AppendCertsFromPEM(caCert); !ok {
panic("failed to add CA cert")
}
tlsConfig := &tls.Config{
ClientCAs: caPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
}
r := chi.NewRouter()
// Attach secure caching or other middleware here.
r.Get("/api/data", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("protected response"))
})
if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil {
panic(err)
}
}
Client request with mTLS
Clients must present a certificate signed by the trusted CA. Example using http.Client:
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
)
func newMTLSClient() (*http.Client, error) {
caCert, err := ioutil.ReadFile("path/to/ca.crt")
if err != nil {
return nil, err
}
caPool := x509.NewCertPool()
if ok := caPool.AppendCertsFromPEM(caCert); !ok {
return nil, err
}
cliCert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
return nil, err
}
RootCAs: caPool,
Certificates: []tls.Certificate{cliCert},
}
return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}, nil
}
// Usage:
// client, err := newMTLSClient()
// resp, err := client.Get("https://api.example.com/api/data")