From cd8a281676dc88ea7784a96e70957a6518f8138d Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Tue, 28 Jul 2015 22:57:11 -0700 Subject: [PATCH] fix API handler to respect referer + exit on CORS this commit makes the API handler short circuit the request if the CORS headers say its not allowed. (the CORS handler only sets the headers, but does not short-circuit) It also makes the handler respect the referer again. See security discussion at https://github.com/ipfs/go-ipfs/issues/1532 License: MIT Signed-off-by: Juan Batiz-Benet --- commands/http/handler.go | 63 +++++++++++++++++++++++++++++++++++++++ core/corehttp/commands.go | 6 ++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/commands/http/handler.go b/commands/http/handler.go index 71c25cf2961a..66793bc96ac1 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 { @@ -311,3 +325,52 @@ 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") + 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 97b9c2b4be55..0a65fcc3a72b 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") }