From 6896d2bfb158df341d51e230a53e77e326c7231c Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 14 Sep 2024 21:45:25 +0200 Subject: [PATCH] httpd: validate reference also for CSRF token in headers Signed-off-by: Nicola Murino --- internal/httpd/auth_utils.go | 4 ++++ internal/httpd/httpd_test.go | 15 ++++++++++++++- internal/httpd/middleware.go | 4 ++++ pkgs/build.sh | 2 +- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/internal/httpd/auth_utils.go b/internal/httpd/auth_utils.go index 12d24a4f..2cb66651 100644 --- a/internal/httpd/auth_utils.go +++ b/internal/httpd/auth_utils.go @@ -468,6 +468,10 @@ func verifyCSRFToken(r *http.Request, csrfTokenAuth *jwtauth.JWTAuth) error { logger.Debug(logSender, "", "error validating CSRF token IP audience") return errors.New("the form token is not valid") } + return checkCSRFTokenRef(r, token) +} + +func checkCSRFTokenRef(r *http.Request, token jwt.Token) error { claims, err := getTokenClaims(r) if err != nil { logger.Debug(logSender, "", "error getting token claims for CSRF validation: %v", err) diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index 29534d59..beb290b4 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -20705,6 +20705,17 @@ func TestWebAdminBasicMock(t *testing.T) { checkResponseCode(t, http.StatusForbidden, rr) assert.Contains(t, rr.Body.String(), "Invalid token") + req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON)) + assert.NoError(t, err) + setJWTCookieForReq(req, altToken) + setCSRFHeaderForReq(req, csrfToken) // invalid CSRF token + req.RemoteAddr = defaultRemoteAddr + rr = executeRequest(req) + checkResponseCode(t, http.StatusForbidden, rr) + assert.Contains(t, rr.Body.String(), "the token is not valid") + + csrfToken, err = getCSRFTokenFromInternalPageMock(webAdminPath, altToken) + assert.NoError(t, err) req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON)) assert.NoError(t, err) setJWTCookieForReq(req, altToken) @@ -20781,7 +20792,7 @@ func TestWebAdminBasicMock(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr) - form.Set(csrfFormToken, "invalid csrf") + form.Set(csrfFormToken, csrfToken) // associated to altToken req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode()))) req.RemoteAddr = defaultRemoteAddr req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -20790,6 +20801,8 @@ func TestWebAdminBasicMock(t *testing.T) { checkResponseCode(t, http.StatusForbidden, rr) assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidCSRF) + csrfToken, err = getCSRFTokenFromInternalPageMock(webAdminPath, token) + assert.NoError(t, err) form.Set(csrfFormToken, csrfToken) form.Set("email", "not-an-email") req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode()))) diff --git a/internal/httpd/middleware.go b/internal/httpd/middleware.go index ac647f69..789ec4cc 100644 --- a/internal/httpd/middleware.go +++ b/internal/httpd/middleware.go @@ -343,6 +343,10 @@ func (s *httpdServer) verifyCSRFHeader(next http.Handler) http.Handler { sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden) return } + if err := checkCSRFTokenRef(r, token); err != nil { + sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden) + return + } next.ServeHTTP(w, r) }) diff --git a/pkgs/build.sh b/pkgs/build.sh index 45b0b7f0..b2f62767 100755 --- a/pkgs/build.sh +++ b/pkgs/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -NFPM_VERSION=2.39.0 +NFPM_VERSION=2.40.0 NFPM_ARCH=${NFPM_ARCH:-amd64} if [ -z ${SFTPGO_VERSION} ] then