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
+}