From 838cd9aa00aed3bc06261b2a856cb6d51a3771d0 Mon Sep 17 00:00:00 2001 From: Ivan Valdes Date: Wed, 13 Dec 2023 09:21:53 -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 --- etcdserver/cluster_util.go | 13 ++++++++++++- etcdserver/corrupt.go | 7 ++++++- lease/leasehttp/http.go | 14 ++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/etcdserver/cluster_util.go b/etcdserver/cluster_util.go index f92706cb7a1..0b02781b8d7 100644 --- a/etcdserver/cluster_util.go +++ b/etcdserver/cluster_util.go @@ -66,6 +66,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" @@ -301,6 +304,9 @@ func isCompatibleWithVers(lg *zap.Logger, vers map[string]*version.Versions, loc func getVersion(lg *zap.Logger, m *membership.Member, rt http.RoundTripper) (*version.Versions, error) { cc := &http.Client{ Transport: rt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, } var ( err error @@ -359,7 +365,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) diff --git a/etcdserver/corrupt.go b/etcdserver/corrupt.go index a7ce2ae88e4..9cbfd967891 100644 --- a/etcdserver/corrupt.go +++ b/etcdserver/corrupt.go @@ -497,7 +497,12 @@ func (h *hashKVHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // getPeerHashKVHTTP fetch hash of kv store at the given rev via http call to the given url func (s *EtcdServer) getPeerHashKVHTTP(ctx context.Context, cid types.ID, url string, rev int64) (*pb.HashKVResponse, error) { - cc := &http.Client{Transport: s.peerRt} + cc := &http.Client{ + Transport: s.peerRt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } hashReq := &pb.HashKVRequest{Revision: rev} hashReqBytes, err := json.Marshal(hashReq) if err != nil { diff --git a/lease/leasehttp/http.go b/lease/leasehttp/http.go index 67e916dba9e..e2f5b1ce1d6 100644 --- a/lease/leasehttp/http.go +++ b/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)