From 98aa4669052b70abca709fcdcf16a7cc5ad28182 Mon Sep 17 00:00:00 2001 From: Ivan Valdes Date: Tue, 5 Dec 2023 10:59:25 -0800 Subject: [PATCH] server: disable redirects in peer communication Disable following redirects from peer HTTP communication on the client's side. Etcd server may run into SSRF (Server-side request forgery) when adding a new member. If users provide a malicious peer URL, the existing etcd members may be redirected to another unexpected internal URL when getting the new member's version. Signed-off-by: Ivan Valdes --- server/etcdserver/cluster_util.go | 13 ++++++++++++- server/etcdserver/corrupt.go | 7 ++++++- server/lease/leasehttp/http.go | 14 ++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/server/etcdserver/cluster_util.go b/server/etcdserver/cluster_util.go index 97b2eb8f3db..eace8f69afc 100644 --- a/server/etcdserver/cluster_util.go +++ b/server/etcdserver/cluster_util.go @@ -70,6 +70,9 @@ func getClusterFromRemotePeers(lg *zap.Logger, urls []string, timeout time.Durat cc := &http.Client{ Transport: rt, Timeout: timeout, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, } for _, u := range urls { addr := u + "/members" @@ -324,7 +327,12 @@ func getVersion(lg *zap.Logger, m *membership.Member, rt http.RoundTripper) (*ve } func promoteMemberHTTP(ctx context.Context, url string, id uint64, peerRt http.RoundTripper) ([]*membership.Member, error) { - cc := &http.Client{Transport: peerRt} + cc := &http.Client{ + Transport: peerRt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } // TODO: refactor member http handler code // cannot import etcdhttp, so manually construct url requestUrl := url + "/members/promote/" + fmt.Sprintf("%d", id) @@ -396,6 +404,9 @@ func getDowngradeEnabledFromRemotePeers(lg *zap.Logger, cl *membership.RaftClust func getDowngradeEnabled(lg *zap.Logger, m *membership.Member, rt http.RoundTripper) (bool, error) { cc := &http.Client{ Transport: rt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, } var ( err error diff --git a/server/etcdserver/corrupt.go b/server/etcdserver/corrupt.go index 6b465125a6f..852b844a863 100644 --- a/server/etcdserver/corrupt.go +++ b/server/etcdserver/corrupt.go @@ -390,7 +390,12 @@ func (s *EtcdServer) getPeerHashKVs(rev int64) []*peerHashKVResp { lg := s.Logger() - cc := &http.Client{Transport: s.peerRt} + cc := &http.Client{ + Transport: s.peerRt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } var resps []*peerHashKVResp for _, p := range peers { if len(p.eps) == 0 { diff --git a/server/lease/leasehttp/http.go b/server/lease/leasehttp/http.go index 4b0a60a9be6..1b9390d97fc 100644 --- a/server/lease/leasehttp/http.go +++ b/server/lease/leasehttp/http.go @@ -150,7 +150,12 @@ func RenewHTTP(ctx context.Context, id lease.LeaseID, url string, rt http.RoundT return -1, err } - cc := &http.Client{Transport: rt} + cc := &http.Client{ + Transport: rt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } req, err := http.NewRequest("POST", url, bytes.NewReader(lreq)) if err != nil { return -1, err @@ -210,7 +215,12 @@ func TimeToLiveHTTP(ctx context.Context, id lease.LeaseID, keys bool, url string req = req.WithContext(ctx) - cc := &http.Client{Transport: rt} + cc := &http.Client{ + Transport: rt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } var b []byte // buffer errc channel so that errc don't block inside the go routinue resp, err := cc.Do(req)