Back to scan results
Check 23 of 24

CSRF Token Presence

We parse every <form method="post"> on your homepage and look for a hidden input with a name that matches the conventions used by major frameworks for anti-CSRF tokens. A POST form without one is at least a yellow flag for cross-site request forgery.

What this check probes

For each POST form found, we look for any hidden input whose name matches one of:

  • csrf_token, csrfmiddlewaretoken (Django)
  • authenticity_token (Rails)
  • _token, _csrf (Laravel, Express csurf)
  • __RequestVerificationToken (ASP.NET)
  • X-CSRF-Token when injected as a meta tag for AJAX use
  • nonce, state, xsrf, antiforgery — generic patterns

Forms with no token are flagged as a warning — the absence of a token doesn't prove CSRF vulnerability (the framework might enforce it via cookie+header instead, or the form might be intentionally cross-origin like a public newsletter signup), but it's worth investigating.

Forms with method="get" are not checked — GET should never have side effects.

Why this matters for PCI DSS

Cross-Site Request Forgery: an attacker hosts a page that triggers a state-changing request to your site. The user, who is logged in to your site in another tab, has cookies sent automatically. Without an anti-CSRF token, your server can't distinguish between an intentional submission and a forged one.

For PCI scope: a CSRF on a "change shipping address" or "send refund" endpoint can be used to redirect goods or money to the attacker. A CSRF on the password change endpoint can be used to lock the legitimate user out and take over the account.

PCI DSS 4.0 Requirement 6.2.4 explicitly names cross-site request forgery as an attack class against which payment systems must defend.

How to fix it

Modern frameworks all ship CSRF protection — turn it on and use the helper:

Django — {% csrf_token %} in every form template; django.middleware.csrf.CsrfViewMiddleware in MIDDLEWARE.

Rails — protect_from_forgery with: :exception in ApplicationController; the form_with helper inserts the token automatically.

Laravel — Blade @csrf directive in every form; the VerifyCsrfToken middleware enforces.

ASP.NET MVC / Razor Pages — @Html.AntiForgeryToken() in the form; [ValidateAntiForgeryToken] attribute on the action. ASP.NET Core enforces tokens automatically when antiforgery is configured.

Express (Node.js) — install csurf middleware; in the template render the token as a hidden field; in the AJAX flow set the X-CSRF-Token header.

For SPAs and APIs, the standard pattern is the "double-submit cookie": the server sets a CSRF cookie on first response; the SPA reads it and copies the value into a custom header (e.g., X-CSRF-Token) on each request. Same-origin policy stops cross-site pages from reading the cookie or setting the header, so forgery fails.

Defense in depth — set SameSite=Lax or Strict on session cookies (see Check 14). Modern browsers default to Lax, which blocks most forms of CSRF before the request even leaves the browser. SameSite is not a substitute for tokens but a strong second layer.

Don't use referer-checking alone — Referer headers can be stripped by intermediaries and are not trustworthy.

Fixed it? Re-run the scan to confirm.

Run scan again