Seguridad web: buenas prácticas capa por capa (guía 2026)
Punto clave
La seguridad web funciona por capas: protege el dominio y el DNS (registrar lock, DNSSEC, CAA), fuerza TLS moderno con HSTS, envía un conjunto estricto de cabeceras de seguridad HTTP, aplica hash a las contraseñas con Argon2id respaldado por MFA y límites de tasa, valida las entradas y aplica el control de acceso en el servidor, fija y audita las dependencias, cierra todos los puertos que no uses y registra lo suficiente para detectar una brecha. Cada práctica de esta guía incluye la configuración exacta y una forma gratuita de comprobar que de verdad está activa, porque un control que no has probado es un control que no tienes.
Compruébelo ahora

Empiece a leer
- Qué significan de verdad las buenas prácticas de seguridad web
- La pila de seguridad web: seis capas que proteger
- Capa 1: protege el dominio y el DNS
- Capa 2: HTTPS y TLS bien configurados
- Capa 3: cabeceras de seguridad HTTP
- Capa 4: una autenticación que resista el credential stuffing
- Sesiones, cookies y CSRF
- Inyección y XSS: valida la entrada, codifica la salida
- Broken Access Control: el riesgo número uno
- Capa 5: dependencias y cadena de suministro de software
- Capa 6: hardening del servidor (puertos, parches, secretos y copias de seguridad)
- Autenticación del correo: SPF, DKIM y DMARC
- Registra, monitoriza y prepárate para la brecha
- Ponlo a prueba: escáneres, pentests y comprobaciones continuas
- Checklist de seguridad web
- Cómo encaja con el OWASP Top 10:2025
- Preguntas frecuentes sobre seguridad web
Qué significan de verdad las buenas prácticas de seguridad web
Las buenas prácticas de seguridad web son el conjunto de controles que impiden que un sitio o una aplicación web acabe en manos de un atacante, sea desfigurado, se use para atacar a sus propios visitantes o pierda datos sin que nadie lo note. No son un producto ni un ajuste concreto. Son una pila de decisiones que empieza en el registrador del dominio y sigue por el DNS, el TLS, las cabeceras HTTP que envía tu servidor, el código que gestiona los inicios de sesión y las entradas, los paquetes de terceros sobre los que construyes, el propio servidor y, por último, los registros que te avisan de que algo ha ido mal.
La razón para pensar en capas es que los atacantes lo hacen. El 2026 Data Breach Investigations Report de Verizon, que abarca más de 22 000 brechas entre noviembre de 2024 y octubre de 2025, descubrió que el 31 % de las brechas empezó con una vulnerabilidad de software explotada, frente al 20 % un año antes y, por primera vez, por delante de las credenciales robadas (13 %), con el phishing estable en el 16 %. El ransomware apareció en el 48 % de las brechas, frente al 44 %. Basta un solo parche pendiente, una clave de API filtrada o una página de administración sin límite de intentos. Ninguno de los controles de esta guía es exótico; los sitios que sufren una brecha suelen ser los que descuidaron lo básico en una capa mientras pulían otra.
Esta guía está organizada de fuera hacia dentro. Cada práctica te dice por qué importa, qué configurar exactamente y cómo verificarla con un comando o una herramienta gratuita como el Verificador de Cabeceras HTTP o el Verificador de Certificado SSL, porque un ajuste que crees activo pero nunca has confirmado no protege nada.
Check a site's security headers
La pila de seguridad web: seis capas que proteger
Todo ataque a un sitio web recae en una de seis capas. La tabla siguiente es el mapa del resto de la guía: qué vive en cada capa, cómo suele atacarse y qué comprobación gratuita confirma que tu defensa está realmente en su sitio.
| Capa | Qué hacen aquí los atacantes | Prácticas clave | Verificar con |
|---|---|---|---|
| 1. Dominio y DNS | Secuestran el dominio, cambian los servidores de nombres, toman subdominios huérfanos, obtienen certificados fraudulentos | Registrar lock, MFA, DNSSEC, CAA, auditoría de registros obsoletos | Consulta WHOIS, Verificación de salud DNS, Buscador de Subdominios |
| 2. Transporte (TLS) | Degradan la conexión a HTTP, eliminan el cifrado, explotan cifrados antiguos, aprovechan certificados caducados | TLS 1.2+, HSTS (precarga opcional), renovación automatizada | Verificador de Certificado SSL |
| 3. Cabeceras HTTP | Inyectan scripts (XSS), enmarcan el sitio para hacer clickjacking, provocan MIME sniffing, filtran el referrer | CSP, frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Verificador de Cabeceras HTTP |
| 4. Aplicación | Credential stuffing, inyección, control de acceso roto, CSRF, subidas de archivos inseguras | Argon2id + MFA + límites de tasa, consultas parametrizadas, autorización en el servidor, cookies SameSite | Revisión de código, ZAP, Prueba de Fuerza de Contraseña |
| 5. Dependencias | Paquetes envenenados, CVE conocidos en bibliotecas, pipelines de CI comprometidos | Lockfiles, auditorías, versiones fijadas, SBOM, tokens de mínimo privilegio | npm audit, Dependabot, Trivy |
| 6. Servidor y operaciones | Puertos abiertos, credenciales por defecto, parches pendientes, sin copias de seguridad, sin registros | Firewall, SSH solo con claves, parches, copias 3-2-1, registro centralizado | Verificador de Puertos, Verificador de Lista Negra IP |
Trabaja de arriba abajo. Las capas 1 a 3 son configuración que puedes terminar en una tarde y protegen todas las páginas a la vez. Las capas 4 a 6 son hábitos continuos que deben formar parte de tu revisión de código y de tu proceso de despliegue.
Capa 1: protege el dominio y el DNS
Todo lo demás en esta guía da por hecho que sigues controlando tu dominio. Si un atacante puede entrar en tu cuenta del registrador o cambiar tus servidores de nombres, puede dirigir tu tráfico a cualquier parte, obtener un certificado válido para tu nombre y leer todo el correo que te envíen, sin que tu código de aplicación llegue a ejecutarse nunca. Por eso la seguridad del dominio y del DNS es la primera buena práctica de seguridad web, y es casi por completo una cuestión de configuración.
Empieza con una consulta WHOIS de tu propio dominio y confirma tres cosas: que los códigos de estado incluyen clientTransferProhibited, que la fecha de caducidad está a más de un año vista y que el registrador es uno en el que realmente tienes cuenta. Es sorprendentemente habitual que el dominio de una empresa siga en la cuenta de registrador de un antiguo proveedor externo. Nuestra guía de consulta WHOIS explica cada código de estado que verás.
Registrar lock, MFA y alertas de caducidad
Activa el registrar lock (también llamado bloqueo de transferencia) para que el dominio no pueda trasladarse a otro registrador sin que lo desbloquees explícitamente, y activa la autenticación multifactor (MFA) en la propia cuenta del registrador. Las cuentas de registrador son un objetivo favorito del phishing precisamente porque un solo inicio de sesión controla todo lo que depende de ellas. Usa una llave de hardware o una app de autenticación en lugar de SMS siempre que el registrador lo permita.
Configura el dominio para que se renueve automáticamente con un medio de pago que no caduque, y añade igualmente una alerta en el calendario 60 días antes de la fecha de vencimiento. Un dominio vencido no se pierde el día en que expira, pero cuando se agotan los periodos de gracia y de redención (hasta unos 80 días en .com) se elimina, y los servicios de drop-catch lo registran en segundos tras su liberación. El comprador hereda tu correo, tu tráfico y cualquier inicio de sesión OAuth que confíe en tu dominio.
- El registry lock (un bloqueo manual, fuera de banda, a nivel del registro) compensa la tarifa extra en un dominio del que depende un negocio: impide que incluso una cuenta de registrador comprometida cambie los servidores de nombres.
- Separa los roles. La persona que paga el dominio, la cuenta que lo posee y el proveedor de DNS deben estar documentados y no depender del buzón de un solo empleado.
- Activa la privacidad WHOIS para que tus datos de contacto no sirvan de mapa para el phishing, y mantén en la cuenta una dirección de rol supervisada, como
domains@.
Firma la zona con DNSSEC
DNSSEC firma tu zona DNS para que un resolvedor pueda detectar respuestas falsificadas. Sin él, un atacante que logre envenenar la caché de un resolvedor puede enviar a tus visitantes a un servidor falso mientras su navegador sigue mostrando tu nombre de dominio. La mayoría de los proveedores de DNS gestionado (Cloudflare, Route 53, Google Cloud DNS y muchos registradores) lo activan con un solo interruptor; el único paso manual es publicar el registro DS en tu registrador. Compruébalo con dig o con la Verificación de salud DNS, que revisa el registro DS y las firmas:
dig +dnssec +short yourdomain.com A
# A signed zone returns the record AND an RRSIG line:
# 203.0.113.10
# A 13 2 3600 20270115000000 20261225000000 34505 yourdomain.com. Kx3f...==
dig +short yourdomain.com DS
# A DS record at the parent zone proves the chain of trust is complete
# 2371 13 2 6A1B...C9Restringe la emisión de certificados con CAA
Un registro CAA (RFC 8659) indica a las autoridades de certificación cuáles de ellas pueden emitir certificados para tu dominio. Toda CA pública está obligada a consultarlo antes de emitir, así que un registro CAA le cierra la puerta a un atacante que haya engañado a la validación de dominio de otra CA. Tres registros cubren los casos habituales: quién puede emitir, quién puede emitir certificados wildcard y dónde notificar una solicitud rechazada:
yourdomain.com. CAA 0 issue "letsencrypt.org"
yourdomain.com. CAA 0 issuewild ";"
yourdomain.com. CAA 0 iodef "mailto:security@yourdomain.com"Audita los registros obsoletos: subdomain takeover
Un subdomain takeover (toma de control de subdominio) ocurre cuando un registro DNS sigue apuntando a un servicio que dejaste de usar: un CNAME a un sitio de GitHub Pages eliminado, una app de Azure, un bucket de S3 o el nombre de host de un proveedor SaaS. Cualquiera puede registrar el destino abandonado y servir contenido en old-app.yourdomain.com en tu nombre, con un certificado válido. Es un hallazgo habitual en los programas de bug bounty porque nadie recuerda que el registro existe.
Pasa tu dominio por el Buscador de Subdominios: consulta dos servicios de registros de transparencia de certificados, Cert Spotter (certificados actuales) y crt.sh (todos los certificados desde 2013), prueba 1000 nombres comunes, resuelve cada nombre en directo, marca los CNAME huérfanos y muestra qué host o CDN ejecuta cada uno. Elimina todo registro cuyo destino devuelva NXDOMAIN o una página «no such app» del proveedor; una consulta DNS confirma un nombre concreto. Repite cada trimestre y haz que borrar el registro DNS sea parte de la baja de cualquier servicio.
Certificate Transparency es también tu sistema de alerta temprana: Cert Spotter y la monitorización de CT de Cloudflare pueden enviarte un correo cada vez que cualquier CA emita un certificado para tu dominio. Un certificado inesperado suele ser la primera señal visible de que tu DNS o tu registrador están comprometidos.
Capa 2: HTTPS y TLS bien configurados
HTTPS ya no es una buena práctica, es la base, y los navegadores marcan las páginas HTTP simples como «No seguro». Google también anunció que, desde Chrome 154 (octubre de 2026), el navegador pregunta antes de abrir cualquier sitio público por HTTP simple (Google). Lo que aún separa un sitio seguro de uno meramente cifrado son las versiones de TLS que aceptas, si se puede llegar a ti por HTTP, y si la renovación está lo bastante automatizada como para sobrevivir a los recortes de vida útil de los certificados que empezaron en marzo de 2026.
TLS 1.2 como mínimo, TLS 1.3 preferido
TLS 1.0 y 1.1 quedaron formalmente obsoletos con el RFC 8996 en 2021, y ningún navegador actual los negocia. Desactívalos en el servidor, da preferencia a TLS 1.3 (que elimina todos los cifrados débiles conocidos y completa el handshake en un solo viaje de ida y vuelta) y mantén TLS 1.2 solo con suites de cifrado AEAD como ECDHE-ECDSA-AES128-GCM-SHA256 o ECDHE-RSA-CHACHA20-POLY1305.
Dos líneas importan cuando pruebas desde fuera: el protocolo negociado y Verify return code: 0, que significa que toda la cadena de certificados se validó. Un certificado intermedio ausente es una causa frecuente de informes del tipo «funciona en Chrome, falla en curl y en Android»; nuestra guía sobre la cadena de certificados SSL muestra cómo arreglarlo, y el Verificador de Certificado SSL señala directamente una cadena incompleta. Aquí está la comprobación ejecutada contra dnsrobot.net el 27 de octubre de 2026, y luego una prueba de que el servidor rechaza TLS 1.1:
echo | openssl s_client -connect dnsrobot.net:443 -servername dnsrobot.net 2>/dev/null \
| grep -E 'Protocol|Cipher|Verify return'
# New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
# Protocol: TLSv1.3
# Verify return code: 0 (ok)
# Is TLS 1.1 refused? SECLEVEL=0 lets OpenSSL 3 and later offer it at all
openssl s_client -connect example.com:443 -servername example.com -tls1_1 \
-cipher 'DEFAULT:@SECLEVEL=0' </dev/null 2>&1 | grep -E 'alert|Cipher is'
# Refused, as it should be: ...tlsv1 alert protocol version...
# New, (NONE), Cipher is (NONE)
# Still accepted (fix it): New, TLSv1.0, Cipher is ECDHE-ECDSA-AES128-SHA
# (TLSv1.0 is the cipher's version; the handshake was TLS 1.1)Las listas de cifrados son donde las configuraciones copiadas y pegadas se quedan anticuadas. En lugar de escribirlas a mano, genera el bloque de servidor recomendado actual para nginx, Apache, Caddy o HAProxy con el Mozilla SSL Configuration Generator y elige el perfil "Intermediate", salvo que tengas que dar soporte a clientes muy antiguos.
HSTS: que el navegador no vuelva a usar HTTP
Redirigir HTTP a HTTPS no basta por sí solo. La primera petición que hace un visitante a http://yourdomain.com sigue viajando en texto plano, y un atacante en la misma red puede responderla antes que tu redirección. HTTP Strict Transport Security (RFC 6797) lo soluciona: en cuanto un navegador ha visto la cabecera, reescribe cualquier URL http:// futura de tu dominio a https:// antes de enviar nada.
Usa un max-age de al menos un año (31536000 segundos) y añade includeSubDomains cuando todos los subdominios sirvan HTTPS. La precarga en hstspreload.org, que integra el dominio en Chrome, Firefox, Safari y Edge, es opcional: los propios responsables de la lista dicen ahora que HSTS se recomienda pero la precarga no, porque Chrome y Safari ya elevan las navegaciones a HTTPS y salir de la lista lleva meses. Verifica la cabecera con el Verificador de Cabeceras HTTP, que puntúa HSTS con las pruebas de Mozilla HTTP Observatory.
Strict-Transport-Security: max-age=31536000; includeSubDomainsCertificados de 47 días: automatiza ya la renovación
La votación SC-081 del CA/Browser Forum está reduciendo por fases la vida útil máxima de todos los certificados TLS públicos. El primer recorte entró en vigor el 15 de marzo de 2026, así que cualquier certificado que compres o renueves hoy ya está limitado a 200 días, y en 2029 habrá que sustituir un certificado aproximadamente cada seis semanas:
| Fecha de entrada en vigor | Validez máxima del certificado | Reutilización de la validación de dominio |
|---|---|---|
| Antes del 15 de marzo de 2026 | 398 días | 398 días |
| 15 de marzo de 2026 | 200 días | 200 días |
| 15 de marzo de 2027 | 100 días | 100 días |
| 15 de marzo de 2029 | 47 días | 10 días |
La renovación manual no sobrevive a este calendario. La buena práctica es la emisión totalmente automatizada mediante ACME: Let's Encrypt o Google Trust Services con certbot, acme.sh o Caddy, o los certificados automáticos integrados en Cloudflare, Vercel, Netlify y la mayoría de los paneles de hosting. Prueba la renovación antes de necesitarla (certbot renew --dry-run en el caso de certbot); el fallo que hay que buscar es un registro CAA o una regla de firewall añadidos desde la última renovación que ahora bloquean la validación. Uses lo que uses, vigila también la fecha de caducidad desde fuera: el Verificador de Certificado SSL muestra los días restantes, y un certificado caducado convierte cada visita en un aviso del navegador a pantalla completa.
Capa 3: cabeceras de seguridad HTTP
Las cabeceras de seguridad son instrucciones que tu servidor da al navegador sobre lo que puede y no puede hacer con tus páginas: qué scripts pueden ejecutarse, si la página puede incrustarse en un marco, si el navegador puede adivinar tipos de contenido y qué debe contar a otros sitios sobre de dónde viene un visitante. No cuestan nada, se aplican a todas las páginas a la vez y bloquean clases enteras de ataques. Son también la capa que la mayoría de los sitios configura mal en parte, por eso el Verificador de Cabeceras HTTP las puntúa con las pruebas de Mozilla HTTP Observatory. Esto es lo que github.com envió el 27 de octubre de 2026, recortado a las cabeceras de seguridad (su script-src nombra un host de recursos y un hash de script, sin 'unsafe-inline'):
curl -sI https://github.com/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy'
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'; base-uri 'self'; ... frame-ancestors 'none'; ... script-src github.githubassets.com 'sha256-6AKuI+cDd1MoUL1u4eqrA9PXmnPAkOB4h7421hPUUm0='; ...Las siete cabeceras que todo sitio debería enviar
Estos son los valores a los que apuntar en un sitio típico. En la puntuación de Mozilla HTTP Observatory, que usa nuestro Verificador de Cabeceras HTTP, la falta de CSP cuesta 25 puntos, la falta de HSTS o de protección contra marcos 20 cada una, y la falta de nosniff 5; Referrer-Policy y COOP suman puntos extra mientras la puntuación siga en 90 o más. Permissions-Policy no se puntúa (solo los navegadores Chromium la leen), pero conviene definirla igualmente.
| Cabecera | Valor recomendado | Qué evita |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Degradación de protocolo, robo de cookies a través de HTTP |
Content-Security-Policy | script-src basado en nonces con 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' | Cross-site scripting (XSS), inyección de datos, clickjacking |
X-Content-Type-Options | nosniff | MIME sniffing que convierte un archivo subido en un script ejecutable |
Referrer-Policy | strict-origin-when-cross-origin | Filtración de URL completas (tokens, términos de búsqueda) a terceros |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | Scripts de terceros que usan sin avisar APIs potentes del navegador |
X-Frame-Options | DENY (respaldo heredado para frame-ancestors) | Clickjacking en navegadores sin CSP Level 2 |
Cross-Origin-Opener-Policy | same-origin | Ataques entre ventanas a través de window.opener |
Content Security Policy sin romper el sitio
Una Content Security Policy es la defensa más eficaz contra XSS, porque impide que los scripts inyectados se ejecuten incluso cuando existe un fallo de inyección. El problema es que una política estricta rompe cualquier script inline o etiqueta de terceros que se te haya olvidado. El enfoque moderno, recomendado por Google y documentado en MDN, es una política basada en nonces: el servidor genera un nonce aleatorio por respuesta, lo pone en cada etiqueta script que renderiza de forma intencionada y el navegador rechaza todo lo demás.
'strict-dynamic' es lo que hace práctica la política: un script con nonce puede cargar más scripts (analítica, gestores de etiquetas, widgets) sin que haya que añadir cada uno a una lista de permitidos, mientras que los navegadores modernos ignoran los tokens https: y 'unsafe-inline', que solo sirven de respaldo para los antiguos. Despliégala primero con Content-Security-Policy-Report-Only, revisa los informes de infracciones durante una semana y luego pasa al modo de aplicación. Frameworks como Next.js, Rails y Django tienen soporte nativo para nonces; los sitios estáticos pueden usar en su lugar un script-src basado en hashes.
Content-Security-Policy:
script-src 'nonce-r4nd0m1z3dV4lu3' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
report-to csp-endpoint
<!-- Only scripts carrying the nonce run; strict-dynamic lets them load their own dependencies -->
<script nonce="r4nd0m1z3dV4lu3" src="/app.js"></script>Una política de lista de permitidos como script-src 'self' https://cdn.example.com es mejor que nada, pero cualquier endpoint JSONP o biblioteca antigua alojada en un host permitido puede usarse para saltársela. Si todavía no puedes usar nonces, define al menos object-src 'none' y base-uri 'none', que cierran de inmediato dos vías de evasión habituales.
Clickjacking: frame-ancestors y X-Frame-Options
El clickjacking carga tu sitio de forma invisible dentro de la página de un atacante y engaña al visitante para que haga clic en un botón que no ve. frame-ancestors 'none' en la CSP (o 'self' si incrustas tus propias páginas) lo impide en todos los navegadores actuales, y X-Frame-Options: DENY cubre los más antiguos. Si ofreces a propósito un widget incrustable, exime solo esa ruta y solo para los orígenes que lo necesiten; nuestra guía de X-Frame-Options repasa las reglas de servidor exactas y los errores "refused to connect" que te encontrarás al hacer pruebas.
nosniff, Referrer-Policy, Permissions-Policy y COOP
X-Content-Type-Options: nosniff impide que el navegador cuestione un Content-Type, que es lo que convierte una "imagen" subida por un usuario que contiene HTML en una página ejecutable. Referrer-Policy: strict-origin-when-cross-origin (el valor por defecto en los navegadores actuales, pero defínelo explícitamente) envía a otros sitios solo tu origen, de modo que los tokens de restablecimiento de contraseña y las búsquedas incluidas en las URL se mantienen privados. Permissions-Policy desactiva las funciones del navegador que no usas, para que un script de terceros comprometido no pueda abrir la cámara ni leer la ubicación. Cross-Origin-Opener-Policy: same-origin corta el vínculo window.opener con las páginas que abres, lo que bloquea toda una clase de ataques entre ventanas.
También importan las cabeceras que debes eliminar: Server y X-Powered-By anuncian a los escáneres de vulnerabilidades las versiones exactas del software, y X-XSS-Protection está obsoleta y puede introducir errores en navegadores antiguos, así que ponla en 0 o quítala. El Detector de CMS muestra lo que cualquiera puede averiguar sobre tu stack y sus versiones a partir de la propia página.
Define las cabeceras una sola vez en el borde (CDN, proxy inverso o middleware del framework) en lugar de página por página. En Next.js es el archivo proxy o middleware; en nginx, un bloque add_header en el contexto server; en Apache, Header always set. Después vuelve a pasar el Verificador de Cabeceras HTTP tras cada despliegue, porque una nueva versión del framework o un ajuste de la CDN puede eliminar alguna sin avisar.
Capa 4: una autenticación que resista el credential stuffing
Los atacantes rara vez adivinan contraseñas de una en una. Prueban miles de millones de pares de correo y contraseña filtrados de otros sitios (credential stuffing) y consiguen el resto con phishing. Por eso las buenas prácticas de autenticación tienen tres partes: almacenar las contraseñas de modo que una fuga de la base de datos no sea una fuga de contraseñas, hacer que una contraseña robada no sirva por sí sola y hacer que la fuerza bruta sea demasiado lenta para importar. OWASP sitúa los Authentication Failures (fallos de autenticación) en el puesto A07 del OWASP Top 10:2025.
Hash de contraseñas con Argon2id o bcrypt
Nunca almacenes una contraseña, y nunca almacenes un hash rápido de ella. MD5, SHA-1 e incluso SHA-256 están diseñados para ser rápidos, así que una tabla filtrada de estos hashes puede probarse a miles de millones de intentos por segundo con una sola GPU. Usa una función de hash de contraseñas lenta y con uso intensivo de memoria, con una sal única por contraseña. La OWASP Password Storage Cheat Sheet recomienda, por este orden:
- Argon2id con al menos 19 MiB de memoria, 2 iteraciones y 1 grado de paralelismo (o 46 MiB con 1 iteración).
- scrypt con N = 2^17, r = 8, p = 1 cuando Argon2 no esté disponible.
- bcrypt con un factor de trabajo de 10 o más (ten en cuenta su límite de entrada de 72 bytes; aplicar un hash previo a las contraseñas más largas requiere cuidado).
- PBKDF2-HMAC-SHA256 con 600.000 iteraciones, solo cuando el cumplimiento de FIPS lo exija.
// Node.js with the argon2 package
import argon2 from "argon2";
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // KiB = 19 MiB
timeCost: 2,
parallelism: 1,
});
// Later, on login:
const ok = await argon2.verify(hash, submittedPassword);Ajusta el coste para que un hash tarde entre 100 y 250 ms en tu servidor de inicio de sesión. Es invisible para un usuario real, pero limita cada núcleo de CPU a un puñado de intentos por segundo, e incluso una tarjeta gráfica de gama alta a miles en lugar de miles de millones. Vuelve a calcular el hash en cada inicio de sesión correcto cuando subas los parámetros, para que los hashes antiguos se actualicen solos.
Reglas de contraseñas que sí ayudan (NIST SP 800-63B)
Las reglas de composición que la mayoría de los sitios siguen imponiendo (una mayúscula, un símbolo, cambio cada 90 días) generan contraseñas predecibles como Summer2026! y empujan a la gente a reutilizarlas. La guía actual del NIST SP 800-63B las sustituye por reglas que miden lo que importa:
- Mínimo de 8 caracteres cuando también se exige MFA, y al menos 15 caracteres para una contraseña que se usa sola; admite como mínimo 64.
- Sin reglas de composición y sin rotación periódica forzada; exige un cambio solo cuando haya indicios de que la cuenta está comprometida.
- Comprueba cada contraseña nueva contra una lista de filtraciones (la API de rangos de Have I Been Pwned permite hacerlo sin enviar la contraseña) y rechaza las que ya se hayan filtrado.
- Permite pegar y usar gestores de contraseñas, acepta espacios y Unicode, y ofrece orientación que ayude a elegir una contraseña fuerte, sobre todo justo después de rechazar una.
Nuestra Prueba de Fuerza de Contraseña muestra en qué se diferencian estas medidas de las reglas antiguas: estima cuánto tarda en romperse una contraseña con 8 ataques concretos, muestra los patrones que la debilitan y la compara con datos de filtraciones sin enviar la contraseña, una respuesta mucho más útil que un mensaje rojo de «necesita un símbolo».
MFA y passkeys
La autenticación multifactor convierte una contraseña robada en un callejón sin salida. Ofrécela a todo el mundo y exígela para los roles de administración, finanzas y soporte. Ordena las opciones por su resistencia al phishing: las passkeys y las llaves de seguridad FIDO2 no pueden robarse con phishing porque la credencial está vinculada a tu origen real; las apps de autenticación (TOTP) son buenas; los códigos por SMS son mejor que nada, pero pueden interceptarse mediante SIM swapping. Las passkeys funcionan en todos los navegadores y sistemas operativos actuales y eliminan por completo la contraseña, lo que también elimina el credential stuffing como categoría.
Protege la vía de recuperación con el mismo cuidado que el inicio de sesión: códigos de recuperación almacenados con hash, restablecimientos por correo con tokens de un solo uso que caduquen en menos de 15 minutos y nada de preguntas de seguridad.
Rate limiting, bloqueo de cuentas y defensa contra bots
Aplica rate limiting (limitación de tasa) a los endpoints de inicio de sesión, registro, restablecimiento de contraseña y MFA por IP y por cuenta, y devuelve 429 Too Many Requests con una cabecera Retry-After cuando se alcance el límite (nuestra guía sobre el error HTTP 429 explica cómo deben reaccionar los clientes). Combínalo con un retardo progresivo o un bloqueo temporal tras fallos repetidos en una misma cuenta, y con un CAPTCHA o un reto de prueba de trabajo en los endpoints que reciben ataques distribuidos. Registra cada inicio de sesión fallido con la IP de origen para que el patrón de una campaña de credential stuffing sea visible en minutos y no en meses.
# nginx: 5 login attempts at once, then 1 every 12 seconds, per client IP
# (limit_req sends no Retry-After header; add one on your 429 page if clients need it)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /api/login {
limit_req zone=login burst=4 nodelay;
limit_req_status 429;
proxy_pass http://app;
}Usa mensajes de error idénticos para "usuario desconocido" y "contraseña incorrecta", y haz que el tiempo de respuesta sea el mismo en ambos casos (calcula el hash de una contraseña ficticia cuando el usuario no exista). De lo contrario, el formulario de inicio de sesión funciona también como una API de enumeración de usuarios.
Sesiones, cookies y CSRF
Tras el inicio de sesión, la cookie de sesión es el usuario, así que merece la misma protección que la contraseña. Tres atributos de cookie y un prefijo de nombre hacen la mayor parte del trabajo:
Securesignifica que la cookie nunca se envía por HTTP sin cifrar, así que no puede interceptarse en una red Wi-Fi pública.HttpOnlyla hace invisible para JavaScript, así que un fallo XSS no puede leerla.SameSite=Lax(oStrictpara los paneles de administración) evita que viaje en peticiones POST entre sitios, lo que neutraliza la mayoría de los ataques CSRF.- El prefijo
__Host-hace que el navegador rechace la cookie salvo que seaSecure, tengaPath=/y no lleve atributoDomain, de modo que un subdominio no puede sobrescribirla.
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800Las acciones que cambian el estado nunca deben ser accesibles mediante GET. Un enlace como /account/delete?id=42 puede activarse con una etiqueta <img> en cualquier página que visite el usuario, por buenos que sean los flags de tus cookies.
CSRF: una segunda línea de defensa detrás de SameSite
El cross-site request forgery (CSRF) engaña a un navegador con sesión iniciada para que envíe a tu sitio, desde otra página, una petición que cambia el estado. Las cookies SameSite detienen el caso común, pero mantén una segunda defensa en cada petición que no sea GET: un token anti-CSRF por sesión en un campo oculto o en una cabecera personalizada, o una comprobación de que la cabecera Origin (o Sec-Fetch-Site) coincide con tu propio origen. La mayoría de los frameworks (Django, Rails, Laravel, las server actions de Next.js) lo hacen por defecto; el error es desactivarlo para un endpoint de API y luego llamar a ese endpoint desde un navegador.
IDs de sesión, rotación y JWT
Genera los IDs de sesión con al menos 128 bits de aleatoriedad, rota el ID al iniciar sesión y ante cualquier cambio de privilegios para frustrar la fijación de sesión, haz caducar las sesiones tras un periodo de inactividad y ofrece a los usuarios un botón de "cerrar sesión en todos los dispositivos" que las invalide en el servidor. En el caso de los JWT, dales una vida corta (minutos, con un refresh token revocable), no los guardes nunca en localStorage, donde cualquier script puede leerlos, y trata una clave de firma filtrada como una brecha completa, porque cualquier token que haya firmado pasa a poder falsificarse.
Inyección y XSS: valida la entrada, codifica la salida
La inyección (A05:2025) y el cross-site scripting son el mismo error en lugares distintos: los datos de un usuario se entregan a un intérprete (SQL, la shell, una consulta LDAP, una página HTML) como si fueran código. La solución también es la misma en todas partes: mantener separados los datos y el código, y no construir nunca un comando concatenando cadenas.
Consultas parametrizadas, no cadenas concatenadas
En SQL, usa sentencias preparadas o un ORM que las genere; así la base de datos trata la entrada como un valor que nunca puede cambiar la estructura de la consulta. La misma regla se aplica a los comandos de shell (pasa un array de argumentos, nunca una cadena), a los filtros NoSQL (rechaza objetos de operador como {"$gt": ""} en la entrada del usuario) y a las rutas de archivo (resuelve la ruta y confirma que se queda dentro del directorio previsto).
// Vulnerable: the input becomes part of the query text
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Safe: the driver sends the value separately from the query
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);
// Python / psycopg: same idea
cur.execute("SELECT * FROM users WHERE email = %s", (email,))La codificación de salida detiene el XSS
El cross-site scripting se produce cuando un texto controlado por el usuario se escribe en una página sin codificarlo para el contexto en el que acaba. Los motores de plantillas modernos (React y JSX, Vue, Jinja2, Blade, ERB) codifican por defecto; hoy el XSS suele venir de las vías de escape: dangerouslySetInnerHTML, v-html, |safe, innerHTML y la construcción de URL o manejadores de eventos inline a partir de datos del usuario. Codifica para el contexto exacto y sanea el HTML enriquecido con una biblioteca creada para ello, como DOMPurify, en lugar de con una expresión regular.
| Dónde acaban los datos | Codificar como | Ejemplo |
|---|---|---|
| Cuerpo HTML | Entidades HTML | < pasa a ser <, " pasa a ser " |
| Atributo HTML | Entidades con el atributo entre comillas | Pon siempre el atributo entre comillas; codifica " y ' |
| JavaScript | No inyectes en el texto del script; pasa los datos mediante atributos data- o un bloque JSON <script type="application/json"> | </script> dentro de una cadena pasa a ser \u003c/script\u003e |
| Parámetro de URL | Codificación porcentual | encodeURIComponent(value) |
| Valor CSS | Evítalo por completo; elige nombres de clase de una lista de permitidos | Nunca interpoles entrada del usuario en style |
SSRF, subida de archivos y deserialización
Server-side request forgery (SSRF): si tu servidor descarga una URL proporcionada por un usuario (webhooks, proxies de imágenes, vistas previas de enlaces), un atacante puede apuntarla a servicios internos o al endpoint de metadatos de la nube 169.254.169.254 y leer credenciales. Resuelve primero el hostname, rechaza los rangos privados y link-local, desactiva las redirecciones y coloca los procesos que hacen estas peticiones en un segmento de red que no pueda llegar a nada interno.
Subida de archivos: valida el tipo inspeccionando el contenido, no la extensión ni el Content-Type que envía el cliente; impón un límite de tamaño; guarda los archivos fuera de la raíz web o en almacenamiento de objetos con un nombre aleatorio generado por ti; y sírvelos desde un origen separado con nosniff y Content-Disposition: attachment siempre que sea posible. No dejes nunca que un archivo subido acabe en un lugar donde el servidor web vaya a ejecutarlo.
Deserialización y asignación masiva: no deserialices nunca datos no fiables con un formato que pueda instanciar clases arbitrarias (la serialización nativa de Java, pickle de Python, unserialize de PHP, YAML con etiquetas personalizadas); usa JSON con un esquema. Vincula los cuerpos de las peticiones a una lista explícita de campos permitidos para que un usuario no pueda enviar por POST "role": "admin" a un modelo que casualmente tenga esa columna.
Broken Access Control: el riesgo número uno
Broken Access Control (control de acceso roto) ocupa el primer puesto del OWASP Top 10 desde 2021 y lo mantiene como A01:2025. No es un fallo concreto, sino un hábito: comprobar quién es alguien (autenticación) y olvidarse de comprobar qué puede tocar (autorización) en cada petición. La forma clásica es la referencia directa insegura a objetos (IDOR): GET /api/invoices/1042 funciona para el propietario de la factura, y también para cualquiera que cambie el número.
- Deniega por defecto. Cada ruta requiere una regla explícita que conceda acceso; si falta la regla, la respuesta es 403, no 200.
- Aplícalo en el servidor, objeto por objeto. Ocultar un botón en la interfaz no es control de acceso. Cada lectura y escritura debe confirmar que el usuario actual es el propietario del registro concreto o tiene permiso sobre él, también en los endpoints masivos, las exportaciones y las tareas en segundo plano.
- Usa IDs imposibles de adivinar cuando ayude (UUID), pero no confíes nunca en ellos: la ocultación no es autorización.
- Restringe CORS.
Access-Control-Allow-Origin: *con credenciales, o reflejar cualquierOriginque envíe la petición, pone tu API en manos de cualquier sitio web que visite el usuario. - Desactiva el listado de directorios, bloquea
.git,.env, las copias de seguridad y los archivos de configuración a nivel del servidor web, y mantén los paneles de administración fuera de internet o detrás de MFA y listas de IP permitidas. - Aplica rate limiting y registra los fallos de autorización. Una ráfaga de 403 desde una misma sesión es un ataque de enumeración en curso.
// Express: ownership check on every object access
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
if (!invoice || invoice.ownerId !== req.user.id) {
return res.status(404).end(); // 404, not 403: don't confirm the record exists
}
res.json(invoice);
});Capa 5: dependencias y cadena de suministro de software
La mayor parte del código de una aplicación web típica es código descargado, no escrito por ti. Por eso los fallos en la cadena de suministro de software debutan en el puesto A03 del OWASP Top 10 de 2025, y por eso la explotación de vulnerabilidades superó al phishing y a las credenciales robadas en el DBIR 2026. En septiembre de 2025, un solo mantenedor víctima de phishing introdujo malware que roba criptomonedas en chalk, debug y otros 16 paquetes de npm que suman más de dos mil millones de descargas semanales; todo proyecto que instaló una versión sin fijar durante esa ventana lo incorporó automáticamente.
- Haz commit de un lockfile (
package-lock.json,yarn.lock,pnpm-lock.yaml,poetry.lock,go.sum) e instala connpm cien CI para que las compilaciones sean reproducibles y una nueva versión upstream no pueda colarse sin revisión. - Analiza de forma continua:
npm audit,pip-audit,bundler-audit, GitHub Dependabot o Renovate para las actualizaciones, y un escáner de contenedores como Trivy o Grype para la capa del sistema operativo. - Retrasa unos días las actualizaciones que no sean de seguridad (
minimumReleaseAgede Renovate) para que una versión envenenada suela retirarse antes de que la adoptes, pero aplica los parches de seguridad de inmediato. - Fija las actions y los scripts de terceros a un SHA de commit en CI, y usa Subresource Integrity (
integrity="sha384-...") en cualquier script que cargues desde una CDN pública. - Da a CI el mínimo privilegio: tokens de solo lectura cuando sea posible, credenciales OIDC de corta duración en lugar de secretos de larga duración y permisos de publicación limitados a un job de release.
- Genera un SBOM (CycloneDX o SPDX) en tiempo de compilación para poder responder "¿nos afecta?" en minutos cuando llegue el próximo CVE del calibre de Log4Shell.
# Reproducible install + audit in CI
npm ci --ignore-scripts
npm audit --audit-level=high
# Pin a GitHub Action to a commit, not a floating tag
# uses: actions/checkout@v4 <- can change underneath you
# uses: actions/checkout@<full-commit-sha> # v4.2.2
# Subresource Integrity for a CDN script (hash from: openssl dgst -sha384 -binary lib.min.js | openssl base64 -A)
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-<base64-hash>"
crossorigin="anonymous"></script>npm ci --ignore-scripts bloquea los scripts de instalación que usa la mayor parte del malware de npm para ejecutarse; después, vuelve a activar los scripts solo para los pocos paquetes que de verdad necesitan un paso de compilación.
Si usas WordPress u otro CMS, las mismas reglas se aplican a los plugins y temas, que es donde empiezan la mayoría de los compromisos de un CMS. Mantén actualizados el núcleo y todos los plugins, elimina los que no uses y comprueba lo que un escáner puede averiguar sobre tus versiones con el Detector de CMS.
Capa 6: hardening del servidor (puertos, parches, secretos y copias de seguridad)
La capa de aplicación da igual si el servidor que hay debajo acepta inicios de sesión SSH con contraseña, ejecuta una base de datos en un puerto público o no se ha parcheado desde que se instaló. Las buenas prácticas de servidor son aburridas, y es justo por ahí por donde entra el ransomware.
Cierra todos los puertos que no uses
Un servidor web público necesita abiertos los puertos 80 y 443, y SSH desde tu propio rango de IP. Las bases de datos (3306, 5432, 27017), Redis (6379), Elasticsearch (9200), los paneles de administración y los endpoints de métricas deben escuchar solo en localhost o en una red privada. Compruébalo desde fuera con el Verificador de Puertos después de cada cambio, porque esa es la vista que tiene un atacante, y los grupos de seguridad en la nube son fáciles de malinterpretar.
# Ubuntu: default-deny firewall, allow only web + SSH from your office range
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable # check your own IP is in that range first, or you lock yourself out
# SSH: keys only, no root password login, in a drop-in that sshd reads first
printf 'PasswordAuthentication no\nPermitRootLogin prohibit-password\n' \
| sudo tee /etc/ssh/sshd_config.d/00-hardening.conf
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin) ' # what sshd will really use
sudo systemctl reload sshEn Ubuntu y Debian, sshd lee los archivos de /etc/ssh/sshd_config.d/ antes que el resto de sshd_config, y para cada ajuste gana el primer valor leído. Por eso los comandos anteriores escriben un drop-in llamado 00-hardening.conf: una edición más abajo en el archivo principal pierde frente al drop-in de una imagen cloud que diga PasswordAuthentication yes. Confirma el resultado con sudo sshd -T antes de cerrar sesión. En el Verificador de Puertos, un puerto que tu firewall descarta aparece como filtered (sin respuesta) y uno sin ningún servicio escuchando como closed: ambos están bien, open es el que hay que arreglar.
Parchea con calendario y saca los secretos del código
Activa las actualizaciones de seguridad desatendidas del sistema operativo, suscríbete a las listas de seguridad de tu runtime y de tu framework, y trata un CVE crítico en cualquier cosa expuesta a internet como una tarea para el mismo día. Ejecuta los servicios con usuarios sin privilegios, en contenedores o con el sandboxing de systemd, para que un proceso comprometido no pueda leer toda la máquina.
Los secretos (contraseñas de bases de datos, claves de API, claves de firma) nunca deben estar en el repositorio, en una capa de una imagen de Docker ni en JavaScript del lado del cliente. Cárgalos desde variables de entorno inyectadas en el despliegue o desde un gestor de secretos, rótalos cuando se marche alguien con acceso y analiza el historial del repositorio con una herramienta como gitleaks, porque una clave que se ha subido una sola vez queda comprometida para siempre, aunque se elimine el commit.
Copias de seguridad que sobrevivan al ransomware y una CDN delante
Sigue la regla 3-2-1: tres copias, en dos soportes distintos y una fuera de las instalaciones, y haz que al menos una copia sea inmutable o esté desconectada para que un ransomware que llegue al servidor no pueda cifrar también las copias. Cifra las copias de seguridad, incluye la base de datos y el directorio de subidas y, sobre todo, prueba una restauración de forma periódica. Una copia de seguridad que nadie ha restaurado nunca es una esperanza, no un plan. El tiempo de recuperación es lo que decide si un incidente es una simple caída del servicio o algo que acaba con la empresa.
Coloca un CDN o un WAF (Cloudflare, Fastly, AWS CloudFront con WAF o el equivalente de tu hosting) delante del origen. Absorbe los DDoS volumétricos, aplica reglas gestionadas contra cargas de inyección habituales y bots maliciosos, oculta la IP de tu origen y te da un lugar donde aplicar límites de peticiones y reglas geográficas sin tocar la aplicación. Después restringe el firewall del origen para que solo los rangos de IP del CDN puedan llegar directamente al puerto 443; si no, los atacantes simplemente lo rodean. El Comprobador de Hosting muestra qué CDN está delante de un sitio y qué dicen las pruebas públicas sobre el host que hay detrás.
Autenticación del correo: SPF, DKIM y DMARC
La seguridad web incluye el correo que lleva tus restablecimientos de contraseña, facturas y respuestas de soporte. Sin SPF, DKIM y DMARC, cualquiera puede enviar correo como billing@yourdomain.com, y desde febrero de 2024 Google y Yahoo exigen los tres a los remitentes masivos, y Microsoft hace lo mismo con Outlook.com desde mayo de 2025. El conjunto completo son tres registros TXT de DNS:
yourdomain.com. TXT "v=spf1 include:_spf.google.com -all"
google._domainkey.yourdomain.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
_dmarc.yourdomain.com. TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s"DMARC p=reject es también un control de protección de marca: es lo único que impide que una campaña de phishing ponga exactamente tu dominio en el campo From. Los informes agregados (rua=) te muestran quién lo está intentando.
Empieza DMARC en p=none para recopilar informes, pasa a p=quarantine y luego a p=reject cuando cada remitente legítimo (plataforma de marketing, helpdesk, CRM) supere la alineación. Añade registros MTA-STS y TLS-RPT para forzar la entrega cifrada a tus servidores de correo. En los dominios que nunca envían ni reciben correo, publica un MX nulo (MX 0 .), v=spf1 -all y un registro DMARC con p=reject: el MX nulo solo dice que el dominio no acepta correo; son SPF y DMARC los que permiten a los receptores rechazar falsificaciones. Verifica cada registro con el Verificador de Registro SPF, el Verificador DKIM y el Verificador DMARC, y comprueba tus IP de envío contra listas de bloqueo con el Verificador de Lista Negra IP si baja la entregabilidad.
Registra, monitoriza y prepárate para la brecha
Dos de las diez categorías de OWASP 2025 tratan de lo que ocurre después de un error: Security Logging and Alerting Failures (fallos de registro y alertas de seguridad, A09) y la nueva Mishandling of Exceptional Conditions (gestión incorrecta de condiciones excepcionales, A10). La brecha típica sigue pasando desapercibida durante meses porque las pruebas nunca se registraron, o se registraron y nadie las miró.
- Registra los eventos de seguridad con contexto: cada inicio de sesión correcto y fallido, cambios de MFA, restablecimientos de contraseña, cambios de permisos, denegaciones de control de acceso, fallos de validación de entradas y acciones de administración, cada uno con marca de tiempo, ID de usuario, IP de origen y user agent.
- No registres nunca secretos: contraseñas, tokens de sesión, números de tarjeta completos, claves de API. Enmascáralos en la capa de registro, no en cada punto de llamada.
- Saca los registros del servidor a un almacén central (CloudWatch, Loki, Elastic, un SIEM) con una retención de al menos 90 días, para que un atacante con acceso root no pueda borrar su rastro.
- Crea alertas sobre patrones, no sobre eventos aislados: 50 inicios de sesión fallidos en un minuto, un pico de 403 desde una sesión, un nuevo administrador creado fuera del horario laboral, un certificado emitido para tu dominio que no has solicitado.
- Falla en cerrado: cuando una comprobación intermedia (servicio de autenticación, limitador de tasa, WAF) devuelve un error, deniega la petición. A10 existe porque muchísimos sistemas conceden el acceso cuando la comprobación que debía bloquearlo lanza una excepción.
- Monitoriza desde fuera: disponibilidad, caducidad de certificados, cambios de DNS, inclusión en listas de bloqueo y regresiones de cabeceras. Vuelve a ejecutar el Verificador de Certificado SSL, el Verificador de Cabeceras HTTP y la Verificación de salud DNS (40 comprobaciones de delegación, servidores de nombres, DNSSEC y SPF/DMARC) después de cada despliegue.
Escribe el plan de respuesta a incidentes antes de necesitarlo
Decide con antelación quién está de guardia, cómo rotar cada credencial, cómo poner el sitio en modo de solo lectura, dónde están las copias de seguridad limpias y qué regulador o clientes deben ser notificados y en cuántas horas (72 según el RGPD). Haz un ejercicio de mesa una vez al año. Un plan ensayado convierte días de improvisación en una lista de comprobación.
Publica un archivo security.txt en /.well-known/security.txt (RFC 9116) con una línea Contact: y una fecha Expires:, los dos campos obligatorios (una clave Encryption: es opcional). Los investigadores que encuentren un fallo en tu sitio mirarán ahí primero; sin él, quizá no encuentren a nadie a quien avisar.
Ponlo a prueba: escáneres, pentests y comprobaciones continuas
Todos los controles anteriores pueden verificarse, y la verificación debería automatizarse siempre que sea posible para que no se deteriore con el tiempo. Una escalera razonable, desde lo gratuito e instantáneo hasta lo periódico y de pago:
| Comprobación | Herramienta | Frecuencia |
|---|---|---|
| Configuración TLS, cadena, caducidad | Verificador de Certificado SSL, SSL Labs, testssl.sh | Tras cada cambio de certificado o de servidor; la caducidad, a diario |
| Cabeceras de seguridad y CSP | Verificador de Cabeceras HTTP, MDN HTTP Observatory | Tras cada despliegue (inclúyelo en CI) |
| Puertos abiertos | Verificador de Puertos, nmap desde fuera de tu red | Tras cada cambio de firewall o de la nube |
| DNS, DNSSEC, autenticación del correo | Verificación de salud DNS, Verificador DMARC | Cada mes y tras cualquier cambio de DNS |
| Subdominios obsoletos | Buscador de Subdominios | Cada trimestre y en cada retirada de un servicio |
| Dependencias con vulnerabilidades conocidas | npm audit, Dependabot, Trivy | En cada compilación |
| Fallos a nivel de código (SAST) | Semgrep, CodeQL | En cada pull request |
| Vulnerabilidades web en tiempo de ejecución (DAST) | ZAP, Nuclei | Cada semana, contra el entorno de staging |
| Autorización y lógica de negocio | Pentest manual o programa de bug bounty | Una vez al año y tras cada funcionalidad importante |
Los escáneres son buenos detectando inyecciones, cabeceras y componentes desactualizados, y malos con la autorización y la lógica de negocio, así que reserva presupuesto para al menos una prueba humana al año en cualquier cosa que maneje dinero o datos personales. Corrige según la exposición: lo que un usuario no autenticado pueda explotar desde internet va primero.
Checklist de seguridad web
Copia esto en el README de tu proyecto o en tu gestor de tickets. Cada línea es una de las prácticas anteriores, redactada para que pueda marcarse como hecha o pendiente; la última columna es la prueba.
| Capa | Práctica | Hecho cuando |
|---|---|---|
| Dominio | Registrar lock y MFA en la cuenta del registrador; renovación automática activada | WHOIS muestra clientTransferProhibited; caducidad a más de 12 meses vista |
| DNS | Zona firmada con DNSSEC; registro CAA que solo incluye tus CA | dig +dnssec devuelve RRSIG; registro DS presente en la zona padre |
| DNS | Ningún registro CNAME ni A huérfano | Cada hostname del Buscador de Subdominios resuelve a algo que controlas |
| TLS | Solo TLS 1.2+, TLS 1.3 preferido, cadena completa servida | Verificador de Certificado SSL: cadena completa, TLS 1.0/1.1 rechazados |
| TLS | HSTS con max-age de un año e includeSubDomains (precarga opcional) | El Verificador de Cabeceras HTTP supera la prueba de HSTS |
| TLS | Renovación automatizada mediante ACME; caducidad monitorizada | certbot renew --dry-run se ejecuta sin errores; alerta configurada a 14 días |
| Cabeceras | CSP (basada en nonces), frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Verificador de Cabeceras HTTP: nota A o A+ |
| Autenticación | Hash con Argon2id o bcrypt; reglas de contraseñas del NIST; comprobación contra listas de filtraciones | Un hash tarda de 100 a 250 ms; sin reglas de composición |
| Autenticación | MFA ofrecida a todos y obligatoria para administradores; passkeys admitidas | Imposible iniciar sesión como administrador sin un segundo factor |
| Autenticación | Endpoints de inicio de sesión, restablecimiento y MFA con rate limiting por IP y por cuenta | Seis intentos rápidos desde una IP: el sexto devuelve 429 |
| Sesiones | Cookie __Host- con Secure, HttpOnly y SameSite; ID rotado al iniciar sesión | Atributos de la cookie visibles en DevTools; la sesión antigua deja de ser válida tras el inicio de sesión |
| Entradas | Consultas parametrizadas en todas partes; salida codificada según el contexto; archivos subidos validados por su contenido | Ninguna consulta construida con cadenas en una búsqueda por el código; los payloads XSS se muestran como texto |
| Control de acceso | Enrutamiento con denegación por defecto; comprobaciones de propiedad por objeto; CORS estricto | Repetir peticiones con los IDs de otro usuario devuelve 404 o 403 |
| Dependencias | Lockfile en el repositorio; npm ci; auditoría en CI; actions fijadas a un SHA; SRI en los scripts de CDN | La compilación falla ante un CVE de gravedad alta |
| Servidor | Solo 80 y 443 públicos; SSH solo con claves; actualizaciones de seguridad desatendidas; secretos en variables de entorno o en un vault | El Verificador de Puertos muestra 3306 y 6379 como closed o filtered, y también el 22 desde fuera de tu propio rango |
| Copias de seguridad | 3-2-1 con una copia inmutable; restauración probada | Última prueba de restauración correcta realizada hace menos de 90 días |
| Correo | SPF -all, DKIM, DMARC p=reject, MTA-STS | El Verificador DMARC no muestra errores; llegan los informes agregados |
| Monitorización | Eventos de autenticación y autorización registrados de forma centralizada; alertas sobre patrones; monitorización externa de disponibilidad, certificados y DNS | Alerta de prueba enviada y recibida |
| Respuesta | Plan de incidentes por escrito; security.txt publicado; ejercicio de simulación realizado | Plan revisado en los últimos 12 meses |
Cómo encaja con el OWASP Top 10:2025
El OWASP Top 10 es la lista de riesgos de aplicaciones web más citada, y su edición de 2025 reordenó los puestos e introdujo dos categorías nuevas. Si un cliente, un auditor o un marco de cumplimiento te pregunta cómo lo abordas, esta es la correspondencia con las prácticas de esta guía:
| OWASP Top 10:2025 | Prácticas de esta guía |
|---|---|
| A01 Broken Access Control (control de acceso roto) | Denegación por defecto, comprobaciones por objeto, CORS estricto, paneles de administración blindados |
| A02 Security Misconfiguration (configuración de seguridad incorrecta) | Cabeceras de seguridad, HSTS, puertos cerrados, cabeceras de versión eliminadas, listado de directorios desactivado |
| A03 Software Supply Chain Failures (fallos en la cadena de suministro de software; nueva) | Lockfiles, auditorías, actions fijadas, SRI, SBOM, CI con mínimo privilegio |
| A04 Cryptographic Failures (fallos criptográficos) | TLS 1.2+ y 1.3, HSTS, Argon2id, gestión de secretos, copias de seguridad cifradas |
| A05 Injection (inyección) | Consultas parametrizadas, codificación de salida, CSP, validación de archivos subidos |
| A06 Insecure Design (diseño inseguro) | Modelo de amenazas por capa, valores por defecto que fallan en cerrado, rate limiting desde el diseño |
| A07 Authentication Failures (fallos de autenticación) | Reglas de contraseñas del NIST, comprobación de filtraciones, MFA y passkeys, rotación de sesiones |
| A08 Software or Data Integrity Failures (fallos de integridad del software o de los datos) | SRI, dependencias firmadas y fijadas, deserialización segura |
| A09 Security Logging and Alerting Failures (fallos de registro y alertas de seguridad) | Registro centralizado, alertas por patrones, monitorización externa |
| A10 Mishandling of Exceptional Conditions (gestión incorrecta de condiciones excepcionales; nueva) | Fallar en cerrado, respuestas de error uniformes, ninguna traza de pila visible para los usuarios |
El ASVS de OWASP (Application Security Verification Standard) convierte el mismo material en un checklist numerado para auditorías. El nivel 1 es un objetivo realista para cualquier sitio público; el nivel 2, para cualquier cosa que maneje datos personales o financieros.
Comprueba las cabeceras de seguridad de tu sitio en segundos
El Verificador de Cabeceras HTTP gratuito de DNS Robot muestra las cabeceras de respuesta de cualquier URL exactamente como las envía el servidor y puntúa sus cabeceras de seguridad con las pruebas de Mozilla HTTP Observatory, con la solución para cada una que falte. Sin registro.
Fuentes
- 2026 Data Breach Investigations Report, Verizon
- OWASP Top 10:2025, OWASP
- Password Storage Cheat Sheet, OWASP
- SP 800-63B-4: Authentication and Authenticator Management, NIST
- Ballot SC-081v3: reducing certificate validity and data reuse periods, CA/Browser Forum
- HTTPS by default, Google
- RFC 9116: A File Format to Aid in Security Vulnerability Disclosure, IETF
Preguntas frecuentes sobre seguridad web
¿Cuáles son las buenas prácticas de seguridad web más importantes?
¿Qué es el OWASP Top 10 y sigue siendo importante en 2026?
¿Qué cabeceras de seguridad HTTP debería enviar todo sitio web?
¿Basta con HTTPS para que un sitio web sea seguro?
¿Cómo almacenar de forma segura las contraseñas de los usuarios?
¿Qué es una Content Security Policy (CSP) y la necesita mi sitio?
¿Cómo saber si mi sitio web es seguro?
¿Por qué los certificados TLS duran cada vez menos?
¿Cada cuánto debo actualizar las dependencias de mi sitio web?
¿De verdad atacan a los sitios web pequeños?
Artículos relacionados
Herramientas relacionadas
Verificador de encabezados HTTP
Analiza cabeceras de respuesta HTTP y clasificación de cabeceras de seguridad
Verificador de Certificado SSL
Verifica la validez y detalles de seguridad del certificado SSL
Verificador de Puertos
Prueba la conectividad de puertos y disponibilidad de servicios
Verificación de salud DNS
Califique el DNS: delegación, servidores, SOA, DNSSEC y correo en 40 pruebas
Verificador DMARC
Verifica la política DMARC y configuraciones de alineación de cualquier dominio
Buscador de Subdominios
Descubre todos los subdominios de cualquier dominio



