WordPress CSRF Protection: How Nonces, SameSite, and Referer Checks Stop Forged Requests

Card showing a forged request from a second tab rejected without a nonce

CSRF (cross-site request forgery) makes a logged-in admin’s browser send a request they did not intend. It sits in the OWASP Top 10 under Broken Access Control (A01), and its CWE number is 352.

How the attack runs

  1. You’re logged into your WordPress admin.
  2. In another tab, you open a page the attacker controls, from a link, an ad or a compromised site.
  3. That page sends a hidden request to your site: a form submission, an image request or a script call.
  4. Your browser attaches your session cookie. Your site treats the forged request as yours and acts on it.

Forged-request flaws keep turning up in WordPress plugins. Some were in two-factor setup code.

Three controls that stop a forged request

ControlHow it worksLimit
NoncesA short-lived token tied to one action and one user. The server checks it with wp_verify_nonce() or check_admin_referer() before acting. An attacker’s page can’t create a valid oneOnly protects handlers that check it. Core checks its own. Plugins and custom code have to do it themselves
SameSite cookiesWith Lax or Strict, the browser doesn’t send your session cookie on cross-site POST requests, which is the main route CSRF usesDoesn’t fix a handler that never verifies a token
Referer checkingcheck_admin_referer() confirms the request came from your own siteReferer headers can be stripped, so it can’t stand alone

Verify the nonce on every request that changes state in custom endpoints.

How to harden your site against CSRF

  1. Set SameSite on your cookies. Use at least SameSite=Lax for session and authentication cookies. This lives in your security headers.
  2. Add the rest of your security headers. See our guide to website security headers.
  3. Add a content security policy. It limits what an injected page can do.
  4. Audit nonces in your custom code. Every handler that changes state should call wp_verify_nonce() or check_admin_referer().
  5. Make admin endpoints less predictable. CSRF payloads target default WordPress paths. If you change your wp-admin URL, canned payloads can’t find their target.

What it does and doesn’t cover

WP Ghost reduces the attack surface around login and admin. It does not replace nonce checks.

DoesDoesn’t
Rejects forged requests, even from a real admin’s browserFix a plugin that ships without nonce checks. That flaw stays until the plugin is patched

Keep plugins updated and pick ones that verify requests. The complete hack prevention guide shows how this fits with other defenses.

FAQ

Does WordPress have built-in CSRF protection?

Partly. Core provides nonces, and custom code has to check them.

Will SameSite cookies break my logins or my site?

Not with SameSite=Lax, the recommended default.

Can a firewall stop CSRF attacks?

Only partly. A forged request can look like a normal logged-in one.

How is CSRF different from XSS?

CSRF tricks a victim’s browser into sending a forged request to your site.