Cross-Site Request Forgery (CSRF)

Overview

Cross-site request forgery (CSRF) is a vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform.

For example, consider an application that allows a user to change their email address by making the following HTTP request:

POST /email/change HTTP/1.1
Host: vulnerable-website.local
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE

email=wiener@normal-user.com

In this case, an attacker can construct a web page containing the following HTML:

<html>
    <body>
        <form action="https://vulnerable-website.local/email/change" method="POST">
            <input type="hidden" name="email" value="pwned@evil-user.net" />
        </form>
        <script>
            document.forms[0].submit();
        </script>
    </body>
</html>

If a victim visits this page, the following will happen:

  1. The attacker's page will trigger an HTTP request to the vulnerable website.

  2. If a user is logged in to the vulnerable website, their browser will automatically include their session cookie in the request (assuming SameSite cookies are not being used).

  3. The vulnerable website will process the request in the normal way, treat it as having been made by a victim, and change their email address.

  4. As a result, an attacker's email will be linked to the victim's account.

You can find more details at PortSwigger Web Security Academy: Cross-site request forgery (CSRF).

This page describes various Cross-Site Request Forgery (CSRF) attack protection schemes and recommendations for implementing them.

General

Base mitigations

This section describes CSRF protections based on a token that a client passes to a server when making a request.

General

  • Validate a CSRF token for each request with unsafe HTTP methods: POST, PUT, DELETE, PATCH.

  • Reject the request if a CSRF token fails validation.

  • Use CSRF tokens of length 16+ bytes.

Synchronizer token (Statefull)

Synchronizer token is an approach to implementing CSRF protection that requires storing a token on a server side.

Approach description:

  1. At the start of a session, a CSRF token is generated on the server side.

  2. The token is stored on the server side for later verification.

  3. In response to an authentication request (session start), the token is returned to a client:

    1. The token can be returned inside HTML, for example as a hidden form field or inside the <meta> tag.

    2. The token may be returned in an HTTP header, such as X-CSRF-Token.

  4. Subsequent requests from a client must contain the CSRF token:

    1. The token can be transmitted to a server within a request body.

    2. The token can be transmitted to a server in an HTTP header, such as X-CSRF-Token, using an XHR request.

  5. The server side checks for the identity of the token stored on the server side and the token sent by a client.

  • Use a cryptographically strong generator to generate a CSRF token, see the Cryptography: Random Generators page.

  • Generate a unique CSRF token for each user session.

  • Sent a CSRF token in a request body or a custom header. Do not use cookies or URL parameters to transmit CSRF tokens.

  • Sent a CSRF token in the custom HTTP request header that is inserted via JavaScript.

Double submit cookie is an approach to implement CSRF protection that does not require storing tokens on the server side.

Approach description:

  1. A CSRF token is generated on the server side when a client performs a request.

  2. In response to the request, the server-side returns the token in two places:

    1. Cookies.

    2. One of the response parameters.

  3. Subsequent requests from a client must contain both CSRF tokens received earlier:

    1. Cookies.

    2. Inside the body or in the HTTP header.

  4. The server side checks for the identity of the token from cookies and the token from the body or HTTP header.

Use this approach only if you are sure that:

  1. All subdomains are under your control.

  2. All subdomains have the same protection level.

  3. All requests go exclusively over HTTPS.

  • Use a cryptographically strong generator to generate a CSRF token, see the Cryptography: Random Generators page.

  • Generate a new CSRF token for each request.

  • Store a CSRF token in the ultimate cookie, see the Cookie Security page.

  • The lifetime of a cookie with a CSRF token ~ 30 minutes, see the Expires and Max-Age cookie attributes in the Cookie Security page.

HMAC-based token (Stateless)

The HMAC-based token is an approach to implement CSRF protection that does not require storing tokens on the server side. In this case, the token is data hashed using HMAC.

Approach description:

  1. A token is generated on the backend when a client performs a request:

    timestamp + ... + HMAC(user ID + timestamp + ...)
  2. In response to the request, the server side returns the generated token:

    1. The token can be given inside HTML, for example as a hidden form field or inside the <meta> attribute.

    2. The token may be given in an HTTP header, such as X-CSRF-Token.

  3. Subsequent requests from a client must contain the CSRF token received earlier:

    1. The token can be passed to the backend inside a body.

    2. The token can be passed to the backend in an HTTP header, such as X-CSRF-Token, via an XHR request.

  4. The server side generates a token based on at least the user ID and timestamp and checks for the identity of the generated token with the token from the client.

  • Use HMAC to generate CSRF tokens. Use at least the following data for the CSRF token generation:

    • User ID.

    • Timestamp.

    timestamp + HMAC(user ID + timestamp)
  • Do not use cookies to transmit CSRF tokens.

  • Generate a new CSRF token for each request.

  • Set the lifetime of a CSRF token ~ 30 minutes.

  • Comply with the requirements from the Cryptography: Hash-based Message Authentication Code (HMAC) page.

Encryption-based token (Stateless)

The encryption-based token is an approach to implement CSRF protection that does not require storing tokens on the server side. In this case, the token is encrypted data.

Approach description:

  1. A token is generated on the server side when a client performs a request:

    ENCRYPT(SECRET_KEY, user ID + timestamp)
  2. In response to the request, the server side returns the generated token:

    1. The token can be given inside HTML, for example as a hidden form field or inside the <meta> attribute.

    2. The token may be given in an HTTP header, such as X-CSRF-Token.

  3. Subsequent requests from a client must contain the CSRF token received earlier:

    1. The token can be passed to the backend inside a body.

    2. The token can be passed to the backend in an HTTP header, such as X-CSRF-Token, via an XHR request.

  4. The server side decrypts the received token and compares the stored data (at least user ID and timestamp) with the expected data.

  • Use encryption to generate CSRF tokens. Use at least the following data for the CSRF token generation:

    • User ID.

    • Timestamp.

    ENCRYPT(SECRET_KEY, user ID + timestamp)
  • Do not use cookies to transmit CSRF tokens.

  • Generate a new CSRF token for each request.

  • Set the lifetime of a CSRF token ~ 30 minutes.

  • Comply with the requirements from the Cryptography: Encryption page.

Advanced mitigations

Advanced CSRF protection mechanisms. Use these mechanisms as an additional layer of protection to the mechanisms described in the Base Mitigations section.

  • Store a session token in the ultimate cookie, see the Cookie Security page.

  • Require user interaction for highly sensitive operations:

    • Require a user to re-authenticate.

    • Require multi-factor authentication.

    • Use CAPTCHA.

CSRF protection implementation

Use the github.com/gorilla/csrf package to implement CSRF protection.

github.com/gorilla/csrf implements the double-submit cookie approach. Make sure the requirements for the Double-submit cookie (Stateless) section are met.

package main

import (
    "net/http"
    "github.com/gorilla/csrf"
)

func main() {
    mux := http.NewServeMux()
    authKey := GetCSRFKey()
    CSRFMiddleware := csrf.Protect(
        authKey
        csrf.MaxAge(180),
        csrf.Path("/"),
        csrf.HttpOnly(true),
        csrf.Secure(true),
        csrf.CookieName("__Host-CSRF-Token"),
        csrf.SameSite(csrf.SameSiteStrictMode),
    )
    mux.Handle("/signup", ShowSignupForm)
    // All POST requests without a valid token will return HTTP 403 Forbidden.
    // We should also ensure that our mutating (non-idempotent) handler only
    // matches on POST requests. We can check that here, at the router level, or
    // within the handler itself via r.Method.
    mux.Handle("/signup/post", ShowSignupForm)
    // Add the middleware to your router by wrapping it.
    http.ListenAndServe(":8000", CSRFMiddleware(mux))
}

func GetCSRFKey() []byte {
    // get CSRF key
}

func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }
    // signup_form.tmpl just needs a {{ .csrfField }} template tag for
    // csrf.TemplateField to inject the CSRF token into. Easy!
    t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
        csrf.TemplateTag: csrf.TemplateField(r),
    })
}

func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }
    // We can trust that requests making it this far have satisfied
    // our CSRF protection requirements.
}

References

Last updated