From 8578e0711799bad50be4eed4cfe86254872d3203 Mon Sep 17 00:00:00 2001 From: Ivan Valdes Date: Wed, 29 Nov 2023 10:04:32 -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 069d1af8c8d..1138aef5b8b 100644 --- a/server/etcdserver/cluster_util.go +++ b/server/etcdserver/cluster_util.go @@ -72,6 +72,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" @@ -289,7 +292,12 @@ func getVersion(lg *zap.Logger, m *membership.Member, rt http.RoundTripper, time } 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) @@ -362,6 +370,9 @@ func getDowngradeEnabled(lg *zap.Logger, m *membership.Member, rt http.RoundTrip cc := &http.Client{ Transport: rt, Timeout: timeout, + 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 50eafdf6133..65141f05fde 100644 --- a/server/etcdserver/corrupt.go +++ b/server/etcdserver/corrupt.go @@ -467,7 +467,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 30caf22e783..7c9f56bde5c 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(http.MethodPost, 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)