CVE-2022-24863: Improper Handling of Exceptional Conditions (CWE-755)
value: http-swagger is an open source wrapper to automatically generate RESTful API documentation with Swagger 2.0. In versions of http-swagger prior to 1.2.6 an attacker may perform a denial of service attack consisting of memory exhaustion on the host system. The cause of the memory exhaustion is down to improper handling of http methods. Users are advised to upgrade. Users unable to upgrade may to restrict the path prefix to the "GET" method as a workaround.
CWE description: The product does not handle or incorrectly handles an exceptional condition.
CVSS v3 base score: 7.5
Published: 2022-04-18T19:15Z
Repository: https://github.com/swaggo/http-swagger
Language: Go
Files changed: .github/workflows/ci.yml, swagger.go, swagger_test.go
Commit date: 2022-04-16 09:23:47 +0300
Commit message: fix: security improvement (#62)


Diff: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 17ebad4..808f9a6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,11 +8,11 @@ on:
 
 jobs:
   test:
     strategy:
       matrix:
-        go: [ '1.15.x', '1.16.x', '1.17.x' ]
+        go: [ '1.15.x', '1.16.x', '1.17.x', '1.18.x' ]
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@master
       - name: Set up Go
         uses: actions/setup-go@v1
diff --git a/swagger.go b/swagger.go
index 8c0ea81..1dddf43 100644
--- a/swagger.go
+++ b/swagger.go
@@ -120,10 +120,14 @@ func Handler(configFns ...func(*Config)) http.HandlerFunc {
 	index, _ := t.Parse(indexTempl)
 
 	var re = regexp.MustCompile(`^(.*/)([^?].*)?[?|.]*$`)
 
 	return func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != http.MethodGet {
+			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+			return
+		}
 		matches := re.FindStringSubmatch(r.RequestURI)
 		path := matches[2]
 
 		h := swaggerFiles.Handler
 		once.Do(func() {
@@ -153,11 +157,11 @@ func Handler(configFns ...func(*Config)) http.HandlerFunc {
 
 				return
 			}
 			_, _ = w.Write([]byte(doc))
 		case "":
-			http.Redirect(w, r, h.Prefix+"index.html", 301)
+			http.Redirect(w, r, h.Prefix+"index.html", http.StatusMovedPermanently)
 		default:
 			h.ServeHTTP(w, r)
 		}
 	}
 }
diff --git a/swagger_test.go b/swagger_test.go
index 6978c12..2045b8a 100644
--- a/swagger_test.go
+++ b/swagger_test.go
@@ -41,39 +41,40 @@ func (s *mockedSwag) ReadDoc() string {
 func TestWrapHandler(t *testing.T) {
 	router := http.NewServeMux()
 
 	router.Handle("/", Handler(DocExpansion("none"), DomID("#swagger-ui")))
 
-	w1 := performRequest("GET", "/index.html", router)
-	assert.Equal(t, 200, w1.Code)
+	w1 := performRequest(http.MethodGet, "/index.html", router)
+	assert.Equal(t, http.StatusOK, w1.Code)
 	assert.Equal(t, w1.Header()["Content-Type"][0], "text/html; charset=utf-8")
 
-	w2 := performRequest("GET", "/doc.json", router)
-	assert.Equal(t, 500, w2.Code)
+	assert.Equal(t, http.StatusInternalServerError, performRequest(http.MethodGet, "/doc.json", router).Code)
 
 	swag.Register(swag.Name, &mockedSwag{})
-	w2 = performRequest("GET", "/doc.json", router)
-	assert.Equal(t, 200, w2.Code)
+	w2 := performRequest(http.MethodGet, "/doc.json", router)
+	assert.Equal(t, http.StatusOK, w2.Code)
 	assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("content-type"))
 
-	w3 := performRequest("GET", "/favicon-16x16.png", router)
-	assert.Equal(t, 200, w3.Code)
+	w3 := performRequest(http.MethodGet, "/favicon-16x16.png", router)
+	assert.Equal(t, http.StatusOK, w3.Code)
 	assert.Equal(t, w3.Header()["Content-Type"][0], "image/png")
 
-	w4 := performRequest("GET", "/swagger-ui.css", router)
-	assert.Equal(t, 200, w4.Code)
+	w4 := performRequest(http.MethodGet, "/swagger-ui.css", router)
+	assert.Equal(t, http.StatusOK, w4.Code)
 	assert.Equal(t, w4.Header()["Content-Type"][0], "text/css; charset=utf-8")
 
-	w5 := performRequest("GET", "/swagger-ui-bundle.js", router)
-	assert.Equal(t, 200, w5.Code)
+	w5 := performRequest(http.MethodGet, "/swagger-ui-bundle.js", router)
+	assert.Equal(t, http.StatusOK, w5.Code)
 	assert.Equal(t, w5.Header()["Content-Type"][0], "application/javascript")
 
-	w6 := performRequest("GET", "/notfound", router)
-	assert.Equal(t, 404, w6.Code)
+	assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/notfound", router).Code)
 
-	w7 := performRequest("GET", "/", router)
-	assert.Equal(t, 301, w7.Code)
+	assert.Equal(t, 301, performRequest(http.MethodGet, "/", router).Code)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, performRequest(http.MethodPost, "/swagger/index.html", router).Code)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, performRequest(http.MethodPut, "/swagger/index.html", router).Code)
 }
 
 func performRequest(method, target string, h http.Handler) *httptest.ResponseRecorder {
 	r := httptest.NewRequest(method, target, nil)
 	w := httptest.NewRecorder()

Vulnerable code:         go: [ '1.15.x', '1.16.x', '1.17.x' ]
			http.Redirect(w, r, h.Prefix+"index.html", 301)
	w1 := performRequest("GET", "/index.html", router)
	assert.Equal(t, 200, w1.Code)
	w2 := performRequest("GET", "/doc.json", router)
	assert.Equal(t, 500, w2.Code)
	w2 = performRequest("GET", "/doc.json", router)
	assert.Equal(t, 200, w2.Code)
	w3 := performRequest("GET", "/favicon-16x16.png", router)
	assert.Equal(t, 200, w3.Code)
	w4 := performRequest("GET", "/swagger-ui.css", router)
	assert.Equal(t, 200, w4.Code)
	w5 := performRequest("GET", "/swagger-ui-bundle.js", router)
	assert.Equal(t, 200, w5.Code)
	w6 := performRequest("GET", "/notfound", router)
	assert.Equal(t, 404, w6.Code)
	w7 := performRequest("GET", "/", router)
	assert.Equal(t, 301, w7.Code)
Fixed code:         go: [ '1.15.x', '1.16.x', '1.17.x', '1.18.x' ]
		if r.Method != http.MethodGet {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
			http.Redirect(w, r, h.Prefix+"index.html", http.StatusMovedPermanently)
	w1 := performRequest(http.MethodGet, "/index.html", router)
	assert.Equal(t, http.StatusOK, w1.Code)
	assert.Equal(t, http.StatusInternalServerError, performRequest(http.MethodGet, "/doc.json", router).Code)
	w2 := performRequest(http.MethodGet, "/doc.json", router)
	assert.Equal(t, http.StatusOK, w2.Code)
	w3 := performRequest(http.MethodGet, "/favicon-16x16.png", router)
	assert.Equal(t, http.StatusOK, w3.Code)
	w4 := performRequest(http.MethodGet, "/swagger-ui.css", router)
	assert.Equal(t, http.StatusOK, w4.Code)
	w5 := performRequest(http.MethodGet, "/swagger-ui-bundle.js", router)
	assert.Equal(t, http.StatusOK, w5.Code)
	assert.Equal(t, http.StatusNotFound, performRequest(http.MethodGet, "/notfound", router).Code)
	assert.Equal(t, 301, performRequest(http.MethodGet, "/", router).Code)

	assert.Equal(t, http.StatusMethodNotAllowed, performRequest(http.MethodPost, "/swagger/index.html", router).Code)

	assert.Equal(t, http.StatusMethodNotAllowed, performRequest(http.MethodPut, "/swagger/index.html", router).Code)