Blog
9 min de lecturaseguridad · formularios · web

Errores de seguridad comunes en formularios web

Los formularios parecen una parte sencilla de una web, pero concentran muchos riesgos: validación débil, XSS, CSRF, subida de archivos, spam, filtrado de datos y errores de privacidad.

Un formulario parece una parte sencilla de una web. Unos inputs, un botón, un mensaje de confirmación y poco más.

Pero en cuanto ese formulario envía datos a un servidor, deja de ser solo una pieza de interfaz. Se convierte en una entrada directa a tu aplicación.

Ahí empiezan los problemas.

Cuando estás aprendiendo desarrollo web es fácil pensar en los formularios desde la experiencia de usuario: que se vea bien, que avise si falta un campo, que el botón tenga estado de carga, que el mensaje de éxito sea claro. Todo eso importa. Pero si solo miras el formulario desde el frontend, te puedes dejar fuera la parte más importante: qué puede hacer alguien que no usa tu interfaz como tú esperas.

Un atacante no tiene por qué rellenar tu formulario desde el navegador. Puede modificar el HTML, desactivar JavaScript, repetir peticiones, cambiar campos ocultos, enviar tipos de archivo falsos o probar payloads que nunca pasarían por tu diseño.

Por eso conviene conocer los errores de seguridad más comunes en formularios web.

1. Confiar en la validación del frontend

La validación en el navegador mejora la experiencia. Sirve para avisar rápido al usuario si falta un email, si una contraseña es demasiado corta o si un campo obligatorio está vacío.

Pero no es una barrera de seguridad.

Este es uno de los primeros errores que conviene quitarse de la cabeza:

<input type="email" required maxlength="80" />

Esto ayuda, pero no protege por sí solo.

Un usuario puede abrir DevTools y quitar el required. Puede cambiar el maxlength. Puede enviar la petición con curl, Postman o un script. Puede saltarse completamente tu formulario.

La validación importante debe existir en el servidor.

El frontend puede decir: este campo parece incorrecto.

El backend debe decidir: este dato se acepta o se rechaza.

Lo sano es usar ambas capas:

  • Frontend para feedback rápido y accesible.
  • Backend para seguridad, reglas de negocio y persistencia.

Si una regla importa, no puede vivir solo en JavaScript del cliente.

2. Validar solo la forma y no el significado

No basta con comprobar que un dato tiene pinta de válido. También hay que comprobar si tiene sentido dentro del contexto.

Por ejemplo, un campo de fecha puede tener formato correcto y aun así ser inválido:

  • Una fecha de inicio posterior a la fecha de fin.
  • Una reserva en un día que no está disponible.
  • Una edad negativa enviada manipulando la petición.
  • Una cantidad mayor que el stock real.

OWASP diferencia entre validación sintáctica y validación semántica. La primera comprueba la forma del dato. La segunda comprueba si el dato tiene sentido para el negocio.

En formularios reales necesitas las dos.

Un email puede tener formato de email, pero quizá pertenece a un dominio temporal que no quieres permitir. Un precio puede ser un número, pero no debería venir decidido por el cliente. Un role=user enviado desde un formulario no debería poder cambiarse a role=admin porque alguien ha editado el HTML.

La pregunta práctica es:

¿Este dato solo tiene que tener buena forma o también debe cumplir una regla del sistema?

Si afecta a permisos, dinero, disponibilidad, stock, identidad o estado de una cuenta, no puedes fiarte de lo que llega del navegador.

3. Usar listas negras como defensa principal

Otro error común es intentar bloquear caracteres o palabras peligrosas.

Por ejemplo:

if (message.includes('<script>')) {
  throw new Error('Mensaje no permitido');
}

Parece una solución rápida, pero es frágil.

Los atacantes no necesitan escribir exactamente <script> para provocar problemas. Hay muchas formas de ofuscar, cambiar contexto, usar atributos, entidades, URLs o combinaciones que se saltan filtros simples.

Además, puedes bloquear texto legítimo. Si alguien escribe una explicación técnica y menciona una etiqueta HTML, no necesariamente está atacando tu aplicación.

Como regla general, es mejor permitir lo esperado que intentar adivinar todo lo malo.

Para datos estructurados, usa listas permitidas:

  • Países disponibles.
  • Roles posibles.
  • Categorías existentes.
  • Tipos de archivo aceptados.
  • Rangos de números.
  • Longitudes mínimas y máximas.

Para texto libre, la solución no suele ser prohibir medio teclado. La clave está en guardar el dato de forma segura y codificarlo correctamente cuando lo vuelves a mostrar.

4. Pintar datos del usuario sin escapar

Este es uno de los errores más peligrosos: recibir texto de un formulario y mostrarlo después como HTML sin tratarlo como dato no confiable.

Imagina un formulario de contacto, comentarios o reseñas. Si alguien envía este contenido:

<img src=x onerror=alert('xss')>

Y tu aplicación lo inserta como HTML, puedes abrir la puerta a XSS.

El problema no es solo que aparezca una alerta. XSS puede permitir robo de sesiones, acciones en nombre del usuario, modificación de la página o captura de información.

La defensa no consiste únicamente en validar la entrada. También hay que codificar la salida según el contexto.

No es lo mismo colocar un dato en:

  • Texto HTML.
  • Un atributo HTML.
  • Una URL.
  • JavaScript.
  • CSS.

Cada contexto necesita un tratamiento distinto.

Por eso innerHTML debería darte respeto cuando el contenido viene de un usuario.

Si solo necesitas mostrar texto, usa APIs que traten el valor como texto, no como HTML. Si de verdad necesitas permitir HTML enriquecido, utiliza una librería de saneamiento pensada para eso y limita las etiquetas permitidas.

Un formulario no termina cuando guardas el dato. También tienes que pensar dónde va a aparecer después.

5. Olvidar la protección contra CSRF

CSRF ocurre cuando un sitio externo consigue que el navegador del usuario envíe una petición autenticada a tu aplicación.

El usuario puede estar logueado en tu web. Si tus cookies se envían automáticamente y tu servidor acepta una acción sensible sin más protección, otra página podría intentar provocar esa acción.

Esto afecta especialmente a formularios que cambian estado:

  • Cambiar email.
  • Cambiar contraseña.
  • Borrar una cuenta.
  • Publicar contenido.
  • Modificar configuración.
  • Enviar una compra.

La protección típica es usar tokens CSRF únicos e impredecibles, además de configurar bien las cookies con SameSite, Secure y HttpOnly cuando corresponda.

También ayuda comprobar cabeceras como Origin o Referer, pero no debería ser la única defensa en acciones sensibles.

La idea sencilla es:

No aceptes que una acción importante se ejecute solo porque llega una cookie válida.

Esa petición también debe demostrar que salió de un formulario o flujo legítimo de tu aplicación.

6. Guardar información sensible en campos ocultos

Los campos ocultos no son seguros. Solo están ocultos visualmente.

Este error aparece mucho en formularios donde se envía información como:

<input type="hidden" name="price" value="49.99" />
<input type="hidden" name="role" value="user" />
<input type="hidden" name="discount" value="10" />

Un usuario puede cambiar esos valores antes de enviar el formulario.

Si el servidor confía en ellos, tienes un problema.

Los campos ocultos pueden servir para transportar identificadores o estado no sensible, siempre que el backend los valide. Pero no deberían ser la fuente de verdad para precios, permisos, descuentos, propietarios de recursos o cualquier dato que afecte a seguridad.

La regla práctica:

El cliente puede sugerir. El servidor decide.

Si el precio de un producto importa, lo calcula el servidor. Si el usuario tiene un rol, lo consulta el servidor. Si un descuento existe, el servidor lo valida.

7. Subir archivos sin controles serios

Los formularios de subida de archivos son especialmente delicados.

No basta con revisar la extensión en el frontend:

<input type="file" accept="image/png,image/jpeg" />

accept ayuda al usuario, pero no protege el servidor.

Un atacante puede subir un archivo con extensión falsa, un tamaño enorme, contenido inesperado o un nombre diseñado para provocar problemas.

Algunas reglas básicas:

  • Validar tamaño máximo en servidor.
  • Usar una lista permitida de tipos.
  • No confiar solo en el nombre o extensión.
  • Generar un nombre nuevo para almacenar el archivo.
  • No permitir rutas controladas por el usuario.
  • Servir archivos con el Content-Type correcto.
  • Analizar archivos si el contexto lo requiere.
  • Evitar tipos ejecutables o peligrosos.

También conviene separar almacenamiento y ejecución. Un archivo subido por un usuario no debería acabar en una carpeta donde pueda ejecutarse como código.

Subir una imagen de perfil parece una función pequeña. En seguridad, no lo es.

8. Dar demasiada información en los errores

Los mensajes de error son útiles para el usuario, pero también pueden dar pistas.

No es lo mismo decir:

No hemos podido procesar la solicitud.

Que decir:

Error SQL en users.password_hash línea 42.

En formularios de login también hay que tener cuidado. Si respondes:

  • Este email no existe.
  • La contraseña es incorrecta.

Estás permitiendo enumerar usuarios. A veces conviene responder con un mensaje más neutro:

Las credenciales no son correctas.

Esto no significa ocultar todos los errores. El usuario necesita saber qué corregir. Pero no necesita ver trazas internas, nombres de tablas, rutas del servidor o detalles de validación que ayuden a atacar.

Buen equilibrio:

  • Mensajes claros para el usuario.
  • Logs detallados para el desarrollador.
  • Nada de información interna en la respuesta pública.

9. No limitar spam, abuso o automatización

Un formulario público puede recibir spam, pruebas automáticas y abuso.

Esto afecta a formularios de contacto, comentarios, registro, newsletter, recuperación de contraseña y cualquier endpoint expuesto.

Algunas defensas posibles:

  • Rate limiting por IP o usuario.
  • Honeypot accesible correctamente.
  • Tokens de formulario.
  • CAPTCHA solo si de verdad hace falta.
  • Límites de longitud.
  • Bloqueo temporal ante demasiados intentos.
  • Validación de email cuando sea necesaria.
  • Alertas si el volumen cambia de golpe.

No todo formulario necesita CAPTCHA. De hecho, meter CAPTCHA por defecto puede empeorar la experiencia. Pero sí conviene pensar qué pasa si alguien envía el formulario 1.000 veces.

La pregunta útil:

¿Qué coste tiene para mí que este formulario se pueda automatizar?

Si dispara emails, crea cuentas, escribe en base de datos o consume recursos, necesitas límites.

10. Pedir más datos de los necesarios

La seguridad también tiene que ver con privacidad.

Un formulario debería pedir los datos que necesita, no los que podrían venir bien algún día.

Cada campo extra aumenta responsabilidad:

  • Más información que almacenar.
  • Más datos que proteger.
  • Más impacto si hay una fuga.
  • Más fricción para el usuario.

Si un formulario de contacto solo necesita nombre, email y mensaje, quizá no hace falta pedir teléfono, empresa, cargo, ciudad y presupuesto.

Menos datos no solo mejora conversión. También reduce riesgo.

Además, hay que cuidar dónde terminan esos datos: email, CRM, hoja de cálculo, logs, analítica, herramientas externas. A veces se protege el formulario y luego se filtra información en un log o en una integración.

11. No probar el formulario fuera del camino feliz

El camino feliz es lo que pruebas cuando todo sale bien:

  1. Rellenas campos válidos.
  2. Pulsas enviar.
  3. Ves mensaje de éxito.

Eso no basta.

Un formulario debería probarse también con casos incómodos:

  • Campos vacíos.
  • Texto demasiado largo.
  • Caracteres raros.
  • Emails con formatos límite.
  • Fechas imposibles.
  • Números negativos.
  • Archivos grandes.
  • Doble envío rápido.
  • Recarga después de enviar.
  • Peticiones repetidas.
  • JavaScript desactivado.
  • Respuesta lenta del servidor.

No necesitas convertir cada formulario en una auditoría completa, pero sí salir del uso perfecto.

Muchos errores aparecen cuando el usuario, la red o el atacante no siguen el guion.

Una checklist razonable antes de publicar

Antes de publicar un formulario, revisaría esto:

  • ¿La validación importante existe en servidor?
  • ¿Hay límites de longitud, tipo y formato?
  • ¿Las reglas de negocio se validan fuera del cliente?
  • ¿Los datos del usuario se escapan al mostrarlos?
  • ¿Hay protección CSRF en acciones autenticadas?
  • ¿Los campos ocultos no contienen decisiones sensibles?
  • ¿Las subidas de archivo se validan y almacenan con cuidado?
  • ¿Los errores públicos no filtran información interna?
  • ¿Existe alguna defensa contra spam o abuso?
  • ¿Se piden solo los datos necesarios?
  • ¿Se han probado casos fuera del camino feliz?

No es una lista perfecta, pero ya cambia la forma de mirar un formulario.

Lo importante

Un formulario no es solo una interfaz. Es una frontera entre el usuario y tu sistema.

Como desarrollador frontend puedes hacer mucho para que sea claro, accesible y cómodo. Pero también necesitas entender qué partes no pueden depender del navegador.

La idea que me quedo es esta:

Todo dato que llega de un formulario debe tratarse como no confiable hasta que el servidor lo valide.

Esto no significa programar con paranoia. Significa aceptar una realidad básica de la web: el usuario controla el cliente.

Si tienes eso presente, empiezas a diseñar formularios de otra manera. Validación frontend para ayudar. Validación backend para decidir. Salida codificada para mostrar. Límites para evitar abuso. Y solo los datos que realmente necesitas.

Ahí un formulario deja de ser un bloque más de HTML y empieza a formar parte seria de la seguridad de la aplicación.