Principiante
CVSS 6.5 - Medio
18 min lectura
Cross-Site Request Forgery (CSRF)
Fuerza a un usuario autenticado a ejecutar acciones no deseadas en una aplicación web
¿Qué es CSRF?
CSRF es un ataque que engaña al navegador de un usuario autenticado para que envíe una petición HTTP a una aplicación web en la que está autenticado, ejecutando una acción no autorizada.
Impacto
- • Transferencias de dinero no autorizadas
- • Cambio de email/contraseña de la víctima
- • Creación/modificación/eliminación de datos
- • Cambios en configuración de cuenta
- • Acciones privilegiadas si la víctima es admin
Ejemplo de Ataque
// 1. Sitio bancario vulnerable
// POST /transfer
// Cookie: sessionId=valid_user_session
// 2. Atacante crea página maliciosa
<!-- evil-site.com/attack.html -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker_account">
<input type="hidden" name="amount" value="10000">
</form>
</body>
</html>
// 3. Víctima visita evil-site.com
// El navegador envía automáticamente la cookie de sesión
// La transferencia se ejecuta sin que la víctima lo sepaVectores de Ataque
1. Formularios Auto-Submit
<form action="https://target.com/delete-account" method="POST"> <input type="hidden" name="confirm" value="yes"> </form> <script>document.forms[0].submit();</script>
2. IMG Tags con GET Requests
<!-- En email o sitio malicioso -->
<img src="https://bank.com/transfer?to=attacker&amount=1000"
style="display:none">3. AJAX Cross-Origin (si CORS mal configurado)
fetch('https://target.com/api/change-email', {
method: 'POST',
credentials: 'include', // enviar cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'attacker@evil.com' })
});Defensas contra CSRF
1. CSRF Tokens (Synchronizer Token)
Token único y aleatorio en cada formulario/petición.
// Backend - Generar token
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// HTML - Incluir token en formulario
<form method="POST" action="/transfer">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input name="to" placeholder="Destinatario">
<input name="amount" placeholder="Cantidad">
<button>Transferir</button>
</form>
// Backend - Verificar token
app.post('/transfer', csrfProtection, (req, res) => {
// csurf middleware verifica automáticamente
// Si el token no coincide, rechaza con 403
processTransfer(req.body);
});
// Next.js API Route
import { getCsrfToken } from 'next-auth/csrf';
export default async function handler(req, res) {
const csrfToken = await getCsrfToken({ req });
if (req.method === 'POST') {
if (req.body.csrfToken !== csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// Procesar petición
}
}2. SameSite Cookies
Prevenir envío de cookies en peticiones cross-site.
// Express.js
res.cookie('sessionId', token, {
httpOnly: true,
secure: true,
sameSite: 'strict' // o 'lax'
});
// SameSite=Strict: No envía cookie en ninguna petición cross-site
// SameSite=Lax: Envía cookie solo en navegación GET top-level
// SameSite=None: Requiere Secure (HTTPS)
// Next.js
export default function handler(req, res) {
res.setHeader('Set-Cookie',
'session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/'
);
}3. Double Submit Cookie
// 1. Servidor envía token en cookie
res.cookie('csrf-token', randomToken, { httpOnly: false });
// 2. Cliente lee cookie y envía en header
const csrfToken = getCookie('csrf-token');
fetch('/api/action', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
});
// 3. Servidor compara cookie vs header
app.post('/api/action', (req, res) => {
const cookieToken = req.cookies['csrf-token'];
const headerToken = req.headers['x-csrf-token'];
if (cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
// Procesar
});4. Custom Request Headers
AJAX requests con headers custom (navegador previene en cross-origin).
// Cliente
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest' // header custom
},
body: JSON.stringify(data)
});
// Servidor verifica presencia del header
app.post('/api/action', (req, res) => {
if (!req.headers['x-requested-with']) {
return res.status(403).json({ error: 'Missing custom header' });
}
// Procesar
});5. Verificar Origin/Referer Headers
app.post('/api/action', (req, res) => {
const origin = req.headers.origin || req.headers.referer;
const allowedOrigins = ['https://myapp.com'];
if (!origin || !allowedOrigins.some(o => origin.startsWith(o))) {
return res.status(403).json({ error: 'Invalid origin' });
}
// Procesar
});Mejores Prácticas
- Nunca usar GET para acciones que cambian estado - Solo POST, PUT, DELETE
- Implementar CSRF tokens en todos los formularios - Especialmente acciones sensibles
- Usar SameSite=Strict o Lax en cookies - Defensa en profundidad
- Re-autenticar para acciones críticas - Transferencias, cambio de password
- Implementar rate limiting - Limitar peticiones sospechosas
Recursos Adicionales
Siguiente Paso
Aprende sobre vulnerabilidades de control de acceso
Insecure Direct Object References (IDOR)→