@marcel91200 / caddy Mirror · read-only / commits / 75c988d1
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 08:30 UTC

reverseproxy: compare sticky-session cookie hash in constant time (#7853)

alhuda committed Jul 10, 2026 at 04:46 UTC 75c988d118a057b7267cfcd455c8569996f40745
2 files changed +44 -1
modules/caddyhttp/reverseproxy/selectionpolicies.go
+1 -1
@@ -712,7 +712,7 @@ func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http
712 continue
713 }
714 sha, err := hashCookie(s.Secret, upstream.Dial)
715 - if err == nil && sha == cookieValue {
715 + if err == nil && hmac.Equal([]byte(sha), []byte(cookieValue)) {
716 return upstream
717 }
718 }
modules/caddyhttp/reverseproxy/selectionpolicies_test.go
+43
@@ -890,3 +890,46 @@ func TestCookieHashPolicyWithFirstFallback(t *testing.T) {
890 t.Error("Expected cookieHashPolicy to set a new cookie.")
891 }
892 }
893 +
894 +func TestCookieHashPolicyWithSecret(t *testing.T) {
895 + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
896 + defer cancel()
897 + cookieHashPolicy := CookieHashSelection{Secret: "hunter2"}
898 + if err := cookieHashPolicy.Provision(ctx); err != nil {
899 + t.Errorf("Provision error: %v", err)
900 + t.FailNow()
901 + }
902 +
903 + pool := testPool()
904 + pool[0].Dial = "localhost:8080"
905 + pool[1].Dial = "localhost:8081"
906 + pool[2].Dial = "localhost:8082"
907 + pool[0].setHealthy(true)
908 + pool[1].setHealthy(true)
909 + pool[2].setHealthy(true)
910 +
911 + request := httptest.NewRequest(http.MethodGet, "/test", nil)
912 + w := httptest.NewRecorder()
913 + h := cookieHashPolicy.Select(pool, request, w)
914 + cookie := w.Result().Cookies()[0]
915 +
916 + // a matching cookie sticks to the same host
917 + request = httptest.NewRequest(http.MethodGet, "/test", nil)
918 + w = httptest.NewRecorder()
919 + request.AddCookie(cookie)
920 + if got := cookieHashPolicy.Select(pool, request, w); got != h {
921 + t.Errorf("Expected to stick to host %s, got %s", h, got)
922 + }
923 + if len(w.Result().Cookies()) != 0 {
924 + t.Error("Expected no new cookie for a matching value")
925 + }
926 +
927 + // a tampered cookie value must not match any host and gets a fresh cookie
928 + request = httptest.NewRequest(http.MethodGet, "/test", nil)
929 + w = httptest.NewRecorder()
930 + request.AddCookie(&http.Cookie{Name: cookie.Name, Value: cookie.Value[:len(cookie.Value)-1] + "0"})
931 + cookieHashPolicy.Select(pool, request, w)
932 + if len(w.Result().Cookies()) == 0 {
933 + t.Error("Expected a new cookie to be set for a non-matching value")
934 + }
935 +}