diff --git a/commands/http/handler.go b/commands/http/handler.go index e82acd3e2..5af088554 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -45,6 +45,13 @@ const ( applicationJson = "application/json" applicationOctetStream = "application/octet-stream" plainText = "text/plain" + originHeader = "origin" +) + +const ( + ACAOrigin = "Access-Control-Allow-Origin" + ACAMethods = "Access-Control-Allow-Methods" + ACACredentials = "Access-Control-Allow-Credentials" ) var localhostOrigins = []string{ @@ -115,6 +122,13 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Debug("Incoming API request: ", r.URL) + if !allowOrigin(r, i.cfg) || !allowReferer(r, i.cfg) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("403 - Forbidden")) + log.Warningf("API blocked request to %s. (possible CSRF)", r.URL) + return + } + req, err := Parse(r, i.root) if err != nil { if err == ErrNotFound { @@ -310,3 +324,60 @@ func sanitizedErrStr(err error) string { s = strings.Split(s, "\r")[0] return s } + +// allowOrigin just stops the request if the origin is not allowed. +// the CORS middleware apparently does not do this for us... +func allowOrigin(r *http.Request, cfg *ServerConfig) bool { + origin := r.Header.Get("Origin") + + // curl, or ipfs shell, typing it in manually, or clicking link + // NOT in a browser. this opens up a hole. we should close it, + // but right now it would break things. TODO + if origin == "" { + return true + } + + for _, o := range cfg.CORSOpts.AllowedOrigins { + if o == "*" { // ok! you asked for it! + return true + } + + if o == origin { // allowed explicitly + return true + } + } + + return false +} + +// allowReferer this is here to prevent some CSRF attacks that +// the API would be vulnerable to. We check that the Referer +// is allowed by CORS Origin (origins and referrers here will +// work similarly in the normla uses of the API). +// See discussion at https://github.com/ipfs/go-ipfs/issues/1532 +func allowReferer(r *http.Request, cfg *ServerConfig) bool { + referer := r.Referer() + + // curl, or ipfs shell, typing it in manually, or clicking link + // NOT in a browser. this opens up a hole. we should close it, + // but right now it would break things. TODO + if referer == "" { + return true + } + + // check CORS ACAOs and pretend Referer works like an origin. + // this is valid for many (most?) sane uses of the API in + // other applications, and will have the desired effect. + for _, o := range cfg.CORSOpts.AllowedOrigins { + if o == "*" { // ok! you asked for it! + return true + } + + // referer is allowed explicitly + if o == referer { + return true + } + } + + return false +} diff --git a/core/corehttp/commands.go b/core/corehttp/commands.go index 97b9c2b4b..0a65fcc3a 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -42,13 +42,13 @@ func addCORSFromEnv(c *cmdsHttp.ServerConfig) { func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) { log.Info("Using API.HTTPHeaders:", nc.API.HTTPHeaders) - if acao := nc.API.HTTPHeaders["Access-Control-Allow-Origin"]; acao != nil { + if acao := nc.API.HTTPHeaders[cmdsHttp.ACAOrigin]; acao != nil { c.CORSOpts.AllowedOrigins = acao } - if acam := nc.API.HTTPHeaders["Access-Control-Allow-Methods"]; acam != nil { + if acam := nc.API.HTTPHeaders[cmdsHttp.ACAMethods]; acam != nil { c.CORSOpts.AllowedMethods = acam } - if acac := nc.API.HTTPHeaders["Access-Control-Allow-Credentials"]; acac != nil { + if acac := nc.API.HTTPHeaders[cmdsHttp.ACACredentials]; acac != nil { for _, v := range acac { c.CORSOpts.AllowCredentials = (strings.ToLower(v) == "true") }