GhostAcademy
GhostAcademy
Client-Side Security — Defensa de Aplicaciones Web
Manual Completo
Seguridad Client-Side
Defensa del Lado del Cliente

La seguridad del lado del cliente cubre todas las medidas que protegen al navegador, las sesiones de usuario y los datos procesados en el front-end. XSS, CSRF, Clickjacking, cookies inseguras, CORS mal configurado y headers de seguridad ausentes son las vulnerabilidades más comunes y directamente explotables desde el navegador.

★ Por qué importa la seguridad client-sideEl 70% de los ataques web exitosos explotan vulnerabilidades del lado del cliente. Son especialmente peligrosas porque el atacante puede ejecutarlas directamente desde el navegador sin acceso al servidor, y porque afectan a los usuarios finales de forma masiva.

Áreas cubiertas en este manual

💉 XSS
Crítico
Cross-Site Scripting — inyección de JavaScript malicioso en páginas visitadas por otros usuarios. Permite robar sesiones, keylogging y redireccionamiento.
🛡 CSP
Defensa
Content Security Policy — cabecera HTTP que controla qué recursos puede cargar el navegador. La defensa más potente contra XSS.
🖱 Clickjacking
Alto
Superposición de iframes invisibles para engañar al usuario. Defensa: X-Frame-Options y CSP frame-ancestors.
🍪 Cookies
Crítico
Configuración segura: HttpOnly, Secure, SameSite — primera línea de defensa de sesiones de usuario.
🔄 CSRF
Alto
Cross-Site Request Forgery — peticiones maliciosas ejecutadas en nombre de un usuario autenticado sin su conocimiento.
🌐 CORS y SOP
Fundamental
Same-Origin Policy y Cross-Origin Resource Sharing — controlan qué puede acceder a qué entre distintos dominios.
📋 Security Headers
Defensa
HSTS, X-Frame-Options, X-Content-Type-Options — cabeceras HTTP que activan mecanismos de protección en el navegador.
🏗 DOM Security
Alto
APIs peligrosas del DOM, postMessage, LocalStorage — manejo seguro de datos procesados en el cliente.
Fundamentos — 01
🌐 El Navegador como Entorno de Seguridad

El navegador es el runtime de toda aplicación web. Entender qué controles implementa automáticamente — y cuáles dependen del servidor — es el punto de partida para defender aplicaciones client-side.

Modelo de seguridad del navegador

MecanismoQué protegeQuién lo configura
Same-Origin PolicyAísla el contenido de distintos orígenes — un script de evil.com no puede leer respuestas de bank.comEl navegador automáticamente
Sandbox de iframesLimita lo que puede hacer un iframe embebido: sin acceso al DOM padre, sin navegaciónAtributo sandbox en el HTML
HTTPS / TLSCifra el tráfico entre el navegador y el servidor para evitar intercepcionesServidor + navegador
Flags de cookiesHttpOnly evita acceso JS; Secure solo envía por HTTPS; SameSite limita envío cross-siteEl servidor al emitir la cookie
CSPControla qué scripts, estilos y recursos puede cargar la páginaEl servidor vía cabecera HTTP
CORSPermite al servidor autorizar peticiones cross-origin de forma controladaEl servidor vía cabeceras HTTP
ℹ El navegador confía en el servidorLa mayoría de controles dependen de que el servidor envíe las cabeceras correctas (CSP, HSTS, X-Frame-Options) y configure las cookies con los flags adecuados. Un servidor que no envía estas cabeceras deja al usuario desprotegido aunque el navegador esté actualizado.
Vulnerabilidades — 01
💉 XSS — Cross-Site Scripting

XSS es la vulnerabilidad más común en aplicaciones web. Ocurre cuando la aplicación incluye datos no confiables en una página sin el encoding adecuado, permitiendo a un atacante ejecutar JavaScript en el navegador de otros usuarios.

Los 3 tipos de XSS

TipoCómo funcionaPersistenciaImpacto
Reflected XSSEl payload viaja en la URL o formulario, el servidor lo refleja sin sanitizar. La víctima debe hacer clic en un enlace malicioso.No persisteAlto
Stored XSSEl payload se almacena en la BBDD (comentario, perfil) y se muestra a todos los usuarios que visitan esa página.Persiste en BBDDCrítico
DOM-based XSSJavaScript de la página lee datos del DOM (URL, hash) y los inserta sin sanitizar. El servidor nunca ve el payload.Solo en clienteAlto

¿Qué puede hacer un atacante con XSS?

🍪 Robo de sesión
Crítico
Si la cookie no tiene HttpOnly, el script lee document.cookie y la exfiltra al atacante — secuestro de sesión completo.
🔑 Keylogging
Alto
El script captura cada tecla pulsada en la página, incluyendo contraseñas escritas en formularios.
🔄 Phishing
Alto
Redirigir al usuario a una página de phishing idéntica para capturar credenciales.
📷 Exfiltración
Medio
Leer datos privados de la página: tokens CSRF, datos de otros usuarios, y enviarlos al atacante.

Contextos de inyección XSS

HTML — Payloads XSS por contexto
## CONTEXTO HTML — en el cuerpo de la página
<script>alert(document.cookie)</script>
<img src=x onerror="alert(1)">
<svg onload="alert(1)">

## CONTEXTO ATRIBUTO HTML
" onmouseover="alert(1)    ← cierra el atributo e inyecta evento

## CONTEXTO JAVASCRIPT — dentro de un bloque JS
'-alert(1)-'               ← cierra el string y ejecuta

## DOM XSS — sink peligroso en el código del cliente
// Si el código hace esto:
document.getElementById('out').innerHTML = location.hash.slice(1);
// El atacante visita: https://sitio.com/#<img src=x onerror=alert(1)>
Defensas — 01
🛡 CSP — Content Security Policy

Content Security Policy es la defensa más potente contra XSS. Es una cabecera HTTP que instruye al navegador sobre qué fuentes de contenido son legítimas. Si un script no viene de una fuente autorizada, el navegador lo bloquea — aunque el HTML contenga código XSS.

★ CSP no elimina el XSS — lo contieneCSP es una defensa en profundidad: si hay un error que permite XSS, CSP puede evitar que el payload se ejecute o exfiltre datos. No sustituye al encoding correcto — es una red de seguridad adicional.

Directivas CSP más importantes

DirectivaControlaEjemplo seguro
default-srcFuente por defecto para todos los recursos no especificadosdefault-src 'self'
script-srcFuentes permitidas para JavaScriptscript-src 'self' 'nonce-RANDOM'
style-srcFuentes permitidas para CSSstyle-src 'self'
img-srcFuentes permitidas para imágenesimg-src 'self' data: https:
connect-srcURLs a las que JS puede conectarse (fetch, XHR, WebSocket)connect-src 'self' api.ejemplo.com
frame-ancestorsQué páginas pueden embeber esta en un iframeframe-ancestors 'none'
report-uriURL donde el navegador reporta violaciones de CSPreport-uri /csp-report
HTTP / Nginx — Configurar CSP
## CABECERA HTTP
Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self' data:; frame-ancestors 'none'; report-uri /csp-violations

## NGINX
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; frame-ancestors 'none';" always;

## MODO REPORT-ONLY — probar sin bloquear (ideal para despliegue progresivo)
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-violations

## NONCES — forma más segura para scripts inline legítimos
# El servidor genera un nonce aleatorio por petición:
Content-Security-Policy: script-src 'self' 'nonce-RANDOM_BASE64'
# El HTML incluye el nonce en cada script legítimo:
<script nonce="RANDOM_BASE64">codigo_legitimo();</script>
# Scripts sin nonce correcto son bloqueados — incluyendo los de XSS
⚠ Errores que anulan la CSP'unsafe-inline' en script-src cancela completamente la protección contra XSS. Wildcards (*) en script-src permiten scripts de cualquier dominio. Usar siempre nonces o hashes en lugar de unsafe-inline.
Defensas — 02
🔒 Defensa contra XSS

La defensa primaria contra XSS es el encoding correcto según el contexto de salida. CSP es la segunda línea. Juntos hacen que XSS sea extremadamente difícil de explotar.

JavaScript / Python — Encoding correcto por contexto
## CONTEXTO HTML — nunca innerHTML con datos del usuario
element.innerHTML = userInput;        // MAL — interpreta HTML y ejecuta scripts
element.textContent = userInput;      // BIEN — trata siempre como texto plano

## EN EL SERVIDOR (encoding de salida)
htmlspecialchars($input, ENT_QUOTES); // PHP
HtmlUtils.htmlEscape(input);          // Java/Spring
{{ input | e }}                        // Jinja2 (autoescape activo por defecto)

## CONTEXTO JAVASCRIPT — serializar como JSON, nunca concatenar
var x = "'" + userInput + "'";       // MAL — inyección trivial
var x = {{ input | tojson }};         // BIEN — escaping automático

## SANITIZACIÓN — cuando necesitas aceptar HTML del usuario
# Usar una librería especializada, nunca hacer una propia
DOMPurify.sanitize(userHTML);         // JS — la más recomendada
bleach.clean(userHTML, tags=allowed); // Python

Checklist de defensa XSS

  • Nunca usar innerHTML, document.write() o eval() con datos del usuario
  • Usar textContent en lugar de innerHTML para texto plano
  • HTML entity encoding en todas las salidas del servidor en contexto HTML
  • CSP con nonces o hashes — sin unsafe-inline
  • DOMPurify para HTML del usuario en el cliente
Vulnerabilidades — 02
🖱 Clickjacking — Secuestro de Clics

Clickjacking engaña al usuario para que haga clic en un elemento invisible. El atacante superpone un iframe transparente de la página objetivo sobre contenido atractivo — el usuario cree que hace clic en algo inofensivo pero activa una acción en la página objetivo.

HTML — Cómo funciona el ataque de clickjacking
<!-- Página maliciosa controlada por el atacante -->
<style>
  iframe {
    position: absolute;
    opacity: 0.0;          /* invisible para la víctima */
    width: 400px; height: 200px;
    top: 120px; left: 50px;
  }
  .senhuelo { position: absolute; top: 130px; left: 60px; }
</style>
<iframe src="https://banco.com/transferir?destino=atacante&cantidad=1000"></iframe>
<button class="senhuelo">Haz clic para ganar un premio</button>
<!-- La víctima hace clic en el "premio" pero activa la transferencia bancaria -->

Vectores comunes de clickjacking

EscenarioQué consigue el atacante
Botón "Me gusta" en RRSS superpuestoLa víctima da like a contenido sin saberlo
Botón de transferencia bancariaIniciar una transferencia en nombre de la víctima
Botón "Activar cámara/micrófono"Conceder permisos de hardware sin consentimiento
Formulario de compra superpuestoCompletar una compra en nombre de la víctima
Defensas — 03
🖼 X-Frame-Options y frame-ancestors

La defensa contra clickjacking es controlar quién puede embeber tu página en un iframe. Hay dos mecanismos: la cabecera X-Frame-Options (legacy) y la directiva CSP frame-ancestors (moderna y más flexible).

HTTP / Nginx / Apache — Protección anti-clickjacking
## X-FRAME-OPTIONS (amplio soporte, legacy)
X-Frame-Options: DENY          # nadie puede embeber esta página en un iframe
X-Frame-Options: SAMEORIGIN    # solo el mismo origen puede embeber

## CSP frame-ancestors (moderno — más flexible y con más opciones)
Content-Security-Policy: frame-ancestors 'none';             # equivale a DENY
Content-Security-Policy: frame-ancestors 'self';             # equivale a SAMEORIGIN
Content-Security-Policy: frame-ancestors https://socio.com;  # solo ese dominio

## NGINX — añadir las dos para máxima compatibilidad
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none';" always;

## APACHE
Header always set X-Frame-Options "DENY"
💡 Usar frame-ancestors si ya tienes política CSPSi ya configuras CSP en tu servidor, añade frame-ancestors dentro de esa política. Tiene precedencia sobre X-Frame-Options en navegadores modernos. Mantén X-Frame-Options también para compatibilidad.
Vulnerabilidades — 03
🍪 Seguridad en Cookies

Las cookies de sesión son el activo más crítico de una aplicación web. Si un atacante roba la cookie de sesión puede suplantar la identidad del usuario completamente. Los flags de seguridad son la primera línea de defensa.

Los flags de seguridad de las cookies

FlagQué haceSin este flag...
HttpOnlyLa cookie NO es accesible desde JavaScript (document.cookie)Un XSS puede robar la cookie con document.cookie y enviarla al atacante
SecureLa cookie SOLO se envía por conexiones HTTPSLa cookie viaja en claro por HTTP — interceptable en redes inseguras
SameSite=StrictLa cookie NO se envía en ninguna petición cross-siteLa cookie se envía en peticiones cross-site — vulnerable a CSRF
SameSite=LaxLa cookie se envía solo en navegación top-level GET cross-site, no en POSTProtección parcial — bloquea POSTs cross-site pero no todos los GETs
SameSite=None; SecureLa cookie se envía siempre (para APIs cross-origin legítimas)Sin Secure los navegadores modernos la rechazan directamente
HTTP / Node.js / Python / PHP — Configurar cookies seguras
## CABECERA HTTP directa
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

## NODE.JS — Express con express-session
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: {
    httpOnly: true,        // no accesible por JavaScript
    secure: true,          // solo por HTTPS
    sameSite: 'strict',    // no enviar en peticiones cross-site
    maxAge: 3600000        // 1 hora en milisegundos
  }
}));

## PYTHON — Flask
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE='Strict',
)

## PHP
session_set_cookie_params([
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

## PREFIJO __Host- (opción más segura disponible)
Set-Cookie: __Host-sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/
# Garantiza: Secure activado, sin atributo Domain, Path=/
# Impide que subdominios comprometidos sobreescriban la cookie
Vulnerabilidades — 04
🔄 CSRF — Cross-Site Request Forgery

CSRF explota que el navegador envía automáticamente las cookies al hacer peticiones a un dominio, independientemente del origen de la petición. Un atacante puede hacer que el navegador de la víctima envíe peticiones autenticadas a otra aplicación sin que ella lo sepa.

HTML / Node.js — Ataque CSRF y sus defensas
## EL ATAQUE — página maliciosa visitada por la víctima autenticada en banco.com
<!-- GET — cargar la imagen dispara la petición autenticada -->
<img src="https://banco.com/transferir?a=atacante&cantidad=5000" width="0">

<!-- POST — formulario auto-enviado al cargar la página -->
<form action="https://banco.com/transferir" method="POST" id="f">
  <input type="hidden" name="destino" value="atacante">
  <input type="hidden" name="cantidad" value="5000">
</form>
<script>document.getElementById('f').submit();</script>

## DEFENSA 1: Token CSRF — el más robusto
<form method="POST" action="/transferir">
  <input type="hidden" name="_csrf" value="{{ csrfToken }}">
  ...
</form>
// En el servidor: verificar que el token coincide con el de la sesión

## DEFENSA 2: SameSite=Strict en la cookie de sesión
Set-Cookie: sessionId=abc; SameSite=Strict; Secure; HttpOnly
// La cookie no se envía en peticiones cross-site — CSRF imposible sin la cookie

## DEFENSA 3: Verificar el header Origin en el servidor
if (req.headers.origin !== 'https://mi-dominio.com') {
    return res.status(403).send('Forbidden');
}

## Django — protección CSRF automática en todos los formularios
<form method="POST">{% csrf_token %}</form>
Vulnerabilidades — 05
🔐 Gestión Segura de Sesiones

Los fallos en la gestión de sesiones son la categoría A07 del OWASP Top 10. Incluyen tokens predecibles, sesiones que no expiran, session fixation y logout que no invalida el token en el servidor.

Requisitos de un token de sesión seguro

PropiedadRequisitoPor qué importa
LongitudMínimo 128 bits (16 bytes) de entropía realTokens cortos son vulnerables a fuerza bruta
AleatoriedadGenerado con CSPRNG (crypto.randomBytes, secrets)Math.random() de JS NO es criptográficamente seguro
RegeneraciónGenerar nuevo token tras cada login exitosoPreviene session fixation
ExpiraciónTimeout absoluto y por inactividadLimita la ventana si un token es comprometido
Invalidación en logoutEl logout destruye el token en el servidorEliminar solo la cookie del cliente no invalida la sesión
Node.js — Sesiones seguras en Express
## GENERAR TOKEN SEGURO
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');  // 256 bits de entropía

## REGENERAR TRAS LOGIN — prevenir session fixation
app.post('/login', async (req, res) => {
    const user = await verificarCredenciales(req.body);
    if (user) {
        req.session.regenerate((err) => {  // crea un nuevo ID de sesión
            req.session.userId = user.id;
            res.redirect('/dashboard');
        });
    }
});

## LOGOUT SEGURO — destruir en el servidor
app.post('/logout', (req, res) => {
    req.session.destroy((err) => {  // elimina la sesión del servidor
        res.clearCookie('sessionId');
        res.redirect('/login');
    });
});

## TIMEOUT DE INACTIVIDAD
app.use(session({
    rolling: true,                          // renueva el timeout en cada petición
    cookie: { maxAge: 30 * 60 * 1000 }     // 30 minutos de inactividad
}));
Fundamentos — 02
🌐 Same-Origin Policy — La Base de Todo

La Same-Origin Policy (SOP) es el mecanismo de seguridad más fundamental del navegador. Define qué puede acceder a qué entre distintos orígenes. Sin SOP, cualquier script podría leer el contenido de cualquier otra página abierta en el navegador.

¿Qué define un "origen"?

Dos URLs tienen el mismo origen si comparten exactamente: esquema + dominio + puerto. Cualquier diferencia en estos tres elementos significa orígenes distintos.

Ejemplos — Comparativa de orígenes
URL de referencia: https://ejemplo.com:443/pagina.html

https://ejemplo.com/otra.html          → MISMO ORIGEN (443 implícito)
http://ejemplo.com/pagina.html         → DIFERENTE (esquema: http vs https)
https://sub.ejemplo.com/pagina.html    → DIFERENTE (subdominio distinto)
https://ejemplo.com:8080/pagina.html   → DIFERENTE (puerto distinto)
https://otraempresa.com/pagina.html    → DIFERENTE (dominio distinto)

¿Qué bloquea y qué permite la SOP?

Acción¿Bloqueada por SOP?Notas
Leer respuesta de fetch/XHR cross-originSí — bloqueadoA menos que el servidor responda con cabeceras CORS adecuadas
Acceder al DOM de un iframe cross-originSí — bloqueadoNo se puede leer ni modificar el DOM de otro origen
Enviar una petición cross-originNo — permitidoLa petición llega al servidor — lo que se bloquea es LEER la respuesta
Cargar imágenes, scripts y CSS cross-originNo — permitidoPor eso funcionan los CDNs. SRI añade verificación de integridad.
Fundamentos — 03
🔁 CORS — Cross-Origin Resource Sharing

CORS permite a un servidor relajar la SOP de forma controlada: autorizar peticiones de orígenes específicos. Una configuración incorrecta puede anular la SOP completamente y exponer todos los datos a cualquier origen.

HTTP / Node.js — CORS correcto e incorrecto
## CABECERAS CORS CORRECTAS en la respuesta del servidor
Access-Control-Allow-Origin: https://app.ejemplo.com   ← origen explícito
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true                 ← solo si necesitas cookies
Access-Control-Max-Age: 86400

## NODE.JS — Express con cors (configuración segura)
const cors = require('cors');
app.use(cors({
    origin: ['https://app.ejemplo.com', 'https://admin.ejemplo.com'], // lista blanca
    methods: ['GET', 'POST'],
    credentials: true,
}));

## ERRORES COMUNES que crean vulnerabilidades CORS
// MAL — refleja cualquier origen sin validar
res.header('Access-Control-Allow-Origin', req.headers.origin);

// MAL — evil-ejemplo.com pasa la validación
if (origin.includes('ejemplo.com')) { ... }

// BIEN — comparación exacta contra lista blanca
const permitidos = ['https://app.ejemplo.com'];
if (permitidos.includes(req.headers.origin)) {
    res.header('Access-Control-Allow-Origin', req.headers.origin);
}
Defensas — 04
📋 Security Headers HTTP

Las cabeceras de seguridad HTTP instruyen al navegador para activar mecanismos de protección. Son fáciles de implementar y de alto impacto. Su ausencia es un hallazgo estándar en cualquier auditoría web.

Nginx / Node.js — Todas las cabeceras de seguridad
## NGINX — añadir en el bloque server
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none';" always;
server_tokens off;   # ocultar versión de Nginx

## NODE.JS — Helmet hace todo esto en una línea
const helmet = require('helmet');
app.use(helmet());   // activa todas las cabeceras con valores seguros por defecto

Descripción de cada cabecera

CabeceraQué haceValor recomendado
X-Content-Type-OptionsEvita MIME-type sniffing — el navegador respeta el Content-Type declarado sin intentar adivinarlonosniff
Referrer-PolicyControla cuánta info de la URL actual se incluye en el header Referer al navegar a otro sitiostrict-origin-when-cross-origin
Permissions-PolicyControla el acceso a APIs del navegador: cámara, micrófono, geolocalización, notificacionesgeolocation=(), microphone=(), camera=()
Defensas — 05
🔒 HSTS — HTTP Strict Transport Security

HSTS instruye al navegador para que siempre use HTTPS con este dominio, incluso si el usuario escribe http:// o hace clic en un enlace http://. Elimina los ataques de downgrade y SSL stripping.

HTTP — Configurar HSTS paso a paso
## CABECERA HSTS COMPLETA
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

max-age=31536000    → el navegador recuerda usar HTTPS durante 1 año
includeSubDomains   → aplica también a todos los subdominios
preload             → solicitar inclusión en la lista preload del navegador

## DESPLIEGUE PROGRESIVO — aumentar max-age gradualmente
Strict-Transport-Security: max-age=300             # 5 min — testear que todo va bien
Strict-Transport-Security: max-age=86400           # 1 día — confirmar
Strict-Transport-Security: max-age=2592000         # 30 días
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload  # definitivo

## Registrar en la lista preload: https://hstspreload.org
⚠ HSTS es difícil de revertirCon max-age largo, el navegador rechazará conexiones HTTP durante ese periodo. Empieza siempre con 5 minutos y aumenta gradualmente. Antes de añadir preload asegúrate de que TODOS los subdominios funcionan en HTTPS.
Defensas — 06
🔗 Subresource Integrity — SRI

SRI permite al navegador verificar que los recursos cargados desde CDNs no han sido manipulados. Si el hash del archivo no coincide, el navegador no lo ejecuta — aunque el CDN haya sido comprometido.

HTML / Bash — Implementar Subresource Integrity
## SRI EN HTML — añadir integrity y crossorigin
<script
  src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"
  integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
  crossorigin="anonymous">
</script>

<link rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css"
  integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
  crossorigin="anonymous">

## GENERAR HASH SRI desde la terminal
curl -s https://cdn.ejemplo.com/libreria.js | openssl dgst -sha384 -binary | openssl base64 -A

## GENERADOR ONLINE: https://www.srihash.org

## Si el hash no coincide, el navegador bloquea el recurso:
Subresource Integrity check failed for resource
https://cdn.ejemplo.com/libreria.js — posible compromiso del CDN
Fundamentos — 04
🏗 Seguridad en el DOM

El DOM expone muchas APIs potencialmente peligrosas. Usarlas con datos no confiables es la causa principal de DOM XSS y otras vulnerabilidades del lado del cliente.

APIs del DOM peligrosas (sinks)

API peligrosaRiesgoAlternativa segura
element.innerHTML = inputXSS — interpreta el string como HTML con scriptselement.textContent = input
document.write(input)XSS — escribe HTML directamente en el documentoManipulación del DOM con createElement
eval(input)RCE en el cliente — ejecuta código JS arbitrarioNunca usar eval con datos del usuario
setTimeout(input, 0)Como eval si el primer argumento es un stringUsar siempre función: setTimeout(() => fn(), 0)
location.href = inputOpen redirect o javascript: URIValidar que la URL pertenece al dominio propio
JavaScript — localStorage y postMessage seguros
## QUÉ NO guardar en localStorage
localStorage.setItem('sessionToken', token);  // MAL — accesible por JS y por XSS

## Por qué es inseguro para tokens de autenticación:
- Accesible por cualquier JS de la página (incluyendo código XSS)
- No tiene flags como HttpOnly
- Persiste aunque el usuario cierre el navegador

## Alternativa segura: cookies HttpOnly gestionadas por el servidor
El servidor emite la cookie con HttpOnly — el JS nunca puede leerla.
El navegador la envía automáticamente en cada petición.

## postMessage — validar SIEMPRE el origen del mensaje
# ENVIAR: especificar el origen destino (nunca '*' con datos sensibles)
window.parent.postMessage({ tipo: 'datos' }, 'https://destino.com');

# RECIBIR: verificar el origen antes de procesar
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://origen-confiable.com') return; // crítico
    // solo aquí procesar event.data
});
Práctica
🔍 Auditar la Seguridad Client-Side

Herramientas y técnicas para evaluar la seguridad del lado del cliente en tu propio proyecto o en auditorías autorizadas.

BASH — Herramientas de auditoría
## VERIFICAR SECURITY HEADERS desde la terminal
curl -I https://ejemplo.com | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"

## HERRAMIENTAS ONLINE
securityheaders.com          → calificación A-F de todas las cabeceras
observatory.mozilla.org      → auditoría completa de Mozilla
csp-evaluator.withgoogle.com → analizar política CSP y detectar bypasses
ssllabs.com/ssltest          → configuración TLS/SSL y HSTS

## NIKTO — escaneo de configuración del servidor
nikto -h https://localhost:3000 -ssl

## OWASP ZAP — escaneo pasivo y activo
zaproxy -cmd -quickurl http://localhost:3000 -quickout /tmp/zap_report.html

## VERIFICAR COOKIES desde las DevTools del navegador
Chrome/Firefox → F12 → Application → Cookies
Verificar que cada cookie de sesión tiene: HttpOnly ✓, Secure ✓, SameSite: Strict

## RETIRE.JS — detectar librerías JavaScript vulnerables
npm install -g retire && retire --path ./public/js/
Laboratorio — 01
⚙️ Lab: Servidor Nginx Securizado Completamente

Configuración completa de seguridad client-side para un servidor Nginx con todas las cabeceras, TLS seguro y redirección HTTP a HTTPS.

Nginx — Configuración de seguridad completa
# /etc/nginx/sites-available/mi-app-segura
server {
    listen 443 ssl http2;
    server_name ejemplo.com;

    ssl_certificate /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ejemplo.com/privkey.pem;

    # TLS seguro — solo TLS 1.2 y 1.3
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self' data:; frame-ancestors 'none'; report-uri /csp-violations" always;

    server_tokens off;   # ocultar versión de Nginx

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_hide_header X-Powered-By;  # ocultar tecnología del backend
    }
}

# Redirigir HTTP → HTTPS
server {
    listen 80;
    server_name ejemplo.com;
    return 301 https://$host$request_uri;
}
Laboratorio — 02
⚙️ Lab: App Node.js con Seguridad Client-Side Completa

Aplicación Express.js con todas las medidas implementadas: Helmet, cookies seguras, CSRF con tokens, CSP con nonces y logout correcto.

Node.js — Express seguro de principio a fin
// npm install express helmet express-session crypto
const express = require('express');
const helmet  = require('helmet');
const session = require('express-session');
const crypto  = require('crypto');
const app     = express();

// 1. HELMET — security headers automáticos
app.use(helmet({ contentSecurityPolicy: false })); // CSP manual con nonces

// 2. CSP CON NONCES — un nonce único y aleatorio por petición
app.use((req, res, next) => {
    const nonce = crypto.randomBytes(16).toString('base64');
    res.locals.nonce = nonce;
    res.setHeader('Content-Security-Policy',
        `default-src 'self'; ` +
        `script-src 'self' 'nonce-${nonce}'; ` +
        `frame-ancestors 'none'; ` +
        `report-uri /csp-violations`
    );
    next();
});

// 3. SESIONES SEGURAS con prefijo __Host-
app.use(session({
    secret: process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex'),
    name: '__Host-sid',      // prefijo __Host- — máxima seguridad de cookie
    resave: false,
    saveUninitialized: false,
    cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 30 * 60 * 1000  // 30 minutos de inactividad
    }
}));

// 4. CSRF — token por sesión verificado en cada POST/PUT/DELETE
app.use((req, res, next) => {
    if (!req.session.csrfToken)
        req.session.csrfToken = crypto.randomBytes(32).toString('hex');
    res.locals.csrfToken = req.session.csrfToken;
    next();
});

const verificarCSRF = (req, res, next) => {
    if (['POST','PUT','DELETE','PATCH'].includes(req.method)) {
        const token = req.body._csrf || req.headers['x-csrf-token'];
        if (token !== req.session.csrfToken)
            return res.status(403).json({ error: 'Token CSRF inválido' });
    }
    next();
};
app.use(express.urlencoded({ extended: true }));
app.use(verificarCSRF);

// 5. LOGOUT SEGURO — destruir sesión en el servidor
app.post('/logout', (req, res) => {
    req.session.destroy(() => {
        res.clearCookie('__Host-sid');
        res.redirect('/login');
    });
});

// 6. ENDPOINT para reportes de violaciones CSP
app.post('/csp-violations', express.json({ type: 'application/csp-report' }), (req, res) => {
    console.log('[CSP Violation]', req.body);
    res.status(204).send();
});

app.listen(3000, () => console.log('[+] Servidor en http://localhost:3000'));
Referencia Final
✅ Checklist de Seguridad Client-Side

Verificación completa de todas las medidas — válida como checklist de auditoría o de implementación.

Cabeceras HTTP de Seguridad

  • HSTS con max-age mínimo 1 año e includeSubDomains
  • Content-Security-Policy sin unsafe-inline en script-src
  • X-Frame-Options: DENY o CSP frame-ancestors 'none'
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy desactivando APIs no usadas
  • Versión del servidor ocultada (server_tokens off)

Cookies

  • Cookie de sesión con HttpOnly
  • Cookie de sesión con Secure
  • Cookie de sesión con SameSite=Strict
  • Prefijo __Host- en cookies de sesión críticas
  • Max-Age definido — no sesiones permanentes

XSS y CSP

  • Ningún uso de innerHTML, document.write() o eval() con datos del usuario
  • textContent en lugar de innerHTML para texto plano
  • DOMPurify para sanitizar HTML del usuario
  • CSP implementada con nonces o hashes — sin unsafe-inline
  • report-uri configurado para detectar violaciones CSP

CSRF, Sesiones y Recursos Externos

  • Tokens CSRF en todos los formularios de cambio de estado
  • Token de sesión regenerado tras login (prevenir fixation)
  • Logout destruye el token en el servidor — no solo la cookie
  • Tokens generados con CSPRNG (crypto.randomBytes)
  • SRI (integrity + crossorigin) en scripts y CSS de CDN
  • CORS con lista blanca explícita — no wildcard con credenciales
  • postMessage valida siempre el origen del mensaje

Herramientas de verificación

HerramientaURLQué verifica
Security Headerssecurityheaders.comCalificación A-F de todas las cabeceras
Mozilla Observatoryobservatory.mozilla.orgAuditoría completa: headers, cookies, CSP, CORS
CSP Evaluator (Google)csp-evaluator.withgoogle.comAnaliza la CSP y detecta bypasses
SSL Labsssllabs.com/ssltestConfiguración TLS y HSTS
HSTS Preloadhstspreload.orgEstado en la lista preload
SRI Hash Generatorsrihash.orgGenerar hashes SRI para CDN