| 258 | var safeMethods = []string{http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace} |
| 259 | |
| 260 | func (config CSRFConfig) checkSecFetchSiteRequest(c *echo.Context) (bool, error) { |
| 261 | // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-metadata-headers |
| 262 | // Sec-Fetch-Site values are: |
| 263 | // - `same-origin` exact origin match - allow always |
| 264 | // - `same-site` same registrable domain (subdomain and/or different port) - block, unless explicitly trusted |
| 265 | // - `cross-site` request originates from different site - block, unless explicitly trusted |
| 266 | // - `none` direct navigation (URL bar, bookmark) - allow always |
| 267 | secFetchSite := c.Request().Header.Get(echo.HeaderSecFetchSite) |
| 268 | if secFetchSite == "" { |
| 269 | return false, nil |
| 270 | } |
| 271 | |
| 272 | if len(config.TrustedOrigins) > 0 { |
| 273 | // trusted sites ala OAuth callbacks etc. should be let through |
| 274 | origin := c.Request().Header.Get(echo.HeaderOrigin) |
| 275 | if origin != "" { |
| 276 | for _, trustedOrigin := range config.TrustedOrigins { |
| 277 | if strings.EqualFold(origin, trustedOrigin) { |
| 278 | return true, nil |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | isSafe := slices.Contains(safeMethods, c.Request().Method) |
| 284 | if !isSafe { // for state-changing request check SecFetchSite value |
| 285 | isSafe = secFetchSite == "same-origin" || secFetchSite == "none" |
| 286 | } |
| 287 | |
| 288 | if isSafe { |
| 289 | // This helps handlers that support older token-based CSRF protection. |
| 290 | // We know that the client is using a browser that supports Sec-Fetch-Site header, so when the form is submitted in |
| 291 | // the future with this dummy token value it is OK. Although the request is safe, the template rendered by the |
| 292 | // handler may need this value to render CSRF token for form. |
| 293 | c.Set(config.ContextKey, CSRFUsingSecFetchSite) |
| 294 | return true, nil |
| 295 | } |
| 296 | // we are here when request is state-changing and `cross-site` or `same-site` |
| 297 | |
| 298 | // Note: if you want to allow `same-site` use config.TrustedOrigins or `config.AllowSecFetchSiteFunc` |
| 299 | if config.AllowSecFetchSiteFunc != nil { |
| 300 | return config.AllowSecFetchSiteFunc(c) |
| 301 | } |
| 302 | |
| 303 | if secFetchSite == "same-site" { |
| 304 | return false, echo.NewHTTPError(http.StatusForbidden, "same-site request blocked by CSRF") |
| 305 | } |
| 306 | return false, echo.NewHTTPError(http.StatusForbidden, "cross-site request blocked by CSRF") |
| 307 | } |