From dbf5bb1f4c1bca7186468a7650256db7498c3910 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 5 Feb 2018 15:52:56 +0100 Subject: [PATCH] http, rpc, utils: make vhosts into map, address review concerns --- cmd/utils/flags.go | 4 ++-- node/api.go | 14 +++++++------- node/config.go | 4 ++-- node/node.go | 8 ++++---- rpc/http.go | 39 +++++++++++++++++++++------------------ 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b13d203b59..ef896f64b6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -385,7 +385,7 @@ var ( } RPCVirtualHostsFlag = cli.StringFlag{ Name: "rpcvhosts", - Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Set to * to disable this protection.", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", Value: "localhost", } RPCApiFlag = cli.StringFlag{ @@ -682,7 +682,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name)) } - cfg.HTTPVirtualHostnames = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name)) + cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name)) } // setWS creates the WebSocket RPC listener interface string from the set diff --git a/node/api.go b/node/api.go index 5f077bb401..4e9b1edc47 100644 --- a/node/api.go +++ b/node/api.go @@ -114,7 +114,7 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, } // StartRPC starts the HTTP RPC API server. -func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, hosts *string) (bool, error) { +func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -141,11 +141,11 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - allowedHosts := api.node.config.HTTPVirtualHostnames - if hosts != nil { - allowedHosts = nil - for _, host := range strings.Split(*host, ",") { - allowedHosts = append(allowedHosts, strings.TrimSpace(host)) + allowedVHosts := api.node.config.HTTPVirtualHosts + if vhosts != nil { + allowedVHosts = nil + for _, vhost := range strings.Split(*host, ",") { + allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) } } @@ -157,7 +157,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedHosts); err != nil { + if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts); err != nil { return false, err } return true, nil diff --git a/node/config.go b/node/config.go index 5eba651502..dda24583ee 100644 --- a/node/config.go +++ b/node/config.go @@ -105,14 +105,14 @@ type Config struct { // useless for custom HTTP clients. HTTPCors []string `toml:",omitempty"` - // HTTPVirtualHostnames is the list of virtual hostnames which are allowed on incoming requests. + // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. // This is by default {'localhost'}. Using this prevents attacks like // DNS rebinding, which bypasses SOP by simply masquerading as being within the same // origin. These attacks do not utilize CORS, since they are not cross-domain. // By explicitly checking the Host-header, the server will not allow requests // made against the server with a malicious host domain. // Requests using ip address directly are not affected - HTTPVirtualHostnames []string `toml:",omitempty"` + HTTPVirtualHosts []string `toml:",omitempty"` // HTTPModules is a list of API modules to expose via the HTTP RPC interface. // If the module list is empty, all RPC API endpoints designated public will be diff --git a/node/node.go b/node/node.go index e893356bcd..98269a92b6 100644 --- a/node/node.go +++ b/node/node.go @@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() return err } - if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHostnames); err != nil { + if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil { n.stopIPC() n.stopInProc() return err @@ -365,7 +365,7 @@ func (n *Node) stopIPC() { } // startHTTP initializes and starts the HTTP RPC endpoint. -func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, allowedHosts []string) error { +func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string) error { // Short circuit if the HTTP endpoint isn't being exposed if endpoint == "" { return nil @@ -393,9 +393,9 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors if listener, err = net.Listen("tcp", endpoint); err != nil { return err } - go rpc.NewHTTPServer(cors, allowedHosts, handler).Serve(listener) + go rpc.NewHTTPServer(cors, vhosts, handler).Serve(listener) n.log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint)) - n.log.Info(fmt.Sprintf("HTTP config: cors %v, vhosts%v", cors, allowedHosts)) + n.log.Info(fmt.Sprintf("HTTP config: cors %v, vhosts%v", cors, vhosts)) // All listeners booted successfully n.httpEndpoint = endpoint n.httpListener = listener diff --git a/rpc/http.go b/rpc/http.go index a3fbb8b7a5..2c8360e494 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -143,10 +143,10 @@ func (t *httpReadWriteNopCloser) Close() error { // NewHTTPServer creates a new HTTP RPC server around an API provider. // // Deprecated: Server implements http.Handler -func NewHTTPServer(cors []string, hosts []string, srv *Server) *http.Server { +func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server { // Wrap the CORS-handler within a host-handler handler := newCorsHandler(srv, cors) - handler = newHostHandler(hosts, handler) + handler = newVHostHandler(vhosts, handler) return &http.Server{Handler: handler} } @@ -193,29 +193,26 @@ func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { if len(allowedOrigins) == 0 { return srv } - c := cors.New(cors.Options{ AllowedOrigins: allowedOrigins, AllowedMethods: []string{http.MethodPost, http.MethodGet}, MaxAge: 600, AllowedHeaders: []string{"*"}, - Debug: true, }) return c.Handler(srv) } -// hostHandler is a handler which validates the Host-header of incoming requests. -// The hostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, +// virtalHostHandler is a handler which validates the Host-header of incoming requests. +// The virtalHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, // since they do in-domain requests against the RPC api. Instead, we can see on the Host-header // which domain was used, and validate that against a whitelist. -type hostHandler struct { - AllowedHosts []string - next http.Handler +type virtalHostHandler struct { + vhosts map[string]struct{} + next http.Handler } // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler -func (h *hostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - +func (h *virtalHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // if r.Host is not set, we can continue serving since a browser would set the Host header if r.Host == "" { h.next.ServeHTTP(w, r) @@ -233,16 +230,22 @@ func (h *hostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Not an ip address, but a hostname. Need to validate - for _, allowedHost := range h.AllowedHosts { - if strings.ToLower(allowedHost) == strings.ToLower(host) || allowedHost == "*" { - h.next.ServeHTTP(w, r) - return - } + if _, exist := h.vhosts["*"]; exist { + h.next.ServeHTTP(w, r) + return + } + if _, exist := h.vhosts[host]; exist { + h.next.ServeHTTP(w, r) + return } http.Error(w, "invalid host specified", http.StatusForbidden) return } -func newHostHandler(allowedHosts []string, next http.Handler) http.Handler { - return &hostHandler{allowedHosts, next} +func newVHostHandler(vhosts []string, next http.Handler) http.Handler { + vhostMap := make(map[string]struct{}) + for _, allowedHost := range vhosts { + vhostMap[strings.ToLower(allowedHost)] = struct{}{} + } + return &virtalHostHandler{vhostMap, next} }