CSRF Tokens

Cross-Site Request Forgery Tokens

Cross-site request forgery (CSRF) abuses the fact that browsers attach your cookies to every request, including one a different site triggers. If a logged-in user visits an attacker's page, a hidden form there can POST to your application with the session cookie riding along, and the action goes through as if the user asked. The defense is a secret the attacker cannot know: a synchronizer token in the session.

csrf.php: the fixed branch requires a token that matches the sessionPHP
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(32));
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($safe && !hash_equals($_SESSION['csrf'], (string) ($_POST['csrf'] ?? ''))) {
        http_response_code(403); exit("403 CSRF token missing or wrong\n");
    }
    $_SESSION['email'] = $_POST['email'] ?? $_SESSION['email'];  // the sensitive action
}

With a saved cookie jar: the victim loads the page, the attacker forges a POST with only the cookie against each endpoint, then a real POST carries the session token:

Output of 179
current email: owner@example.com  token: 7e0bdfd72872613a58fdb561b652c0444f4cbd9b8dd44a2683d736
  700d3ba0cd
email changed to attacker@evil.test
403 CSRF token missing or wrong
email changed to new@example.com

Carrying only the session cookie, the forged POST changed the account email on the vulnerable endpoint. The hardened endpoint rejected it with 403 for want of a token, and accepted it only with the real session token. Compare tokens with hash_equals(), not == (Subsection 4.17.11), render the token into every form as a hidden field, and send it in a header for fetch(). The complementary defense is SameSite (Subsection 4.15.3): set Lax or Strict and the cookie is not sent on a cross-site POST. Use both.