diff --git a/assets/go-licenses.json b/assets/go-licenses.json index c4b62475fd4bc..f259b88c57044 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -734,6 +734,11 @@ "path": "github.com/rivo/uniseg/LICENSE.txt", "licenseText": "MIT License\n\nCopyright (c) 2019 Oliver Kuederle\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "github.com/rs/cors", + "path": "github.com/rs/cors/LICENSE", + "licenseText": "Copyright (c) 2014 Olivier Poitrey \u003crs@dailymotion.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" + }, { "name": "github.com/rs/xid", "path": "github.com/rs/xid/LICENSE", diff --git a/go.mod b/go.mod index 2c53867e1e98d..6f5668132b45e 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,7 @@ require ( github.com/pquerna/otp v1.3.0 github.com/prometheus/client_golang v1.13.0 github.com/quasoft/websspi v1.1.2 + github.com/rs/cors v1.8.2 github.com/santhosh-tekuri/jsonschema/v5 v5.0.1 github.com/sergi/go-diff v1.2.0 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 diff --git a/go.sum b/go.sum index 6388c2b183221..ab9ae78e69b4e 100644 --- a/go.sum +++ b/go.sum @@ -1290,6 +1290,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= diff --git a/modules/setting/cors.go b/modules/setting/cors.go index ae0736e8307c8..f581a637317a0 100644 --- a/modules/setting/cors.go +++ b/modules/setting/cors.go @@ -7,8 +7,14 @@ import ( "time" "code.gitea.io/gitea/modules/log" + + "github.com/rs/cors" ) +// Cors handles CORS requests and allows other middlewares +// to check whetcher request marches CORS allowed origins. +var Cors *cors.Cors + // CORSConfig defines CORS settings var CORSConfig = struct { Enabled bool @@ -34,6 +40,13 @@ func newCORSService() { } if CORSConfig.Enabled { + Cors = cors.New(cors.Options{ + AllowedOrigins: CORSConfig.AllowDomain, + AllowedMethods: CORSConfig.Methods, + AllowCredentials: CORSConfig.AllowCredentials, + MaxAge: int(CORSConfig.MaxAge.Seconds()), + }) + log.Info("CORS Service Enabled") } } diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 0c7838b0ef5be..750f38bf235e3 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -78,5 +78,44 @@ func Middlewares() []func(http.Handler) http.Handler { next.ServeHTTP(resp, req) }) }) + + // Add CSRF handler. + handlers = append(handlers, csrfHandler()) + return handlers } + +// csfrHandler blocks recognized CSRF attempts. +// WARNING: for this proctection to work, web browser compatible with +// Fetch Metadata Request Headers (https://w3c.github.io/webappsec-fetch-metadata) +// must be used. +func csrfHandler() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + // Put header names we use for CSRF recognition into Vary response header. + if setting.CORSConfig.Enabled { + resp.Header().Set("Vary", "Origin, Sec-Fetch-Site") + } else { + resp.Header().Set("Vary", "Sec-Fetch-Site") + } + + // Allow requests not recognized as CSRF. + secFetchSite := strings.ToLower(req.Header.Get("Sec-Fetch-Site")) + if req.Method == "GET" || // GET, HEAD and OPTIONS must not be used for changing state (CSRF resistant). + req.Method == "HEAD" || + req.Method == "OPTIONS" || + secFetchSite == "" || // Accept requests from clients without Fetch Metadata Request Headers support. + secFetchSite == "same-origin" || // Accept requests from own origin. + secFetchSite == "none" || // Accept requests initiated by user (i.e. using bookmark). + ((secFetchSite == "same-site" || secFetchSite == "cross-site") && // Accept cross site requests allowed by CORS. + setting.CORSConfig.Enabled && setting.Cors.OriginAllowed(req)) { + next.ServeHTTP(resp, req) + return + } + + // Forbid and log other requests as CSRF. + log.Error("CSRF rejected: METHOD=\"%s\", Origin=\"%s\", Sec-Fetch-Site=\"%s\"", req.Method, req.Header.Get("Origin"), secFetchSite) + http.Error(resp, http.StatusText(http.StatusForbidden), http.StatusForbidden) + }) + } +}