Commit cd51b41f authored by Hector Sanjuan's avatar Hector Sanjuan

Fix: disallow POST without Origin nor Referer from specific user agents

Addresses browsers being able to POST without control due to things like
https://bugzilla.mozilla.org/show_bug.cgi?id=429594
parent f2a4f7be
......@@ -3,6 +3,7 @@ package http
import (
"net/http"
"net/url"
"strings"
"sync"
cors "github.com/rs/cors"
......@@ -14,6 +15,19 @@ const (
ACACredentials = "Access-Control-Allow-Credentials"
)
// disallowedUserAgents specifies a denylist 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-related things. The matching against the user-agent string
// is made with strings.Contains().
var disallowedUserAgents = []string{
"Firefox",
"Focus",
"Klar",
"FxiOS",
}
type ServerConfig struct {
// APIPath is the prefix of all request paths.
// Example: host:port/api/v0/add. Here the APIPath is /api/v0
......@@ -30,6 +44,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
......@@ -150,3 +172,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 disallowedUserAgents {
if strings.Contains(ua, forbiddenUA) {
return false
}
}
return true
}
......@@ -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)
}
}
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment