ejemplo.itvweb.es
Resumen
El análisis muestra una web con varios puntos de atención en privacidad y protección de datos: faltan enlaces visibles al Aviso Legal y a la Política de Privacidad, se cargan servicios de Google sin que exista un banner de consentimiento que los bloquee, y un formulario de contacto envía los datos sin cifrar. A nivel de infraestructura técnica, el correo electrónico carece de protecciones básicas contra la suplantación (SPF y DMARC ausentes), y tanto el gestor de contenidos (WordPress) como una librería JavaScript están desactualizados con vulnerabilidades conocidas. También faltan varias cabeceras de seguridad web habituales. En conjunto, la situación requiere atención, especialmente en los aspectos de privacidad del formulario y la configuración de correo.
Qué hacer ahora: todos los hallazgos graves y muy graves (4)
Todos los hallazgos · 32
Grave (4)
GRAVEFalta el registro SPF en el dominioAñadir un registro SPF en el DNS que defina los servidores de envío autorizados.
El dominio no tiene registro SPF en el DNS, por lo que los servidores receptores no pueden verificar qué máquinas están autorizadas a enviar correo en nombre del dominio. Esto facilita la suplantación del remitente y reduce la entregabilidad de los correos legítimos.
En lenguaje sencillo
SPF es la lista de servidores autorizados a enviar correo en nombre del dominio. Si no existe, cualquier servidor ajeno puede enviar mensajes con ese dominio sin llamar la atención.
Ejemplo: Un grupo de phishing envía correos masivos con el dominio como remitente. El dominio acaba en listas de spam y, a partir de entonces, tampoco llegan los correos legítimos de la empresa.
Guía de corrección ~10 min · Fácil
- En el proveedor de DNS, añadir al dominio
ejemplo.itvweb.esun registro TXT: - Esperar la propagación (5-30 min) y comprobar:
dig TXT ejemplo.itvweb.es - Si se usa Google Workspace:
include:_spf.google.comya está incluido.
v=spf1 include:_spf.google.com mx ~allGRAVEFalta la política DMARC del dominioPublicar un registro DMARC con una política inicial de monitorización y evolucionar hacia rechazo progresivo.
No existe registro DMARC, por lo que no hay una política que indique a los servidores receptores qué hacer con correos que fallan SPF o DKIM (rechazar, cuarentena o nada). Esto impide también recibir informes agregados sobre intentos de suplantación del dominio.
En lenguaje sencillo
DMARC define qué ocurre con los correos que se envían en nombre del dominio pero no proceden de la empresa. Sin DMARC, prácticamente cualquiera puede enviar mensajes con el dominio como remitente, y quien los recibe no tiene forma de detectar la falsificación.
Ejemplo: Un estafador envía, en nombre de administracion@sudominio.es, una factura muy convincente a un cliente, con el número de cuenta cambiado. El cliente paga al estafador. El dinero se pierde y el problema acaba en la empresa.
Guía de corrección ~15 min · Fácil
- Crear el registro TXT de
_dmarc.ejemplo.itvweb.es: - rua= es la dirección donde se reciben los informes DMARC (diarios).
- Empezar con p=quarantine y pasar a p=reject tras la fase de seguimiento.
- Comprobar:
dig TXT _dmarc.ejemplo.itvweb.es
v=DMARC1; p=quarantine; rua=mailto:dmarc@ejemplo.itvweb.es; ruf=mailto:dmarc@ejemplo.itvweb.es; pct=100; adkim=r; aspf=rGRAVEServicios de terceros se cargan sin banner de cookiesInstalar un banner de consentimiento que bloquee la carga de Google Fonts hasta la aceptación, o alojar las fuentes localmente para eliminar la necesidad de consentimiento.
Se detectan 2 servicios (Google Fonts en sus dos variantes) que suelen requerir consentimiento previo, pero no existe ninguna herramienta de gestión de consentimiento (CMP) que los bloquee hasta que el visitante decida. Esto significa que la conexión a servidores externos ocurre de forma automática en cada carga de página. La solución pasa por instalar un banner que realmente bloquee la carga hasta la aceptación, o sustituir estos servicios por alternativas alojadas en local.
En lenguaje sencillo
Hay servicios en marcha que requieren consentimiento, pero no se detecta ninguna herramienta de gestión del consentimiento. Importante: un banner por sí solo no basta; debe bloquear realmente los servicios hasta que alguien acepte.
Ejemplo: La autoridad de control revisa por muestreo las webs de un sector. Un sitio que usa seguimiento sin consentimiento válido puede recibir primero un requerimiento con plazo para corregirlo.
Guía de corrección ~2-4 h · Media
- Importante de entrada: un banner por sí solo no basta. Debe bloquear los servicios de verdad hasta que se acepte. Muchas instalaciones muestran solo un aviso mientras el seguimiento ya funciona en segundo plano.
- Elegir la herramienta. En WordPress son habituales Complianz, Real Cookie Banner o Borlabs Cookie, porque bloquean ellas mismas los scripts. Sin WordPress, Klaro es una solución libre.
- Marcar como bloqueados en la herramienta los scripts que requieren consentimiento, no solo mencionarlos en el texto.
- El banner necesita un botón de rechazar al mismo nivel que el de aceptar. Solo "Aceptar" más "Configuración" no es suficiente.
- Las casillas premarcadas no son válidas, salvo para las cookies técnicas necesarias.
- Para terminar, comprobar: ventana privada, Herramientas de desarrollo → Aplicación → Cookies. Antes del consentimiento no debe haber ninguna cookie de seguimiento.
- Referencia normativa (solo cita, sin valoración): art. 22.2 de la LSSI-CE, RGPD y guía de cookies de la AEPD.
# Orden de comprobación tras la instalación:
1. Abrir una ventana privada, cargar la página, NO hacer clic en nada
2. DevTools > Application > Cookies > dominio propio
-> solo se admite la cookie de sesión o de idioma del sitio
3. DevTools > Network, filtrar por 'google'
-> ninguna petición a analytics/googletagmanager
4. Pulsar 'Rechazar' -> sigue sin cargarse nada
5. Pulsar 'Aceptar' -> ahora sí puede cargarseGRAVEFormulario de contacto envía datos sin cifrar por HTTPConfigurar el formulario para que envíe los datos únicamente a través de HTTPS.
El formulario envía sus datos a una dirección http:// en lugar de https://, por lo que la información viaja en texto plano por la red. El art. 32 del RGPD menciona el cifrado como ejemplo de medida técnica para proteger datos personales, y este caso representa una carencia concreta de esa protección.
En lenguaje sencillo
Los formularios de contacto y de boletín recogen nombres, correos o teléfonos. Desde fuera se pueden comprobar tres puntos técnicos: si los datos viajan cifrados, si van al propio dominio o a un servicio externo, y si junto al formulario hay un enlace a la política de privacidad.
Ejemplo: Un interesado escribe su teléfono en un formulario. Si los datos llegan a la herramienta de boletines de un tercero, conviene que pueda leerlo antes, lo más sencillo es un enlace junto al botón de enviar.
Guía de corrección ~20 min · Fácil
- La dirección del atributo
actiondebe empezar porhttps://o ser relativa (/contacto). Cambiar cualquier direcciónhttp://y servir la propia página solo por HTTPS. - Si el formulario envía a un servicio externo (herramienta de boletines, proveedor de formularios): nombrar al destinatario en la política de privacidad y comprobar si existe contrato de encargado del tratamiento. Eso no es visible desde fuera.
- Junto al botón de enviar, añadir una frase con enlace a la política de privacidad. Un enlace solo en el pie de página no es información 'en el formulario'.
- Si se pide consentimiento (por ejemplo, para un boletín), la casilla con el mismo enlace forma parte del formulario. No dejarla marcada de antemano.
<form action="/contacto" method="post">
<input type="email" name="email" required>
<button type="submit">Enviar</button>
<p><small>Información sobre el tratamiento de los datos: <a href="/politica-de-privacidad/">Política de privacidad</a></small></p>
</form>Leve (14)
LEVEEl sitio no fuerza el uso de HTTPS (falta HSTS)Configurar la cabecera HSTS en el servidor web.
Falta la cabecera HSTS, que indica al navegador que solo debe cargar el sitio mediante HTTPS. Sin ella, existe una ventana donde un atacante en la misma red podría intentar forzar una conexión no cifrada (downgrade).
En lenguaje sencillo
Las cabeceras de seguridad son instrucciones de protección que el servidor envía al navegador de cada visita. Si faltan, resulta más fácil realizar ataques habituales contra quienes visitan la web.
Ejemplo: Sin estas reglas, un atacante puede incrustar la página en un marco invisible y hacer que las visitas pulsen sin querer donde él decide (clickjacking).
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i strict-transport-security
# nginx (dentro del bloque server { }):
add_header Strict-Transport-Security "max-age=31536000" always;
# Apache (.htaccess o VirtualHost):
Header always set Strict-Transport-Security "max-age=31536000"LEVEFalta la cabecera Content-Security-PolicyDefinir y desplegar una política CSP adaptada a los recursos que usa el sitio.
La ausencia de CSP significa que el navegador no tiene restricciones definidas sobre qué scripts y recursos puede ejecutar la página, lo que amplía el impacto de posibles vulnerabilidades de tipo XSS si llegaran a explotarse.
Ver explicación en «El sitio no fuerza el uso de HTTPS (falta HSTS)»
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i content-security-policy
# nginx (dentro del bloque server { }):
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' https:;" always;
# Apache (.htaccess o VirtualHost):
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' https:;"LEVELa web puede incrustarse en otras páginas (falta X-Frame-Options)Añadir la cabecera X-Frame-Options o la directiva frame-ancestors en CSP.
Sin esta cabecera, el sitio puede cargarse dentro de un iframe en otro dominio, lo que habilita ataques de clickjacking donde se superponen elementos invisibles sobre botones reales de la página.
Ver explicación en «El sitio no fuerza el uso de HTTPS (falta HSTS)»
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i x-frame-options
# nginx (dentro del bloque server { }):
add_header X-Frame-Options "SAMEORIGIN" always;
# Apache (.htaccess o VirtualHost):
Header always set X-Frame-Options "SAMEORIGIN"LEVEEl navegador puede interpretar archivos de forma incorrectaAñadir la cabecera X-Content-Type-Options: nosniff en el servidor.
Falta la cabecera X-Content-Type-Options: nosniff, por lo que el navegador puede intentar adivinar el tipo de archivo en lugar de respetar el declarado por el servidor, lo que en combinación con otras debilidades puede facilitar ataques.
Ver explicación en «El sitio no fuerza el uso de HTTPS (falta HSTS)»
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i x-content-type-options
# nginx (dentro del bloque server { }):
add_header X-Content-Type-Options "nosniff" always;
# Apache (.htaccess o VirtualHost):
Header always set X-Content-Type-Options "nosniff"LEVESe comparte la dirección de origen al salir del sitio (falta Referrer-Policy)Configurar una cabecera Referrer-Policy restrictiva, por ejemplo strict-origin-when-cross-origin.
Sin una política de Referrer-Policy definida, el navegador puede enviar la URL completa de origen a sitios externos enlazados, lo que en algunos casos puede filtrar información de navegación interna.
Ver explicación en «El sitio no fuerza el uso de HTTPS (falta HSTS)»
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i referrer-policy
# nginx (dentro del bloque server { }):
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Apache (.htaccess o VirtualHost):
Header always set Referrer-Policy "strict-origin-when-cross-origin"LEVENo se restringen permisos del navegador (falta Permissions-Policy)Definir una cabecera Permissions-Policy que deshabilite las funciones no utilizadas.
La falta de Permissions-Policy implica que no se han definido restricciones explícitas sobre qué APIs del navegador (cámara, geolocalización, micrófono) pueden usar recursos de terceros incrustados en la página.
Ver explicación en «El sitio no fuerza el uso de HTTPS (falta HSTS)»
Guía de corrección ~10 min · Fácil
- Abrir la configuración de nginx o Apache y añadir la cabecera:
- Recargar la configuración:
nginx -t && systemctl reload nginxoapachectl configtest && systemctl reload apache2 - Comprobar:
curl -I https://ejemplo.itvweb.es | grep -i permissions-policy
# nginx (dentro del bloque server { }):
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
# Apache (.htaccess o VirtualHost):
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"LEVENo se detecta firma DKIM en los correosActivar la firma DKIM en el proveedor de correo y publicar el selector correspondiente en el DNS.
Se probaron 9 selectores DKIM habituales sin obtener respuesta, lo que sugiere que los correos salientes no están firmados criptográficamente. Esto reduce la capacidad de los receptores para validar la autenticidad del remitente.
En lenguaje sencillo
DKIM es una firma digital invisible en cada correo que demuestra que realmente procede de la empresa y que nadie lo ha modificado por el camino. Sin DKIM no se puede comprobar esa autenticidad.
Ejemplo: Un correo de la empresa es interceptado y se sustituye un enlace del mensaje. Sin DKIM, el destinatario no nota nada y hace clic en el enlace manipulado.
Guía de corrección ~20 min · Fácil
- Generar la clave DKIM en el proveedor de correo (Google Workspace, Mailgun, Mailchimp, etc.).
- Añadir en el DNS el registro TXT generado (por ejemplo
default._domainkey.ejemplo.itvweb.es). - En Google Workspace: Admin → Aplicaciones → Google Workspace → Gmail → Autenticar el correo.
- En Mailgun: Sending → Domains → ejemplo.itvweb.es → DNS Records.
- Comprobar:
dig TXT default._domainkey.ejemplo.itvweb.es
LEVEGoogle Fonts se carga desde servidores externos sin consulta previaAlojar las fuentes de Google de forma local en el servidor propio para evitar la conexión externa automática.
La página carga tipografías directamente desde fonts.googleapis.com, lo que provoca una conexión automática a servidores de Google y la transferencia de la IP del visitante antes de cualquier interacción. Existen directrices sobre el tratamiento de datos personales (RGPD) y sobre el consentimiento previo para servicios de terceros (LSSI-CE, art. 22.2) que resultan relevantes aquí; lo que aplica en el caso concreto debe valorarlo la empresa con su asesoría.
En lenguaje sencillo
La página carga elementos desde servidores de terceros, casi siempre de Google. Con ello, el servidor ajeno conoce la dirección IP de cada visita sin que se le haya preguntado. La IP es un dato personal a efectos del RGPD, por lo que estos servicios deben figurar en la política de privacidad y, según el caso, requerir consentimiento.
Ejemplo: Una persona abre la página. Antes de que se vea nada, su dirección IP ya ha llegado a Google. Quitar la dependencia (por ejemplo, alojando las fuentes en el propio servidor) suele llevar una hora de trabajo.
Guía de corrección ~1 h · Fácil
- Al cargar las fuentes desde Google, su servidor recibe la dirección IP de cada visita (un dato personal a efectos del RGPD). Alojarlas en local lo evita y se resuelve en una hora.
- Descargar las fuentes utilizadas (google-webfonts-helper genera un paquete listo con archivos woff2 y CSS).
- Copiar los archivos al propio proyecto, por ejemplo en
/assets/fonts/. - En el código fuente, eliminar la llamada a fonts.googleapis.com e incluir en su lugar el CSS propio.
- Comprobar: Herramientas de desarrollo → Red → recargar la página. No debe haber ninguna petición a fonts.googleapis.com ni a fonts.gstatic.com.
- En WordPress: muchos temas cargan las fuentes por su cuenta. En ese caso, buscar también en el tema o usar un plugin que las aloje en local.
<!-- FUERA del <head>: -->
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<!-- DENTRO, CSS propio: -->
<link href="/assets/fonts/fonts.css" rel="stylesheet">
/* /assets/fonts/fonts.css */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/assets/fonts/inter-v13-latin-regular.woff2') format('woff2');
}LEVEGoogle Fonts carga tipografías desde servidores externosDescargar las fuentes de Google Fonts y servirlas desde el propio servidor.
Las fuentes se sirven desde fonts.gstatic.com en lugar de alojarse localmente. Esto transmite la IP del visitante a Google antes de cualquier consentimiento, lo que suele requerir una base legal o consentimiento previo según el RGPD. Alojar las fuentes en el propio servidor elimina esta transmisión.
Ver explicación en «Google Fonts se carga desde servidores externos sin consulta previa»
Guía de corrección ~1 h · Fácil
- Al cargar las fuentes desde Google, su servidor recibe la dirección IP de cada visita (un dato personal a efectos del RGPD). Alojarlas en local lo evita y se resuelve en una hora.
- Descargar las fuentes utilizadas (google-webfonts-helper genera un paquete listo con archivos woff2 y CSS).
- Copiar los archivos al propio proyecto, por ejemplo en
/assets/fonts/. - En el código fuente, eliminar la llamada a fonts.googleapis.com e incluir en su lugar el CSS propio.
- Comprobar: Herramientas de desarrollo → Red → recargar la página. No debe haber ninguna petición a fonts.googleapis.com ni a fonts.gstatic.com.
- En WordPress: muchos temas cargan las fuentes por su cuenta. En ese caso, buscar también en el tema o usar un plugin que las aloje en local.
<!-- FUERA del <head>: -->
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<!-- DENTRO, CSS propio: -->
<link href="/assets/fonts/fonts.css" rel="stylesheet">
/* /assets/fonts/fonts.css */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/assets/fonts/inter-v13-latin-regular.woff2') format('woff2');
}LEVENo se encuentra enlace al Aviso Legal en la portadaAñadir un enlace visible al Aviso Legal en el pie de página, accesible desde todas las páginas.
El escaneo no detecta ningún enlace hacia un Aviso Legal desde la página de inicio. La normativa LSSI-CE (art. 10) suele requerir que este tipo de información esté accesible con pocos clics desde cualquier página. Conviene revisar si existe la página y, si es así, asegurar que esté enlazada visiblemente.
En lenguaje sencillo
Los sitios web con actividad económica deben mostrar los datos identificativos de su titular (art. 10 de la LSSI-CE, Ley 34/2002). Habitualmente se publican en una página de 'Aviso legal' enlazada desde todas las páginas. Aquí no se ha encontrado un enlace a esa información.
Ejemplo: Un cliente quiere saber quién está detrás de la tienda para reclamar un pedido y no encuentra ningún dato de contacto del titular. Añadir la página y el enlace en el pie lleva alrededor de una hora.
Guía de corrección ~1 h · Fácil
- El art. 10 de la LSSI-CE (Ley 34/2002) establece la información general que debe ofrecer quien presta un servicio de la sociedad de la información con actividad económica. Habitualmente se publica en una página de "Aviso legal".
- Crear una página
/aviso-legalcon los datos identificativos: denominación social o nombre y apellidos, domicilio, dirección de correo electrónico y otro medio de contacto directo, NIF/CIF, datos de inscripción en el Registro Mercantil si procede y, para profesiones reguladas, colegio profesional y título. - Colocar el enlace en el pie de la plantilla para que aparezca en todas las páginas.
- El enlace debe poder identificarse con claridad, por ejemplo como "Aviso legal". Nombres creativos como "Quiénes somos" o "Contacto" no resultan claros para esta función.
- Qué datos corresponden exactamente a cada caso lo determina el titular junto con su asesoría; aquí solo se indica la norma.
<!-- en el pie de la plantilla -->
<footer>
<nav aria-label="Información legal">
<a href="/aviso-legal">Aviso legal</a>
<a href="/politica-de-privacidad">Política de privacidad</a>
<a href="/politica-de-cookies">Política de cookies</a>
</nav>
</footer>LEVENo se encuentra enlace a la Política de Privacidad en la portadaAñadir un enlace visible a la Política de Privacidad en el pie de página, accesible desde todas las páginas.
No se detecta ningún enlace hacia una Política de Privacidad desde la portada. El RGPD (art. 13) suele requerir informar a los usuarios sobre el tratamiento de datos, y el servidor ya procesa datos personales como la IP en cada visita. Conviene verificar si el documento existe y asegurar que esté enlazado de forma visible.
En lenguaje sencillo
Toda web que trata datos personales debe informar sobre ello (art. 13 del RGPD y LOPDGDD). Eso ocurre prácticamente siempre, porque el propio servidor registra la dirección IP de cada visita en sus archivos de registro. Suele publicarse como 'Política de privacidad'.
Ejemplo: Una persona solicita información sobre sus datos y no encuentra ninguna política de privacidad en la web. Lo que era un trámite sencillo pasa a ser una gestión con plazos.
Guía de corrección ~2 h · Media
- El art. 13 del RGPD (y la LOPDGDD) regula la información que debe facilitarse a quien aporta sus datos personales. Afecta a prácticamente cualquier web, porque el propio servidor ya registra la dirección IP en sus archivos de registro.
- Crear una página
/politica-de-privacidad. Suele incluir: responsable del tratamiento y datos de contacto, qué datos se tratan y con qué finalidad, base jurídica, plazo de conservación, destinatarios y servicios utilizados, derechos de las personas interesadas y derecho a reclamar ante la autoridad de control (en España, la AEPD). - Lo decisivo es que figuren todos los servicios: cada servicio externo que ha detectado esta revisión debe estar mencionado.
- El texto debe corresponder a la web. Una política copiada sin revisar describe cosas que no existen y omite las que sí.
- Colocar el enlace en el pie de la plantilla, junto al Aviso legal.
# Estos servicios deben aparecer en la política
# si se han detectado en el informe:
- Alojamiento web / archivos de registro del servidor (siempre)
- Google Fonts (si se cargan externamente)
- Google Analytics / Tag Manager
- Píxel de Meta
- Google Maps / YouTube
- reCAPTCHA
- Formulario de contacto / boletínLEVECampo del formulario sin etiqueta para lectores de pantallaAñadir una etiqueta <label> o atributo aria-label asociado al campo del formulario.
El campo carece de una etiqueta <label> asociada o de un aria-label, por lo que un lector de pantalla solo anuncia 'campo de entrada' sin más contexto. El texto de marcador de posición no sustituye a la etiqueta, ya que desaparece al escribir. Esto afecta justo al canal de contacto, relevante para la Ley 11/2023 y el Real Decreto 1112/2018 sobre accesibilidad (WCAG 3.3.2).
En lenguaje sencillo
Los campos de un formulario sin etiqueta no le dicen al lector de pantalla qué hay que escribir. Un texto gris de ejemplo no basta: desaparece en cuanto se empieza a escribir.
Ejemplo: El formulario de contacto es justo el camino por el que llegan las consultas. Quien no puede rellenarlo no pregunta, y nadie sabrá nunca que hubo un cliente.
Guía de corrección ~20 min por formulario · Fácil
- Campos afectados:
email. - Cada campo lleva un
<label>cuyoforapunta aliddel campo. Es la solución limpia, porque además un clic en la etiqueta coloca el cursor en el campo. - Un texto de ejemplo (placeholder) no sustituye a la etiqueta: desaparece al escribir, suele tener poco contraste y algunos lectores de pantalla ni lo leen.
- Si la etiqueta debe quedar invisible por motivos de diseño, no usar
display:none, porque también la oculta al lector de pantalla. Usar una clase auxiliar que solo la saque de la vista. - Marcar los campos obligatorios con
required, no solo con un asterisco en el texto. - Casi siempre afecta al formulario de contacto, es decir, justo al camino por el que llegan las consultas.
<!-- INCORRECTO: solo texto de ejemplo -->
<input type="email" placeholder="Su correo">
<!-- CORRECTO -->
<label for="email">Correo electrónico</label>
<input type="email" id="email" name="email" required
autocomplete="email" placeholder="nombre@ejemplo.es">
<!-- Etiqueta oculta a la vista, presente para el lector de pantalla -->
<label for="busqueda" class="visually-hidden">Término de búsqueda</label>
<input type="search" id="busqueda" name="q">
<style>
.visually-hidden {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
</style>LEVELibrería jQuery desactualizada con vulnerabilidades conocidasActualizar jQuery a una versión 3.5.0 o superior.
La versión 1.12.4 de jQuery tiene 6 vulnerabilidades conocidas en la base Retire.js, con severidad máxima media (CVE-2015-9251, CVE-2019-11358, CVE-2020-11023, CVE-2020-11022). Las versiones 1.x y 2.x ya no reciben actualizaciones de seguridad; desde la versión 3.5.0 estos problemas están resueltos. La explotabilidad real depende del uso concreto que haga el sitio de esta librería.
En lenguaje sencillo
Casi todas las webs cargan pequeñas piezas de código (bibliotecas JavaScript) como jQuery o Bootstrap. Si nunca se actualizan, se acumulan vulnerabilidades conocidas que figuran en bases de datos públicas. Que una vulnerabilidad se pueda aprovechar en una web concreta depende de cómo se use esa pieza.
Ejemplo: Una agencia montó hace años una web con jQuery 1.12. Hoy un análisis de seguridad señala justo esa versión como vulnerable conocida. Actualizar suele llevar minutos, lo que requiere tiempo es probar.
Guía de corrección ~1 h · Media
- Detectada:
jQuery 1.12.4; las entradas citadas afectan solo a versiones anteriores a 3.5.0. - Averiguar de dónde procede el archivo: tema, plugin, maquetador o código propio. En WordPress suele traerlo un plugin; entonces se actualiza el plugin en lugar de sustituir el archivo.
- Pasar a una versión actual de la misma rama o superior. En jQuery, el plugin jQuery Migrate ayuda a localizar llamadas antiguas que dejan de funcionar al saltar a la 3.x.
- Después, recorrer la web: menús, carruseles, formularios y ventanas emergentes son los sitios habituales donde se esconden bibliotecas antiguas.
- Conviene servir las bibliotecas desde el propio servidor en lugar de cargarlas desde una CDN. Evita una conexión con terceros y mantiene la versión bajo control.
# En proyectos con npm: mostrar vulnerabilidades conocidas en las dependencias
npm auditLEVEWordPress en versión desactualizada (6.2)Actualizar WordPress a la última versión disponible y ocultar la etiqueta generator del código fuente.
El código fuente revela WordPress 6.2 mediante la etiqueta generator, mientras ya existe la versión 6.2.15 dentro de esa misma rama. Sin las actualizaciones de seguridad correspondientes, vulnerabilidades ya conocidas y documentadas permanecen sin corregir. Además, mostrar la versión exacta en el código facilita que un atacante busque fallos específicos de esa versión.
En lenguaje sencillo
Un CMS como WordPress, Joomla o Drupal recibe actualizaciones de seguridad del fabricante mientras la versión tiene soporte. Después, las vulnerabilidades nuevas quedan abiertas. Si además la web muestra su versión en el código fuente, los rastreos automáticos la encuentran con más facilidad.
Ejemplo: Un cliente mantiene desde hace años la misma web en Joomla. La versión deja de tener soporte, nadie se da cuenta, hasta que un rastreo de esa versión incluye la web en su lista.
Guía de corrección ~1 a 3 h · Media
- Primero, una copia de seguridad completa (archivos y base de datos) y, si es posible, probar la actualización en una copia de pruebas.
- Llevar el CMS a la versión actual con soporte. Si se salta varias versiones principales, leer la guía de migración del fabricante y comprobar la compatibilidad de temas y extensiones.
- Después, quitar o desactivar la etiqueta meta generator para que la versión no figure en el código fuente. En WordPress basta la línea de abajo en el functions.php del tema.
- Los parámetros de versión como
?ver=6.xen archivos del núcleo no desaparecen solos; no son críticos mientras el sistema esté al día.
// WordPress: quitar la etiqueta meta generator (functions.php)
remove_action('wp_head', 'wp_generator');Menor (9)
MENORDNSSEC no está activadoActivar DNSSEC en el proveedor de DNS cuando se revisen otras configuraciones del dominio.
Sin DNSSEC, las respuestas DNS no están firmadas criptográficamente, lo que en teoría permite ataques de manipulación de respuestas DNS. Es una medida recomendable pero de prioridad baja frente a otros hallazgos.
En lenguaje sencillo
DNSSEC es una garantía de autenticidad para la resolución de direcciones del dominio. Sin ella, se puede dirigir a las visitas a un destino falso sin que lo noten.
Ejemplo: En una red manipulada, sudominio.es se redirige en secreto al servidor de un atacante. Las visitas llegan a una copia y no se dan cuenta.
MENORNo hay registro BIMI configuradoValorar la configuración de BIMI una vez resueltos SPF, DKIM y DMARC.
BIMI permite mostrar el logo de la marca en clientes de correo como Gmail o Yahoo, pero requiere un certificado específico (CMC o VMC) y depende de tener SPF/DKIM/DMARC correctamente configurados primero.
Guía de corrección ~30 min (más la obtención del certificado) · Media
- ⚠️ Falta un requisito: todavía no hay registro DMARC. BIMI solo funciona con DMARC en
p=quarantineop=reject; primero hay que resolver el hallazgo de DMARC. - Hay que contar con costes recurrentes: sin certificado, ninguno de los grandes proveedores muestra el logotipo. Gmail exige como mínimo un CMC (Common Mark Certificate) y Apple Mail un VMC (Verified Mark Certificate); la marca azul de verificación de Gmail solo existe con VMC. Ambos los emiten DigiCert y Entrust y se facturan cada año.
- Y conviene comprobar de antemano si se puede obtener uno: un VMC exige una marca gráfica registrada; un CMC, al menos doce meses de uso demostrable del mismo logotipo. Un logotipo reciente sin marca registrada no cumple ninguna de las dos condiciones.
- Alojar el logotipo en SVG (Tiny PS, cuadrado, fondo de un solo color, máx. 32 KB) en
https://ejemplo.itvweb.es/bimi-logo.svg. - Solo cuando existan certificado y logotipo: crear el registro TXT de
default._bimi.ejemplo.itvweb.esy apuntar con el campoa=al archivo del certificado.
v=BIMI1; l=https://ejemplo.itvweb.es/bimi-logo.svg; a=https://ejemplo.itvweb.es/bimi-cert.pemMENORMTA-STS no está configuradoConfigurar MTA-STS en el proveedor de correo tras resolver SPF y DMARC.
La ausencia de MTA-STS implica que no hay una política que fuerce el uso de TLS en la entrega de correo entre servidores, lo que en teoría permite ataques de downgrade en el transporte del correo.
Guía de corrección ~45 min · Media
- 1. Crear el subdominio
mta-sts.ejemplo.itvweb.esy hacerlo accesible por HTTPS con un certificado válido; basta un alojamiento sencillo, solo se sirve un archivo de texto. - 2. Publicar el archivo de política en
https://mta-sts.ejemplo.itvweb.es/.well-known/mta-sts.txt(texto plano): - ⚠️ Importante: no se han podido leer los registros MX del dominio. Hay que indicar abajo todos los servidores que aceptan correo del dominio, con el nombre de host de los registros MX y no con el dominio. Una entrada que falte o sea errónea, junto con
mode: enforce, hace que deje de recibirse correo. - 3. Solo después crear el registro TXT de
_mta-sts.ejemplo.itvweb.es. Al revés, los remitentes buscarían una política que todavía no existe. - 4. Dejar entre dos y cuatro semanas en
mode: testing, luego pasar amode: enforcey subirmax_agea 604800. Testing informa de problemas pero no bloquea nada; enforce sí bloquea. Al cambiar, incrementar eliddel registro TXT; si no, los remitentes conservan la versión antigua en caché. - 5. Antes de pasar a enforce, comprobar que cada servidor de correo indicado tiene un certificado válido emitido exactamente para su nombre de host. Si no, la comprobación falla y el correo queda retenido.
# 1. Archivo de política: https://mta-sts.ejemplo.itvweb.es/.well-known/mta-sts.txt
version: STSv1
mode: testing
mx: INDICAR-AQUI-LOS-SERVIDORES-DE-CORREO
max_age: 86400
# 2. Registro DNS TXT (_mta-sts.ejemplo.itvweb.es), crearlo DESPUÉS del archivo de política:
v=STSv1; id=20261106MENORNo hay registro CAA en el DNSAñadir un registro CAA en el DNS limitando las autoridades de certificación autorizadas.
El registro CAA permite restringir qué autoridades de certificación pueden emitir certificados TLS para el dominio. Su ausencia no implica un ataque activo, pero elimina una capa de control preventivo.
En lenguaje sencillo
Un registro CAA establece qué entidades pueden emitir certificados SSL para el dominio. Si falta, en teoría cualquier entidad certificadora puede emitir uno.
Ejemplo: Alguien logra que una entidad certificadora cualquiera emita un certificado para el dominio y monta con él una copia muy convincente de la web.
Guía de corrección ~10 min · Fácil
- Crear el registro DNS CAA de
ejemplo.itvweb.es: - Si se usa Let's Encrypt: indicar letsencrypt.org.
- Si se usa SSL de Cloudflare: añadir además pki.goog.
- Con iodef= se recibe un correo cuando alguien solicita un certificado sin autorización.
ejemplo.itvweb.es. CAA 0 issue "letsencrypt.org"
ejemplo.itvweb.es. CAA 0 issuewild ";"
ejemplo.itvweb.es. CAA 0 iodef "mailto:admin@ejemplo.itvweb.es"MENORLa cabecera del servidor revela la tecnología usada (PleskLin)Eliminar o enmascarar la cabecera X-Powered-By en la configuración del servidor.
El encabezado X-Powered-By revela que el servidor usa Plesk sobre Linux, lo que da información adicional a un atacante pero no constituye una vulnerabilidad por sí mismo.
En lenguaje sencillo
El servidor revela hacia fuera qué software utiliza y en qué versión. Para un atacante es como un cartel en la puerta que indica el modelo exacto de la cerradura.
Ejemplo: Un atacante lee 'Server: Apache 2.2' y busca directamente los fallos conocidos de esa versión antigua, en lugar de probar a ciegas.
MENORArchivo .DS_Store accesible públicamenteEliminar el archivo .DS_Store del servidor y bloquear su acceso público mediante la configuración del servidor web.
El archivo .DS_Store es un artefacto de macOS Finder que puede listar nombres de archivos y carpetas del directorio donde se generó. Su exposición pública es un riesgo bajo pero facilita el reconocimiento de la estructura interna del sitio a un atacante.
En lenguaje sencillo
Shodan es un buscador de dispositivos y servicios conectados a internet. Muestra qué parte de la infraestructura es accesible públicamente, a menudo más de lo que se pretendía.
Ejemplo: Una base de datos o un acceso de administración pensado solo para uso interno queda accesible por error y aparece en Shodan, donde cualquiera puede encontrarlo.
MENORRecurso externo cargado desde CDN de Cloudflare (cdnjs)Valorar alojar el recurso localmente si se quiere minimizar transmisiones a terceros.
cdnjs.cloudflare.com sirve una librería JavaScript de uso común. No es un rastreador, pero transmite la IP del visitante a un tercero durante la carga. El impacto en privacidad es menor comparado con otros servicios de terceros.
Ver explicación en «Google Fonts se carga desde servidores externos sin consulta previa»
Guía de corrección ~30 min · Fácil
- La página carga un archivo de
cdnjs.cloudflare.com. Con ello, un tercero conoce la dirección IP de cada visita. - Descargar el archivo y servirlo desde el propio alojamiento. Así se resuelve el punto de protección de datos y, de paso, la web deja de depender de la disponibilidad del servicio ajeno.
- En el código fuente, sustituir la URL externa por la ruta propia.
- Comprobar: Herramientas de desarrollo → Red; el dominio ya no debe aparecer.
- Si la carga externa se mantiene, el servicio debe figurar en la política de privacidad.
<!-- antes -->
<script src="https://cdnjs.cloudflare.com/....js"></script>
<!-- después -->
<script src="/assets/js/....js"></script>MENORNo hay enlace a una Política de Cookies en la portadaCrear y enlazar una Política de Cookies visible desde la portada, siguiendo la guía de la AEPD.
La portada no enlaza a una Política de Cookies ni tampoco a una Política de Privacidad, pese a que se observan cookies o recursos de terceros al cargar la página. El art. 22.2 de la LSSI-CE junto con la guía de la AEPD sobre cookies orientan hacia un enlace bien visible bajo una denominación habitual como 'Política de cookies'.
En lenguaje sencillo
Una política de cookies explica qué cookies utiliza un sitio web, para qué sirven y cuánto tiempo permanecen. La guía de la AEPD describe como práctica habitual un enlace claramente visible bajo una denominación común como 'Política de cookies'. Esta comprobación solo constata si ese enlace aparece en la página de inicio.
Ejemplo: Una persona quiere saber qué autoriza realmente el botón 'Aceptar' del aviso. Sin enlace, solo le queda abrir las herramientas del navegador o abandonar la página.
Guía de corrección ~30 min · Fácil
- Crear una página propia de 'Política de cookies' que indique, por cada cookie, el nombre, el proveedor, la finalidad y la duración.
- No mantener la lista a mano: herramientas de consentimiento como Cookiebot, Complianz o iubenda generan la tabla a partir de un análisis y la mantienen al día.
- Colocar el enlace en un lugar bien visible: en el pie de todas las páginas y en la primera capa del aviso de cookies, con la denominación habitual en lugar de un rodeo por otras páginas.
- También se puede integrar la información sobre cookies en la política de privacidad. La guía de la AEPD menciona esa vía; en ese caso, el enlace debe nombrarlo con claridad.
<a href="/politica-de-cookies/">Política de cookies</a>MENORFormulario sin enlace cercano a la Política de PrivacidadAñadir un enlace a la Política de Privacidad justo junto al formulario de contacto.
Ni dentro ni alrededor del formulario hay un enlace a la Política de Privacidad; solo contar con el enlace en el pie de página no se considera suficiente. El art. 13 del RGPD, junto con el art. 11 de la LOPDGDD sobre información por capas, orientan hacia un enlace directo junto al formulario.
Ver explicación en «Formulario de contacto envía datos sin cifrar por HTTP»
Guía de corrección ~20 min · Fácil
- La dirección del atributo
actiondebe empezar porhttps://o ser relativa (/contacto). Cambiar cualquier direcciónhttp://y servir la propia página solo por HTTPS. - Si el formulario envía a un servicio externo (herramienta de boletines, proveedor de formularios): nombrar al destinatario en la política de privacidad y comprobar si existe contrato de encargado del tratamiento. Eso no es visible desde fuera.
- Junto al botón de enviar, añadir una frase con enlace a la política de privacidad. Un enlace solo en el pie de página no es información 'en el formulario'.
- Si se pide consentimiento (por ejemplo, para un boletín), la casilla con el mismo enlace forma parte del formulario. No dejarla marcada de antemano.
<form action="/contacto" method="post">
<input type="email" name="email" required>
<button type="submit">Enviar</button>
<p><small>Información sobre el tratamiento de los datos: <a href="/politica-de-privacidad/">Política de privacidad</a></small></p>
</form>Info (5)
INFOInventario de tecnologías detectadas en el sitioRevisar y actualizar WordPress, jQuery y plugins asociados a sus versiones más recientes.
Se identificó PHP, MySQL, WordPress 6.2, Google Font API, jQuery 1.12.4, Nginx y Plesk. Conviene revisar si WordPress y jQuery están actualizados a sus últimas versiones estables, ya que versiones antiguas pueden acumular vulnerabilidades conocidas.
En lenguaje sencillo
Se detecta qué software hace funcionar la web y si hay versiones desactualizadas. El software antiguo suele tener fallos de dominio público, para los que existen herramientas de ataque ya preparadas.
Ejemplo: La web funciona con un software de tienda de hace tres años. Para su fallo conocido existe una herramienta lista para usar con la que incluso personas sin conocimientos toman el control de miles de webs así.
INFOPlan de pruebas específico para la plataforma WordPressNinguna acción directa; sirve de contexto para otros hallazgos de la plataforma.
El escáner identificó WordPress como plataforma base y ejecutó 56 comprobaciones específicas para este tipo de instalación. Es un paso informativo que contextualiza otros hallazgos relacionados con la plataforma.
INFOTodos los servicios de IA pueden leer la webDecidir si se desea restringir el acceso de bots de IA mediante robots.txt, según la estrategia de contenidos de la empresa.
No existe archivo robots.txt que limite el acceso, por lo que todos los bots de IA conocidos (ChatGPT, Claude, Perplexity, etc.) pueden leer e indexar el contenido. Es una decisión de negocio, no un riesgo técnico.
En lenguaje sencillo
Un sitio web puede indicar a los servicios de IA si pueden leer su contenido. Hay dos tipos: la búsqueda con IA, que consulta una página cuando alguien pregunta a ChatGPT o Perplexity, y el entrenamiento de IA, que recopila contenido para aprender. Ambos se pueden permitir o bloquear por separado. Qué es lo adecuado lo decide el titular del sitio.
Ejemplo: Quien bloquea el entrenamiento pero permite la búsqueda sigue apareciendo con enlace en las respuestas de la IA, sin que sus textos pasen a formar parte de un modelo.
Guía de corrección ~15 min · Fácil
- Decidir primero con el cliente qué debe regir. Abajo hay tres variantes habituales.
- El archivo está en
https://ejemplo.itvweb.es/robots.txt. En WordPress lo genera a menudo un plugin de SEO; en ese caso, cambiarlo allí. - Si el sitio está detrás de Cloudflare: comprobar si está activado el robots.txt gestionado o el bloqueo de bots de IA. Ese ajuste inserta el bloqueo automáticamente y prevalece sobre lo que diga el archivo propio.
- No olvidar otras señales:
ai.txt,/.well-known/tdmrep.json, una cabeceratdm-reservationo metaetiquetasnoai. Si contradicen al robots.txt, la situación resulta poco clara para los servicios de IA. - Después, repetir el análisis: el hallazgo de acceso de la IA muestra el nuevo estado por servicio.
# Variante A: todos los servicios de IA pueden leer
User-agent: *
Allow: /
# Variante B: búsqueda con IA sí, entrenamiento de IA no
User-agent: GPTBot
User-agent: ClaudeBot
User-agent: Google-Extended
User-agent: Applebot-Extended
User-agent: CCBot
User-agent: meta-externalagent
Disallow: /
User-agent: *
Allow: /
# Variante C: ningún servicio de IA
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: ChatGPT-User
User-agent: ClaudeBot
User-agent: Claude-SearchBot
User-agent: Claude-User
User-agent: PerplexityBot
User-agent: Perplexity-User
User-agent: Google-Extended
User-agent: Applebot-Extended
User-agent: CCBot
User-agent: meta-externalagent
Disallow: /INFONo se puede comprobar automáticamente la fecha de caducidad del dominio .esConsultar manualmente con el registrador la fecha de caducidad del dominio y mantener la renovación automática activada.
El registro .es no expone la fecha de expiración vía RDAP, a diferencia de otros TLD. Esto no indica ningún problema, solo una limitación técnica de la consulta automática.
En lenguaje sencillo
Se comprueba cuándo caduca el dominio. Si caduca sin que nadie lo note, la web y el correo dejan de funcionar al instante y, en el peor caso, otra persona lo registra.
Ejemplo: El dominio caduca en un festivo y la renovación falla por una tarjeta caducada. El siguiente día laborable la web está caída y un revendedor de dominios ya lo ha registrado.
INFONo existe archivo security.txtOpcionalmente, publicar un archivo security.txt con un contacto de seguridad.
No hay archivo en /.well-known/security.txt. Este archivo, descrito en el RFC 9116, simplemente indica un contacto para reportes de seguridad; su ausencia no implica ninguna vulnerabilidad.
En lenguaje sencillo
El archivo security.txt es un pequeño texto que indica a los investigadores de seguridad a quién comunicar una vulnerabilidad que hayan encontrado. Es un estándar voluntario (RFC 9116) y contiene al menos una dirección de contacto y una fecha de caducidad.
Ejemplo: Una investigadora encuentra un fallo en el portal de clientes, pero no hay dirección de contacto. Lo notifica por un formulario general donde el mensaje queda sin atender.
Guía de corrección ~10 min · Fácil
- Guardar un archivo de texto en
/.well-known/security.txt(crear la carpeta.well-knownen la raíz web). - Como mínimo dos líneas:
Contactcon una dirección de correo o una URL, yExpirescon una fecha que no supere un año en el futuro. - Anotar la fecha de caducidad en el calendario. Un archivo caducado indica a los investigadores que nadie lo atiende.
- Comprobar: abrir el archivo en el navegador por https. Debe verse como texto plano, no como página web.
Contact: mailto:seguridad@ejemplo.itvweb.es
Expires: 2027-11-06T00:00:00.000Z
Preferred-Languages: es, en