From d11094481f3ee575c53bf5008d590b711b78d692 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Fri, 2 Jul 2021 00:11:58 -0500 Subject: [PATCH] Add CSRF verification --- src/auth/session.go | 3 ++- src/templates/templates.go | 3 ++- src/website/forums.go | 14 ++++++++++++-- src/website/routes.go | 26 +++++++++++++++++++++++++- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/auth/session.go b/src/auth/session.go index e02c974..0d6065c 100644 --- a/src/auth/session.go +++ b/src/auth/session.go @@ -18,6 +18,7 @@ import ( ) const SessionCookieName = "HMNSession" +const CSRFFieldName = "csrf_token" const sessionDuration = time.Hour * 24 * 14 @@ -97,7 +98,7 @@ func NewSessionCookie(session *models.Session) *http.Cookie { Secure: config.Config.Auth.CookieSecure, HttpOnly: true, - SameSite: http.SameSiteDefaultMode, + SameSite: http.SameSiteLaxMode, } } diff --git a/src/templates/templates.go b/src/templates/templates.go index 09640cd..423b24a 100644 --- a/src/templates/templates.go +++ b/src/templates/templates.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "git.handmade.network/hmn/hmn/src/auth" "git.handmade.network/hmn/hmn/src/hmnurl" "git.handmade.network/hmn/hmn/src/logging" "github.com/Masterminds/sprig" @@ -113,7 +114,7 @@ var HMNTemplateFuncs = template.FuncMap{ return template.CSS(color.HTML()) }, "csrftoken": func(s Session) template.HTML { - return template.HTML(fmt.Sprintf(``, s.CSRFToken)) + return template.HTML(fmt.Sprintf(``, auth.CSRFFieldName, s.CSRFToken)) }, "darken": func(amount float64, color noire.Color) noire.Color { return color.Shade(amount) diff --git a/src/website/forums.go b/src/website/forums.go index 1bfbf7c..1e92854 100644 --- a/src/website/forums.go +++ b/src/website/forums.go @@ -534,9 +534,20 @@ func ForumNewThread(c *RequestContext) ResponseData { baseData.MathjaxEnabled = true // TODO(ben): Set breadcrumbs + c.Perf.StartBlock("SQL", "Fetch category tree") + categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn) + lineageBuilder := models.MakeCategoryLineageBuilder(categoryTree) + c.Perf.EndBlock() + + currentCatId, valid := validateSubforums(lineageBuilder, c.CurrentProject, c.PathParams["cats"]) + if !valid { + return FourOhFour(c) + } + var res ResponseData err := res.WriteTemplate("editor.html", editorData{ BaseData: baseData, + SubmitUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), true), SubmitLabel: "Post New Thread", PreviewLabel: "Preview", }, c.Perf) @@ -556,8 +567,7 @@ func ForumNewThread(c *RequestContext) ResponseData { } func ForumNewThreadSubmit(c *RequestContext) ResponseData { - var res ResponseData - return res + return c.Redirect(hmnurl.BuildForumNewThread(models.HMNProjectSlug, nil, false), http.StatusSeeOther) } func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) { diff --git a/src/website/routes.go b/src/website/routes.go index 6b5051a..834bd0f 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -90,6 +90,30 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt } } + csrfMiddleware := func(h Handler) Handler { + // CSRF mitigation actions per the OWASP cheat sheet: + // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html + return func(c *RequestContext) ResponseData { + c.Req.ParseForm() + csrfToken := c.Req.Form.Get(auth.CSRFFieldName) + if csrfToken != c.CurrentSession.CSRFToken { + c.Logger.Warn().Str("userId", c.CurrentUser.Username).Msg("user failed CSRF validation - potential attack?") + + err := auth.DeleteSession(c.Context(), c.Conn, c.CurrentSession.ID) + if err != nil { + c.Logger.Error().Err(err).Msg("failed to delete session on CSRF failure") + } + + res := c.Redirect("/", http.StatusSeeOther) + res.SetCookie(auth.DeleteSessionCookie) + + return res + } + + return h(c) + } + } + // TODO(asaf): login/logout shouldn't happen on subdomains. We should verify that in the middleware. routes.POST(hmnurl.RegexLoginAction, Login) routes.GET(hmnurl.RegexLogoutAction, Logout) @@ -124,7 +148,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt // NOTE(asaf): Any-project routes: mainRoutes.Handle([]string{http.MethodGet, http.MethodPost}, hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread)) - mainRoutes.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(ForumNewThreadSubmit)) + mainRoutes.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit))) mainRoutes.GET(hmnurl.RegexForumThread, ForumThread) mainRoutes.GET(hmnurl.RegexForumCategory, ForumCategory) mainRoutes.GET(hmnurl.RegexForumPost, ForumPostRedirect)