HIGH replay attackchimutual tls

Replay Attack in Chi with Mutual Tls

Replay Attack in Chi with Mutual Tls — how this specific combination creates or exposes the vulnerability

A replay attack in the context of Chi with Mutual TLS occurs when an adversary captures a valid, cryptographically authenticated request—complete with client certificates established during the TLS handshake—and retransmits it to the service to produce the same effect without possessing the client’s private key. Mutual TLS authenticates the client via a certificate presented during the handshake, but it does not inherently prevent the replay of that handshake or the associated application-level request. If the application layer does not enforce uniqueness constraints, the service may treat the replayed request as legitimate because the certificate validation passes and the TLS session appears valid.

Chi is a minimal HTTP router and middleware framework for Go that does not enforce replay protection by default. When you use Mutual TLS with Chi, the TLS layer verifies the client certificate and establishes a secure channel, yet the handler receives the request exactly as transmitted. Without additional mechanisms, an attacker who observes a request (e.g., via network interception or compromised logs) can replay the same HTTP method, path, headers, and body to trigger actions such as financial transactions, state changes, or administrative operations. This is particularly relevant for idempotent endpoints or those that rely solely on TLS client auth for identity, where the assumption is that request authenticity equals request safety.

Real-world examples include payment APIs or configuration update endpoints that accept POST requests with side effects. If these endpoints lack nonce, timestamp, or idempotency-key validation, a captured request can be replayed within the certificate’s validity window. The PCI-DSS and OWASP API Security Top 10 highlight such risks under insufficient anti-replay controls and weak authentication enforcement. middleBrick scans for these patterns by correlating unauthenticated attack surface testing with OpenAPI/Swagger specs and runtime behavior, flagging endpoints that lack replay protection despite using Mutual TLS.

Mutual Tls-Specific Remediation in Chi — concrete code fixes

Remediation focuses on ensuring each request is unique and validating contextual properties beyond the TLS handshake. Combine Mutual TLS with application-level protections such as one-time nonces, timestamps, or idempotency keys. The server must verify freshness and uniqueness before processing side-effectful operations.

Mutual TLS setup in Chi (server and client)

Use the following Go examples to establish Mutual TLS with Chi. The server requires a CA to validate client certificates; the client presents a certificate signed by that CA.

// server.go
package main

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	// Load server cert and key
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		panic(err)
	}

	// Load CA cert to verify clients
	caCert, err := ioutil.ReadFile("ca.crt")
	if err != nil {
		panic(err)
	}
	caPool := x509.NewCertPool()
	caPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientCAs:    caPool,
		ClientAuth:   tls.RequireAndVerifyClientCert,
		MinVersion:   tls.VersionTLS12,
	}

	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Post("/transfer", func(w http.ResponseWriter, r *http.Request) {
		// TODO: add replay protection here
		w.Write([]byte("ok"))
	})

	srv := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsConfig,
		Handler:   r,
	}
	srv.ListenAndServeTLS("", "")
}
// client.go
package main

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net/http"
)

func main() {
	// Load client cert and key
	cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
	if err != nil {
		panic(err)
	}

	// Load server CA to verify server cert
	caCert, err := ioutil.ReadFile("ca.crt")
	if err != nil {
		panic(err)
	}
	caPool := x509.NewCertPool()
	caPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		RootCAs:            caPool,
		InsecureSkipVerify: false,
	}
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
	}

	req, _ := http.NewRequest("POST", "https://localhost:8443/transfer", nil)
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
}

Adding replay protection

Include a timestamp and a nonce or an idempotency key in headers, and validate them server-side. The server should maintain a short-term cache of seen nonce-timestamp pairs per client certificate to reject duplicates.

import (
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"sort"
	"strconv"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

var seen = make(map[string]bool) // in practice use a bounded cache with TTL

func isReplay(ts int64, nonce string) bool {
	// enforce a 5-minute window
	if time.Now().Unix()-ts > 300 {
		return true
	}
	key := nonce + strconv.FormatInt(ts, 10)
	// deterministic check to avoid race in examples; use sync.Mutex or sync.Map in production
	if seen[key] {
		return true
	}
	seen[key] = true
	return false
}

func replayMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tsStr := r.Header.Get("X-Request-Timestamp")
		nonce := r.Header.Get("X-Nonce")
		if tsStr == "" || nonce == "" {
			http.Error(w, "missing replay protection headers", http.StatusBadRequest)
			return
		}
		ts, err := strconv.ParseInt(tsStr, 10, 64)
		if err != nil {
			http.Error(w, "invalid timestamp", http.StatusBadRequest)
			return
		}
		if isReplay(ts, nonce) {
			http.Error(w, "replay detected", http.StatusForbidden)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	// (previous TLS setup omitted for brevity)
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(replayMiddleware)
	r.Post("/transfer", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("ok"))
	})
	http.ListenAndServeTLS(":8443", "server.crt", "server.key", r)
}

Additionally, prefer idempotency keys for POST/PUT operations and enforce uniqueness server-side. For clients that cannot rotate nonces easily, consider per-session or per-token replay windows and bind requests to the TLS client certificate fingerprint to tighten correlation. middleBrick’s scans can validate that required replay headers are present and that endpoints using Mutual TLS include such controls.

Frequently Asked Questions

Does Mutual TLS alone prevent replay attacks in Chi?
No. Mutual TLS authenticates the client and encrypts the channel, but it does not guarantee request uniqueness. You must add application-level protections such as timestamps, nonces, or idempotency keys to prevent replay.
How can middleBrick help detect replay vulnerabilities in Chi endpoints using Mutual TLS?
middleBrick scans the unauthenticated attack surface and OpenAPI spec to identify endpoints that rely solely on Mutual TLS without replay-resistant headers or idempotency controls, providing prioritized findings and remediation guidance.