mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
cmd,node,rpc: add allowedHosts to prevent dns rebinding attacks
This commit is contained in:
parent
5d4267911a
commit
b0c2ac46ef
7 changed files with 74 additions and 8 deletions
|
|
@ -111,6 +111,7 @@ var (
|
|||
utils.VMEnableDebugFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.RPCAllowedHostsFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.FakePoWFlag,
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.RPCAllowedHostsFlag,
|
||||
utils.JSpathFlag,
|
||||
utils.ExecFlag,
|
||||
utils.PreloadJSFlag,
|
||||
|
|
|
|||
|
|
@ -383,6 +383,11 @@ var (
|
|||
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
|
||||
Value: "",
|
||||
}
|
||||
RPCAllowedHostsFlag = cli.StringFlag{
|
||||
Name: "rpcallowedhosts",
|
||||
Usage: "Comma separated list of hostnames from which to accept requests (server enforced)",
|
||||
Value: "localhost,127.0.0.1",
|
||||
}
|
||||
RPCApiFlag = cli.StringFlag{
|
||||
Name: "rpcapi",
|
||||
Usage: "API's offered over the HTTP-RPC interface",
|
||||
|
|
@ -676,6 +681,8 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
|||
if ctx.GlobalIsSet(RPCApiFlag.Name) {
|
||||
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name))
|
||||
}
|
||||
|
||||
cfg.HTTPHosts = splitAndTrim(ctx.GlobalString(RPCAllowedHostsFlag.Name))
|
||||
}
|
||||
|
||||
// setWS creates the WebSocket RPC listener interface string from the set
|
||||
|
|
|
|||
12
node/api.go
12
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) (bool, error) {
|
||||
func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, hosts *string) (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
|
|
@ -141,6 +141,14 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
|
|||
}
|
||||
}
|
||||
|
||||
allowedHosts := api.node.config.HTTPHosts
|
||||
if hosts != nil{
|
||||
allowedHosts = nil
|
||||
for _, host := range strings.Split(*host, ",") {
|
||||
allowedHosts = append(allowedHosts, strings.TrimSpace(host))
|
||||
}
|
||||
}
|
||||
|
||||
modules := api.node.httpWhitelist
|
||||
if apis != nil {
|
||||
modules = nil
|
||||
|
|
@ -149,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); err != nil {
|
||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedHosts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
|
|
|
|||
|
|
@ -105,6 +105,14 @@ type Config struct {
|
|||
// useless for custom HTTP clients.
|
||||
HTTPCors []string `toml:",omitempty"`
|
||||
|
||||
// HTTPHosts is the list of hosts which are allowed on incoming requests.
|
||||
// This is by default {'localhost','127.0.0.1'}. 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.
|
||||
HTTPHosts []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
|
||||
// exposed.
|
||||
|
|
|
|||
|
|
@ -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); err != nil {
|
||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPHosts); 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) error {
|
||||
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, allowedHosts []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, handler).Serve(listener)
|
||||
go rpc.NewHTTPServer(cors, allowedHosts, handler).Serve(listener)
|
||||
n.log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
|
||||
|
||||
n.log.Info(fmt.Sprintf("HTTP config: cors %v, hosts%v", cors,allowedHosts))
|
||||
// All listeners booted successfully
|
||||
n.httpEndpoint = endpoint
|
||||
n.httpListener = listener
|
||||
|
|
|
|||
45
rpc/http.go
45
rpc/http.go
|
|
@ -31,6 +31,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/rs/cors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -142,8 +143,11 @@ 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, srv *Server) *http.Server {
|
||||
return &http.Server{Handler: newCorsHandler(srv, cors)}
|
||||
func NewHTTPServer(cors []string, hosts[] string, srv *Server) *http.Server {
|
||||
// Wrap the CORS-handler within a host-handler
|
||||
handler := newCorsHandler(srv, cors)
|
||||
handler = newHostHandler(hosts, handler)
|
||||
return &http.Server{Handler: handler}
|
||||
}
|
||||
|
||||
// ServeHTTP serves JSON-RPC requests over HTTP.
|
||||
|
|
@ -166,6 +170,10 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
srv.ServeSingleRequest(codec, OptionMethodInvocation)
|
||||
}
|
||||
|
||||
func parseHost(hostport string) string {
|
||||
return strings.ToLower(strings.Split(hostport, ":")[0])
|
||||
}
|
||||
|
||||
// validateRequest returns a non-zero response code and error message if the
|
||||
// request is invalid.
|
||||
func validateRequest(r *http.Request) (int, error) {
|
||||
|
|
@ -195,6 +203,39 @@ func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {
|
|||
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,
|
||||
// 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
|
||||
}
|
||||
|
||||
// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
|
||||
func (h *hostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Host != "" {
|
||||
hostpart := parseHost(r.Host)
|
||||
requestAllowed := false
|
||||
for _, allowedHost := range h.AllowedHosts {
|
||||
if strings.ToLower(allowedHost) == hostpart {
|
||||
requestAllowed = true
|
||||
}
|
||||
}
|
||||
if !requestAllowed {
|
||||
http.Error(w, "invalid host specified", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
h.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func newHostHandler(allowedHosts []string, next http.Handler) http.Handler {
|
||||
return &hostHandler{allowedHosts, next}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue