Skip to content

Commit

Permalink
Fix: disallow POST without Origin nor Referer from specific user agents
Browse files Browse the repository at this point in the history
Addresses browsers being able to POST without control due to things like
https://bugzilla.mozilla.org/show_bug.cgi?id=429594
  • Loading branch information
hsanjuan committed Apr 6, 2020
1 parent f2a4f7b commit 3042fc5
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
41 changes: 41 additions & 0 deletions http/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package http
import (
"net/http"
"net/url"
"strings"
"sync"

cors "github.com/rs/cors"
Expand All @@ -14,6 +15,9 @@ const (
ACACredentials = "Access-Control-Allow-Credentials"
)

// defaultDisallowAgents is the default setting for ServerConfig.DisallowAgents.
var defaultDisallowUserAgents = []string{"Firefox"}

type ServerConfig struct {
// APIPath is the prefix of all request paths.
// Example: host:port/api/v0/add. Here the APIPath is /api/v0
Expand All @@ -30,6 +34,14 @@ type ServerConfig struct {
// websites to include resources from the API but not _read_ them.
AllowGet bool

// DisallowUserAgents specifies a blacklist of user agents that are not
// allowed to perform POST requests if they are not providing Origin
// and/or Referer headers. As mitigation for things like
// https://bugzilla.mozilla.org/show_bug.cgi?id=429594.
// Defaults to ["Firefox"]. The matching against the user-agent
// string is made with strings.Contains().
DisallowUserAgents []string

// corsOpts is a set of options for CORS headers.
corsOpts *cors.Options

Expand All @@ -40,6 +52,7 @@ type ServerConfig struct {
func NewServerConfig() *ServerConfig {
cfg := new(ServerConfig)
cfg.corsOpts = new(cors.Options)
cfg.DisallowUserAgents = defaultDisallowUserAgents
return cfg
}

Expand Down Expand Up @@ -150,3 +163,31 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool {

return false
}

// allowUserAgent checks the request's user-agent against the list
// of DisallowUserAgents for requests with no origin nor referer set.
func allowUserAgent(r *http.Request, cfg *ServerConfig) bool {
// This check affects POST as we should never get POST requests from a
// browser without Origin or Referer, but we might:
// https://bugzilla.mozilla.org/show_bug.cgi?id=429594.
if r.Method != http.MethodPost {
return true
}

origin := r.Header.Get("Origin")
referer := r.Referer()

// If these are set, we leave up to CORS and CSRF checks.
if origin != "" || referer != "" {
return true
}

// If not, check that request is not from a blacklisted UA.
ua := r.Header.Get("User-agent")
for _, forbiddenUA := range cfg.DisallowUserAgents {
if strings.Contains(ua, forbiddenUA) {
return false
}
}
return true
}
36 changes: 36 additions & 0 deletions http/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,39 @@ func TestUnhandledMethod(t *testing.T) {
}
tc.test(t)
}

func TestDisallowedUserAgents(t *testing.T) {
tcs := []httpTestCase{
{
// Block Firefox
Method: "POST",
AllowGet: false,
Code: http.StatusForbidden,
ReqHeaders: map[string]string{
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0",
},
},
{
// Do not block on GETs
Method: "GET",
AllowGet: true,
Code: http.StatusOK,
ReqHeaders: map[string]string{
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0",
},
},
{
// Do not block Chrome
Method: "POST",
AllowGet: false,
Code: http.StatusOK,
ReqHeaders: map[string]string{
"User-Agent": "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
},
},
}

for _, tc := range tcs {
tc.test(t)
}
}
2 changes: 1 addition & 1 deletion http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if !allowOrigin(r, h.cfg) || !allowReferer(r, h.cfg) {
if !allowOrigin(r, h.cfg) || !allowReferer(r, h.cfg) || !allowUserAgent(r, h.cfg) {
http.Error(w, "403 - Forbidden", http.StatusForbidden)
log.Warnf("API blocked request to %s. (possible CSRF)", r.URL)
return
Expand Down

0 comments on commit 3042fc5

Please sign in to comment.