CVE-2020-24711: Improper Restriction of Rendered UI Layers or Frames (CWE-1021)
value: The Reset button on the Account Settings page in Gophish before 0.11.0 allows attackers to cause a denial of service via a clickjacking attack
CWE description: The web application does not restrict or incorrectly restricts frame objects or UI layers that belong to another application or domain, which can lead to user confusion about which interface the user is interacting with.
CVSS v3 base score: 6.5
Published: 2020-10-28T20:15Z
Repository: https://github.com/gophish/gophish
Language: Go
Files changed: controllers/route.go, middleware/middleware.go, middleware/middleware_test.go
Commit date: 2020-08-20 10:39:23 -0500
Commit message: Added a simple Content-Security-Policy to mitigate clickjacking attempts.

Diff: diff --git a/controllers/route.go b/controllers/route.go
index 5d2d5f3e..9109ff85 100644
--- a/controllers/route.go
+++ b/controllers/route.go
@@ -153,11 +153,11 @@ func (as *AdminServer) registerRoutes() {
 	}
 	csrfHandler := csrf.Protect(csrfKey,
 		csrf.FieldName("csrf_token"),
 		csrf.Secure(as.config.UseTLS))
 	adminHandler := csrfHandler(router)
-	adminHandler = mid.Use(adminHandler.ServeHTTP, mid.CSRFExceptions, mid.GetContext)
+	adminHandler = mid.Use(adminHandler.ServeHTTP, mid.CSRFExceptions, mid.GetContext, mid.ApplySecurityHeaders)
 
 	// Setup GZIP compression
 	gzipWrapper, _ := gziphandler.NewGzipLevelHandler(gzip.BestCompression)
 	adminHandler = gzipWrapper(adminHandler)
 
diff --git a/middleware/middleware.go b/middleware/middleware.go
index d4ff2019..85a32b05 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -174,10 +174,21 @@ func RequirePermission(perm string) func(http.Handler) http.HandlerFunc {
 			next.ServeHTTP(w, r)
 		}
 	}
 }
 
+// ApplySecurityHeaders applies various security headers according to best-
+// practices.
+func ApplySecurityHeaders(next http.Handler) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		csp := "frame-ancestors 'none';"
+		w.Header().Set("Content-Security-Policy", csp)
+		w.Header().Set("X-Frame-Options", "DENY")
+		next.ServeHTTP(w, r)
+	}
+}
+
 // JSONError returns an error in JSON format with the given
 // status code and message
 func JSONError(w http.ResponseWriter, c int, m string) {
 	cj, _ := json.MarshalIndent(models.Response{Success: false, Message: m}, "", "  ")
 	w.Header().Set("Content-Type", "application/json")
diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go
index 85915a2a..d7f15786 100644
--- a/middleware/middleware_test.go
+++ b/middleware/middleware_test.go
@@ -179,5 +179,21 @@ func TestPasswordResetRequired(t *testing.T) {
 	gotLocation := response.Header().Get("Location")
 	if gotLocation != expectedLocation {
 		t.Fatalf("incorrect location header received. expected %s got %s", expectedLocation, gotLocation)
 	}
 }
+
+func TestApplySecurityHeaders(t *testing.T) {
+	expected := map[string]string{
+		"Content-Security-Policy": "frame-ancestors 'none';",
+		"X-Frame-Options":         "DENY",
+	}
+	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	response := httptest.NewRecorder()
+	ApplySecurityHeaders(successHandler).ServeHTTP(response, req)
+	for header, value := range expected {
+		got := response.Header().Get(header)
+		if got != value {
+			t.Fatalf("incorrect security header received for %s: expected %s got %s", header, value, got)
+		}
+	}
+}

Vulnerable code: 	adminHandler = mid.Use(adminHandler.ServeHTTP, mid.CSRFExceptions, mid.GetContext)
Fixed code: 	adminHandler = mid.Use(adminHandler.ServeHTTP, mid.CSRFExceptions, mid.GetContext, mid.ApplySecurityHeaders)
// ApplySecurityHeaders applies various security headers according to best-
// practices.
func ApplySecurityHeaders(next http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		csp := "frame-ancestors 'none';"
		w.Header().Set("Content-Security-Policy", csp)
		w.Header().Set("X-Frame-Options", "DENY")
		next.ServeHTTP(w, r)
	}
}


func TestApplySecurityHeaders(t *testing.T) {
	expected := map[string]string{
		"Content-Security-Policy": "frame-ancestors 'none';",
		"X-Frame-Options":         "DENY",
	}
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	response := httptest.NewRecorder()
	ApplySecurityHeaders(successHandler).ServeHTTP(response, req)
	for header, value := range expected {
		got := response.Header().Get(header)
		if got != value {
			t.Fatalf("incorrect security header received for %s: expected %s got %s", header, value, got)
		}
	}
}