iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Classic Form CSRF

Classic form CSRF: HTML form on attacker.com posts to victim.com with the victim’s session cookie attached. The defence is a server-issued, server-verified anti-forgery token in the form body.

The attack + the standard cure

EXAMPLE
<!-- THE ATTACK — hosted on attacker.example -->
<!doctype html>
<html>
    <body onload="document.forms[0].submit()">
        <form action="https://bank.example/api/transfer" method="POST">
            <input type="hidden" name="to"     value="attacker_account_id">
            <input type="hidden" name="amount" value="5000">
        </form>
    </body>
</html>
<!--
    Victim is logged in to bank.example.
    Browser auto-sends bank.example's session cookie on the cross-site POST.
    Without CSRF defences, server processes the transfer.
-->

<!-- THE CURE — anti-forgery token per session -->
<!-- Server-side: Express + a tiny token store -->
import crypto from 'node:crypto';

function issueCsrfToken(req) {
    req.session.csrf ??= crypto.randomBytes(32).toString('hex');
    return req.session.csrf;
}

function verifyCsrf(req, res, next) {
    const sent = req.body?._csrf || req.headers['x-csrf-token'];
    if (!sent || sent !== req.session.csrf) return res.status(403).end();
    next();
}

app.get('/transfer', (req, res) => {
    const token = issueCsrfToken(req);
    res.render('transfer', { csrf: token });
});

app.post('/transfer', verifyCsrf, async (req, res) => {
    /* … move money … */
});

<!-- Template -->
<form action="/transfer" method="POST">
    <input type="hidden" name="_csrf" value="{{ csrf }}">
    <input name="to">
    <input name="amount">
    <button>Send</button>
</form>

<!-- Defence in depth — pair the token with SameSite cookies + Origin check -->
Set-Cookie: sid=…; HttpOnly; Secure; SameSite=Lax

// Express middleware
app.use((req, res, next) => {
    if (['POST','PUT','PATCH','DELETE'].includes(req.method) &&
        !ALLOWED_ORIGINS.has(req.headers.origin)) {
        return res.status(403).end();
    }
    next();
});

Why it matters

Three layers stop classic form CSRF: SameSite cookies (most browsers default to Lax), an anti-forgery token in the form, and an Origin check on the server. Skip any one and the attack works.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// VULNERABLE: state-changing GET / unauthenticated POST
// SAFE: only mutate via POST/PUT/DELETE, require a CSRF token in the body,
// validate it server-side against the session.
Try it Yourself »

Discussion

Loading…