Malware
Cross-site request forgery
Also called CSRF.
Cross-site request forgery is making a logged-in user's browser send a state-changing request to a site, using the cookies the browser attaches on its own. The site sees a valid session and has no way to tell the user did not mean it.
How it is measured
Test with a form on a different origin that posts to your endpoint. If the action succeeds with only a session cookie and no token, the endpoint is open. Check for anti-CSRF tokens in forms, `SameSite` on session cookies, and whether the server verifies `Origin` or `Referer`.
Read your routes. Every POST, PUT, PATCH, or DELETE that changes data should require a token tied to the session, or use a header that cross-site forms cannot set.
Worked example
A WordPress site owner is logged in as admin and opens a link in a forum. The page contains a hidden form posting to `/wp-admin/admin-post.php?action=update_email` with the attacker's address. The plugin checks the cookie but not a nonce, so the admin email changes.
The attacker then requests a password reset. The plugin author adds `wp_verify_nonce()` to the handler, and the same hidden form now returns a 403.
How it differs
CSRF abuses the browser's trust in your site by sending a request on the victim's behalf. XSS abuses the victim's trust in your site by running attacker script inside it. A CSRF attack cannot read the response; XSS can. A strong anti-CSRF token is useless if XSS lets the attacker read it. SameSite cookies reduce CSRF but do not replace tokens on older browsers.
Common errors
Protecting only the login form. Using GET for actions that change data. Checking tokens only when they are present. Accepting a token from the URL query string. Assuming JSON endpoints are safe because forms cannot send JSON, even though some browsers and plugins can.
In practice
Set `SameSite=Lax` or `Strict` on session cookies. Add a per-session token to every state-changing form and verify it on the server. List your POST routes this week and confirm each one rejects a request with no token.