
Fallos de seguridad que no puedes ignorar
Cómo evitar dejar la puerta trasera de tu código abierta
¿Sabes cuál es el mayor problema de seguridad de tu aplicación o SaaS? No, no es ese exploit sofisticado que viste en el último evento de seguridad, ni un ataque de red súper complejo. La mayor amenaza está literalmente "entre la silla y el monitor". Sí, estoy hablando de ti, desarrollador, que estás dejando la puerta trasera abierta de par en par sin siquiera darte cuenta.
El problema no es que tu código pueda ser pirateado. El problema es que, en muchos casos, ya lo has dejado prácticamente abierto. ¿Y lo peor? Muchas de estas vulnerabilidades son tan básicas que dejarlas ahí después de leer este artículo sería casi una autosabotaje.
No me voy a centrar en grandes CVEs o vulnerabilidades de red. Después de todo, probablemente estás alojando en Vercel, Google, Amazon u otra empresa con capas decentes de protección. Probablemente estás utilizando un ORM que dificulta la inyección de SQL y construyendo interfaces con JSX que reducen los riesgos de XSS. Esto no significa que estos ataques sean imposibles, pero hay cientos de artículos sobre estas vulnerabilidades por ahí. Vamos a centrarnos en lo que realmente está dejando tu aplicación vulnerable en el día a día.
Webhooks: La puerta trasera desprotegida
A todo el mundo le encantan los webhooks, ¿verdad? Es una forma práctica de integrar sistemas. Pero, ¿te has dado cuenta de que casi todos usan la misma ruta predeterminada? /api/webhook
o /api/hook
. Esto por sí solo no es un problema, pero se convierte en uno cuando cualquier persona malintencionada puede adivinarla fácilmente y, peor aún, enviarte datos falsos.
Imagina un usuario enviando una solicitud a tu ruta de webhook fingiendo ser tu pasarela de pagos, confirmando una compra que nunca ocurrió. ¿Aterrador, no?
La solución es simple: siempre que configures un webhook, utiliza una firma secreta. En Stripe, recibes el encabezado stripe-signature
, mientras que en Mercado Pago (de Brasil) existe el x-signature
. Tu backend debe validar este código para garantizar que la solicitud provino de la fuente correcta. Cuando un usuario malicioso intenta burlar el sistema, tu API simplemente se quejará de la falta de firma.
IDOR: Cuando tu aplicación entrega datos de otros usuarios
IDOR (Insecure Direct Object Reference) es esa falla en la que no se verifican los permisos al acceder a objetos a través de la API. Es más común de lo que imaginas y da escalofríos solo de pensar en las consecuencias.
Imagina que tienes un endpoint /api/purchase/:id
donde los usuarios acceden a los detalles de sus compras. El código es simple: el usuario hace una solicitud GET a /api/purchase/123
y tú devuelves los detalles. Pero, ¿y si la compra con ID 123 no es suya? ¡Felicidades, acabas de exponer los datos de otro cliente!
Otro error clásico es tener un endpoint PATCH /api/profile
donde el servidor recibe el ID del usuario en el cuerpo de la solicitud. ¡Nunca hagas eso! El ID del usuario siempre debe provenir de la sesión, del JWT o de otro mecanismo de autenticación, nunca del cuerpo de la solicitud que puede ser fácilmente manipulado.
La regla es clara: valida siempre quién está solicitando acceso antes de mostrar, editar o eliminar lo que sea.
Exposición de datos: Menos es más
Este problema es más sutil, pero igualmente devastador. Digamos que creaste un marketplace donde es posible acceder a los detalles de un producto a través de una solicitud GET con el ID del producto. La API devuelve los detalles del producto y alguna información del vendedor. Hasta aquí, todo bien.
Pero, ¿y si, junto con el nombre y la foto del vendedor, también envías el correo electrónico, el número de identificación fiscal, el teléfono, la dirección e incluso la contraseña encriptada? Esto sucede cuando buscas el producto, traes todos los datos del vendedor relacionado y te olvidas de filtrar lo que debe o no ser enviado al frontend.
La solución es: envía solo lo que realmente es necesario. Si el frontend solo necesita el nombre y la foto del vendedor, envía solo eso. No caigas en la trampa de pensar "ah, el frontend solo usará lo que necesita". Protege a tus usuarios limitando los datos expuestos desde el principio.
Rate Limit: Protegerse de la cantidad
No implementar límites de solicitudes puede no parecer una vulnerabilidad directa, pero es sin duda una de las cosas que más perjudica un producto. Imagina que tu aplicación tiene una API pública para crear publicaciones. Sin limitación, un atacante puede automatizar esta solicitud y crear miles de publicaciones falsas en cuestión de segundos.
Esto no solo contamina tu base de datos y degrada la experiencia de los usuarios, sino que también puede costarte caro, literalmente. El almacenamiento en la base de datos no es gratuito, y sin protección, terminarás pagando por datos basura.
Otro ejemplo: una API para enviar correos electrónicos. Un atacante podría fácilmente exceder tu límite de envío, obligándote a pagar tarifas adicionales para continuar operando normalmente.
En las páginas de inicio de sesión, la ausencia de rate limit permite ataques de fuerza bruta para descubrir contraseñas. Implementa CAPTCHAs, limitadores de intentos u otros mecanismos de protección en endpoints sensibles o que impliquen costos.
Mass Assignment: Cuando te conviertes en admin sin querer
Esta vulnerabilidad es una de mis favoritas porque es tan simple y tan peligrosa al mismo tiempo. Cuando tienes una ruta PATCH para modificar alguna información de un objeto, como el nombre de usuario o la descripción de un producto, esta falla permite modificar cualquier propiedad del objeto.
Además de cambiar tu nombre de usuario, podrías cambiar tu rol a administrador o cualquier otra propiedad del sistema. ¿La solución? Define explícitamente qué campos pueden ser modificados. No dejes esto abierto.
TOCTTOU: La vulnerabilidad más elegante
Time of Check to Time of Use (TOCTTOU) es una vulnerabilidad fascinante que ocurre cuando hay un intervalo entre la verificación de una condición y el uso del resultado de esa verificación.
El ejemplo clásico es una operación bancaria. Tienes 100€ en tu cuenta y solicitas un retiro de ese monto. El programa verifica si tienes el dinero y, si es así, realiza el retiro. Pero, ¿y si envías dos solicitudes simultáneamente?
En la práctica, es difícil enviar exactamente dos solicitudes al mismo tiempo, por lo que generalmente se envían varias. Debido al retraso de la red, varias instancias de la función se ejecutarán en paralelo, todas pasando por la verificación de saldo y luego realizando el retiro varias veces.
Esto funciona para muchos escenarios: likes en publicaciones, compra de boletos limitados y varias otras operaciones que dependen de recursos finitos.
La solución es relativamente simple: es necesario "bloquear" los recursos críticos mientras se están utilizando. Puedes usar semáforos, sistemas de colas, pero lo más común son las transacciones en bases de datos, donde la verificación y la acción ocurren de forma atómica: o la operación se completa por completo, o no ocurre nada, sin interrupciones.
Confiar en el frontend: El error fundamental
Este es quizás el error más básico y también el más común: creer que las validaciones en el frontend son suficientes para garantizar la seguridad de tu aplicación.
Mucha gente piensa: "Si el botón está desactivado, la solicitud nunca se enviará" o "Si el frontend muestra que el usuario tiene 10€, solo puede retirar 10€, al fin y al cabo fue mi servidor el que informó ese valor". No es exactamente así.
Cualquier regla implementada en el frontend puede ser ignorada o manipulada por el usuario. Esto es especialmente cierto en aplicaciones que utilizan Client-Side Rendering (CSR).
Para ilustrar: imagina una aplicación de finanzas en la que el botón de retiro se desactiva cuando no tienes saldo. Un usuario malintencionado puede inspeccionar fácilmente el código, encontrar la renderización condicional que controla esta visualización, alterar las variables relevantes en tiempo de ejecución y activar el botón.
Claro que, si el backend está bien implementado, la solicitud de retiro fallará. Pero no siempre es así. Piensa en un e-commerce que calcula los precios en el frontend y envía ese valor al backend. Si alguien intercepta la solicitud y cambia el precio de 500€ a 50€, y tu backend confía ciegamente en esa información, tendrás un problema grave.
La regla es clara: recalcula siempre los precios y verifica todas las condiciones en el servidor. Nunca, pero nunca, confíes en el frontend para la seguridad. Si tu sistema depende únicamente del frontend para la protección, alguien lo va a burlar.
¿Tu aplicación está 100% segura?
No. Y nunca lo estará.
La seguridad al 100% no existe ni existirá nunca. Piensa: cada programa que usas para crear o alojar tu aplicación fue hecho por seres humanos, y los humanos cometen errores. Puede ser un bug en tu código, en el framework que usas, en la base de datos, en el sistema operativo… Todo fue construido por personas que, en algún momento, pueden haber cometido un solo error capaz de comprometer todo el sistema.
Incluso si tu código fuera perfecto (lo cual es prácticamente imposible), nada impediría que alguien invadiera físicamente tu servidor, o que algún empleado de tu proveedor de alojamiento robara tus credenciales, o que cayera en un phishing sofisticado.
La seguridad es un juego infinito. El objetivo no es estar 100% seguro, sino dificultar al máximo el ataque y reducir los daños si algo sale mal. Es un proceso continuo de mejora, vigilancia y adaptación.
Hablando de eso, en Yes Marketing ya estamos un paso adelante en este juego. Implementamos medidas para mitigar todas estas vulnerabilidades en nuestros sistemas. Nuestro equipo no solo corrigió las brechas más comunes, sino que también estableció un proceso de monitoreo continuo para identificar posibles nuevas fallas. Dormimos más tranquilos sabiendo que no solo estamos reaccionando a problemas, sino buscando activamente formas de mejorar nuestra seguridad antes de que ocurra cualquier incidente. Como decimos por aquí: más vale prevenir que tener que explicarle al jefe por qué el sistema fue pirateado, ¿verdad?
Quizás hayas reconocido algunas de estas fallas en tus propios proyectos. No te desesperes: identificar el problema es el primer paso para resolverlo. La seguridad no tiene que ser complicada, pero tiene que tomarse en serio.
Y recuerda: si después de leer todo esto sigues cometiendo estos errores, ya no es descuido. Es autosabotaje.