The one-sentence version
Cross-site request forgery (CSRF) tricks a browser that's already logged into your WordPress admin into sending a request it never meant to send — not by stealing your password, but by quietly borrowing the fact that your browser is already authenticated.
Here's the mechanic that makes it possible: when you're logged into wp-admin, your browser holds a session cookie that proves who you are. Every request your browser sends to that site — whether you typed the URL, clicked a link on the site itself, or clicked a link on a completely different page — automatically carries that cookie along. The browser doesn't ask "did the user mean to visit wp-admin just now, from this page?" It just attaches the credentials it has for that domain and sends the request. CSRF abuses exactly that behavior.
A concrete example of how this plays out
Imagine you're logged into your WordPress dashboard in one browser tab, and in another tab you open an email or visit an unrelated page. That page contains something as simple as a hidden, invisible image tag or an auto-submitting form whose destination isn't an image at all — it's a URL on your own WordPress site, formatted to trigger an action a legitimate admin request would normally trigger. Something conceptually like this:
https://yoursite.com/wp-admin/admin-ajax.php?action=create_admin&user=attacker&role=administrator
If that endpoint isn't protected, your browser sends this request exactly as if you'd clicked it on purpose — cookie and all — because as far as the browser is concerned, it's just a normal request to a site you're logged into. The page that triggered it doesn't need to see your cookie or steal anything from you. It only needs your browser to fire the request while you're authenticated. If the receiving code trusts "this request came from a logged-in session" as proof that you intended it, that's the whole attack.
Why WordPress core has "nonces" — and what they actually are
WordPress's own defense against this is the nonce system (short for "number used once," though in WordPress they're valid for a time window rather than genuinely single-use). A nonce is a token generated by the server, tied to a specific user, a specific action, and a specific window of time — typically 12–24 hours. A legitimate form or admin link includes its nonce as a hidden field or URL parameter. When the request comes back in, WordPress checks that the nonce matches what it issued, for that user, for that specific action, before it lets the request through.
The reason this stops CSRF: an attacker crafting a malicious link or form from an outside page has no way to know what nonce value your session currently holds for that action — it's not something they can guess, and it's not something a cross-origin page can read from your browser. Without the correct nonce, the forged request gets rejected before it does anything, even though the cookie is valid and the user is genuinely logged in.
WordPress core itself checks nonces consistently on its own admin actions and has for a long time. The gap almost always shows up somewhere else: a plugin adds its own AJAX endpoint or admin-post handler for a settings change, a bulk action, or an account operation, and the developer either forgets to call wp_verify_nonce() / check_admin_referer(), or implements it incorrectly. That single missing check is functionally the entire vulnerability — everything else about the endpoint can be written correctly and it still won't matter.
A real, verified example
In March 2025, Wordfence's threat intelligence team disclosed CVE-2025-1764, a CSRF vulnerability in LoginPress (the "wp-login Custom Login Page Customizer" plugin), affecting all versions up to and including 3.3.1, with a CVSS score of 7.5 (high). The plugin's custom_plugin_set_option function — used to save the plugin's settings — was missing proper nonce validation, meaning it would accept and apply option changes from any forged request, not just ones the plugin's own settings page had actually generated.
The impact followed the exact pattern described above: an attacker crafts a malicious page containing a forged request to that endpoint. If a logged-in site administrator is tricked into visiting it — a link in an email, a comment, a compromised ad — their browser fires the forged request with their session cookie attached, and the plugin (having no nonce check to catch it) accepts it. In this specific case, the forged request could change WordPress's default registration role to Administrator and enable open user registration — meaning the attacker's actual next step is simply to register a normal new account and inherit full admin access, no password guessing or brute force required at any point.
Why this is a plugin problem, not (usually) a WordPress-core problem
The same pattern as most other WordPress vulnerability classes holds here too: WordPress core's own request handlers get consistent nonce checks and have for years, so core CSRF bugs are rare. Almost every real-world WordPress CSRF disclosure — like the LoginPress case above — is a plugin or theme adding its own request handler and skipping the nonce check that core's own equivalent features already have. Any plugin feature that changes a setting, performs a bulk action, or triggers an account-level change via a URL or form submission is a candidate if its developer didn't wire up nonce verification correctly.
What to actually do about it
- Keep every plugin and theme updated, promptly. Virtually every real CSRF disclosure — including the LoginPress case above — is a patched, known issue by the time it's public. Sites that stay current simply aren't exposed to it.
- Don't stay logged into wp-admin in a browser tab while casually browsing unrelated, untrusted sites, links from unsolicited email, or unfamiliar ad networks. CSRF needs an active, authenticated session to work with — logging out when you're not actively using the dashboard closes that window.
- Be cautious with plugins that add their own settings pages, bulk actions, or account-management endpoints — these are exactly the features where a developer is most likely to have skipped a nonce check that WordPress core's built-in equivalent already has.
Get file, account, and login findings explained the moment they happen — not months later.
Install free →