CVE-2019-20933: Improper Authentication (CWE-287)
value: InfluxDB before 1.7.6 has an authentication bypass vulnerability in the authenticate function in services/httpd/handler.go because a JWT token may have an empty SharedSecret (aka shared secret).
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 9.8
Published: 2020-11-19T02:15Z
Repository: https://github.com/influxdata/influxdb
Language: Go
Files changed: services/httpd/handler.go, services/httpd/handler_test.go
Commit date: 2019-04-03 15:41:11 -0400
Commit message: fix(httpd): fail bearerauth if shared secret blank

Diff: diff --git a/services/httpd/handler.go b/services/httpd/handler.go
index e3a4508c4..8968a7586 100644
--- a/services/httpd/handler.go
+++ b/services/httpd/handler.go
@@ -1579,10 +1579,15 @@ func authenticate(inner func(http.ResponseWriter, *http.Request, meta.User), h *
 					atomic.AddInt64(&h.stats.AuthenticationFailures, 1)
 					h.httpError(w, "authorization failed", http.StatusUnauthorized)
 					return
 				}
 			case BearerAuthentication:
+				if h.Config.SharedSecret == "" {
+					atomic.AddInt64(&h.stats.AuthenticationFailures, 1)
+					h.httpError(w, "bearer auth disabled", http.StatusUnauthorized)
+					return
+				}
 				keyLookupFn := func(token *jwt.Token) (interface{}, error) {
 					// Check for expected signing method.
 					if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
 						return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
 					}
diff --git a/services/httpd/handler_test.go b/services/httpd/handler_test.go
index df408c004..16471e508 100644
--- a/services/httpd/handler_test.go
+++ b/services/httpd/handler_test.go
@@ -230,10 +230,28 @@ func TestHandler_Query_Auth(t *testing.T) {
 		t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String())
 	} else if body := strings.TrimSpace(w.Body.String()); body != `{"error":"token expiration required"}` {
 		t.Fatalf("unexpected body: %s", body)
 	}
 
+	// Test that auth fails if shared secret is blank.
+	origSecret := h.Config.SharedSecret
+	h.Config.SharedSecret = ""
+	token, _ = MustJWTToken("user1", h.Config.SharedSecret, false)
+	signedToken, err = token.SignedString([]byte(h.Config.SharedSecret))
+	if err != nil {
+		t.Fatal(err)
+	}
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken))
+	w = httptest.NewRecorder()
+	h.ServeHTTP(w, req)
+	if w.Code != http.StatusUnauthorized {
+		t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String())
+	} else if body := strings.TrimSpace(w.Body.String()); body != `{"error":"bearer auth disabled"}` {
+		t.Fatalf("unexpected body: %s", body)
+	}
+	h.Config.SharedSecret = origSecret
+
 	// Test the handler with valid user and password in the url and invalid in
 	// basic auth (prioritize url).
 	w = httptest.NewRecorder()
 	r = MustNewJSONRequest("GET", "/query?u=user1&p=abcd&db=foo&q=SELECT+*+FROM+bar", nil)
 	r.SetBasicAuth("user1", "efgh")

Fixed code: 				if h.Config.SharedSecret == "" {
					atomic.AddInt64(&h.stats.AuthenticationFailures, 1)
					h.httpError(w, "bearer auth disabled", http.StatusUnauthorized)
					return
				}
	// Test that auth fails if shared secret is blank.
	origSecret := h.Config.SharedSecret
	h.Config.SharedSecret = ""
	token, _ = MustJWTToken("user1", h.Config.SharedSecret, false)
	signedToken, err = token.SignedString([]byte(h.Config.SharedSecret))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken))
	w = httptest.NewRecorder()
	h.ServeHTTP(w, req)
	if w.Code != http.StatusUnauthorized {
		t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String())
	} else if body := strings.TrimSpace(w.Body.String()); body != `{"error":"bearer auth disabled"}` {
		t.Fatalf("unexpected body: %s", body)
	}
	h.Config.SharedSecret = origSecret