Skip to content
Language:en Author:Laborci Gergely
web-security

CSRF: a request triggered by a foreign page

Objectives

By the end of the lesson, you can explain how CSRF works, you know under what conditions it is possible, and you can name its defenses: CSRF token, Fetch metadata, non-simple requests, SameSite cookie, checking Origin.

Why can a request coming from a foreign page work?

The student is logged in to Kurzuspont, then opens a foreign web page. The foreign page can submit a hidden form to the address of Kurzuspont (POST /lemondas, "cancellation"). The browser attaches cookies according to the destination of the request (7.2), so the request arrives with the logged-in session. If the server of Kurzuspont only checks for the presence of the cookie, it regards the request as the student's intention. This is CSRF (Cross-Site Request Forgery). According to MDN it is possible if the site changes state with HTTP requests, relies solely on cookies for authentication, and uses parameters that the attacker can guess in advance. The attacker does not need to read the response: the side effect is the point (8.2).

sequenceDiagram
    participant H as Student
    participant I as Foreign page
    participant B as Browser
    participant K as Course API
    H->>B: Logged-in session
    B->>I: Opening the foreign page
    I->>B: Triggering a state-changing request
    B->>K: Request + cookie
    K-->>B: Result of the operation

Tried: the same request on three servers

To three variants of a trial server we sent the same request that a browser request triggered from a foreign page would amount to: the student's cookie and the headers Origin: https://idegen.example and Sec-Fetch-Site: cross-site (the browser sends these, curl imitates them here). The second request comes from Kurzuspont's own page, with the corresponding signals.

Protection on the server From the foreign page From the own page
None (checks only the cookie) 200: "a jelentkezés lemondva anna nevében" ("the enrolment was cancelled in anna's name") 200
CSRF token (X-CSRF-Token) 403: "hiányzó vagy hibás CSRF-token" ("missing or wrong CSRF token") 200 (the token arrived when the form was fetched)
Fetch metadata (Sec-Fetch-Site) 403: "idegen eredetű kérés" ("request of foreign origin") 200

The server without protection accepted the request coming from the foreign page too, and cancelled the enrolment in Anna's name. The other two servers rejected it.

The defenses

According to MDN's summary:

  1. CSRF token (primary defense). The server embeds an unpredictable token into the page (a hidden field in a form, a header with fetch()), and checks it on every state-changing request. The foreign page does not know the token because (8.2) it cannot read the page of Kurzuspont.
  2. Fetch metadata (primary defense). The browser sends a Sec-Fetch-Site header with every request, which says whether the request is same-origin, same-site, cross-site, or none (it stems from a direct action of the user). The server can reject cross-site state-changing requests.
  3. Using non-simple requests (for JavaScript APIs). Simple requests (for example a form submission) can be sent from a foreign origin too; non-simple ones (for example Content-Type: application/json or a custom header) first need a preflight (8.2), which the foreign origin does not get. This is worth relying on only if the CORS setting does not allow the attacker's origin.
  4. SameSite cookie (defense in depth). Strict: never sends it from a foreign site; Lax: only for top-level navigation with a safe method. According to MDN, Strict if possible, otherwise Lax. SameSite works at the site level, not the origin level, so all subdomains must be trustworthy.
  5. Checking Origin (and Referer). The server can reject state-changing requests whose Origin header is not its own origin.

And the basic rule: do not change state with GET (2.3). A GET request can be triggered even by an <img src=...>, and a Lax cookie is also sent with a top-level GET navigation.

If the application is XSS-vulnerable (8.3), the attacker starts the request from the own origin, so they can bypass some of the CSRF defenses. The layers of protection therefore build on each other.

Worked example: the cancel operation of Kurzuspont

Decision Reason
Cancellation is DELETE or POST, never GET A GET can be triggered from anywhere (2.3).
The session cookie is HttpOnly; Secure; SameSite=Lax Basic protection against requests coming from a foreign page (7.2).
The form gets a CSRF token, the server checks it Primary defense.
The server checks the Sec-Fetch-Site and Origin headers Defense in depth.
The API expects JSON with a Content-Type: application/json header A non-simple request, a preflight is needed.

Common misconceptions

Statement Clarification
"CORS prevents CSRF." CORS mainly regulates the reading of the response.
"If the request is a POST, it cannot be CSRF." The form of a foreign page can also send a POST request.
"SameSite solves it completely." It is defense in depth; it works at the site level, and does not completely replace the token.
"CSRF requires reading the response." The side effect is enough.
"HTTPS protects." It protects the connection, not the intention of the request (4.3).

In brief

  • CSRF: a foreign page forces a state change with the logged-in browser's cookie.
  • Conditions: a state-changing HTTP request, cookie-only authentication, guessable parameters.
  • Defense: CSRF token, Fetch metadata, non-simple requests, SameSite cookie, checking Origin.
  • Do not change state with GET; CORS is not CSRF protection.

Review questions

  1. Why does the browser attach the cookie to a request started from a foreign page?
  2. Why can a foreign page not guess the CSRF token?
  3. What does the Sec-Fetch-Site: cross-site header say?
  4. Why is the SameSite cookie not enough alone?

Learning resources

Prerequisites (2)

  • cookie
  • same-origin policy

Teaches (1)

  • CSRF